Merge branch 'stable-3.2' into stable-3.3
* stable-3.2:
Auto-reindex: Restore indexIfNeeded for Groups
@Mock GroupReference was replaced by instance of the object to fix
java.lang.StackOverflowError.
Change-Id: Iac6b1cbac9f9eef6d618a563337b67b246521c1c
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
index 1d31ebc..fbb0a8f 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
@@ -14,22 +14,52 @@
package com.ericsson.gerrit.plugins.highavailability.autoreindex;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexGroupHandler;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
+import com.google.common.collect.Streams;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.AccountGroupByIdAudit;
+import com.google.gerrit.entities.AccountGroupMemberAudit;
import com.google.gerrit.entities.GroupReference;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.db.Groups;
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.inject.Inject;
+import java.io.IOException;
import java.sql.Timestamp;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
import java.util.Optional;
+import java.util.stream.Stream;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Repository;
public class GroupReindexRunnable extends ReindexRunnable<GroupReference> {
+ private static final FluentLogger log = FluentLogger.forEnclosingClass();
+
private final Groups groups;
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsers;
+ private final ForwardedIndexGroupHandler indexer;
@Inject
- public GroupReindexRunnable(IndexTs indexTs, OneOffRequestContext ctx, Groups groups) {
+ public GroupReindexRunnable(
+ ForwardedIndexGroupHandler indexer,
+ IndexTs indexTs,
+ OneOffRequestContext ctx,
+ Groups groups,
+ GitRepositoryManager repoManager,
+ AllUsersName allUsers) {
super(AbstractIndexRestApiServlet.IndexName.GROUP, indexTs, ctx);
this.groups = groups;
+ this.repoManager = repoManager;
+ this.allUsers = allUsers;
+ this.indexer = indexer;
}
@Override
@@ -39,6 +69,54 @@
@Override
protected Optional<Timestamp> indexIfNeeded(GroupReference g, Timestamp sinceTs) {
+ try {
+ Optional<InternalGroup> internalGroup = groups.getGroup(g.getUUID());
+ if (internalGroup.isPresent()) {
+ InternalGroup group = internalGroup.get();
+ Timestamp groupCreationTs = group.getCreatedOn();
+
+ Repository allUsersRepo = repoManager.openRepository(allUsers);
+
+ List<AccountGroupByIdAudit> subGroupMembersAud =
+ groups.getSubgroupsAudit(allUsersRepo, g.getUUID());
+ Stream<Timestamp> groupIdAudAddedTs =
+ subGroupMembersAud.stream()
+ .map(AccountGroupByIdAudit::addedOn)
+ .filter(Objects::nonNull);
+ Stream<Timestamp> groupIdAudRemovedTs =
+ subGroupMembersAud.stream()
+ .map(AccountGroupByIdAudit::removedOn)
+ .filter(Optional<Timestamp>::isPresent)
+ .map(Optional<Timestamp>::get);
+
+ List<AccountGroupMemberAudit> groupMembersAud =
+ groups.getMembersAudit(allUsersRepo, g.getUUID());
+ Stream<Timestamp> groupMemberAudAddedTs =
+ groupMembersAud.stream().map(AccountGroupMemberAudit::addedOn).filter(Objects::nonNull);
+ Stream<Timestamp> groupMemberAudRemovedTs =
+ groupMembersAud.stream()
+ .map(AccountGroupMemberAudit::removedOn)
+ .filter(Optional<Timestamp>::isPresent)
+ .map(Optional<Timestamp>::get);
+
+ Optional<Timestamp> groupLastTs =
+ Streams.concat(
+ groupIdAudAddedTs,
+ groupIdAudRemovedTs,
+ groupMemberAudAddedTs,
+ groupMemberAudRemovedTs,
+ Stream.of(groupCreationTs))
+ .max(Comparator.naturalOrder());
+
+ if (groupLastTs.isPresent() && groupLastTs.get().after(sinceTs)) {
+ log.atInfo().log("Index {}/{}/{}", g.getUUID(), g.getName(), groupLastTs.get());
+ indexer.index(g.getUUID(), Operation.INDEX, Optional.empty());
+ return groupLastTs;
+ }
+ }
+ } catch (IOException | ConfigInvalidException e) {
+ log.atSevere().withCause(e).log("Reindex failed");
+ }
return Optional.empty();
}
}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnableTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnableTest.java
new file mode 100644
index 0000000..7ab4a1c
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnableTest.java
@@ -0,0 +1,209 @@
+// 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.ericsson.gerrit.plugins.highavailability.autoreindex;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexGroupHandler;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Account.Id;
+import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.AccountGroup.UUID;
+import com.google.gerrit.entities.AccountGroupByIdAudit;
+import com.google.gerrit.entities.AccountGroupMemberAudit;
+import com.google.gerrit.entities.GroupReference;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.group.InternalGroup;
+import com.google.gerrit.server.group.db.Groups;
+import com.google.gerrit.server.util.OneOffRequestContext;
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+import java.util.Collections;
+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 GroupReindexRunnableTest {
+
+ @Mock private ForwardedIndexGroupHandler indexer;
+ @Mock private IndexTs indexTs;
+ @Mock private OneOffRequestContext ctx;
+ @Mock private Groups groups;
+ @Mock private GitRepositoryManager repoManager;
+ @Mock private AllUsersName allUsers;
+ private GroupReference groupReference;
+
+ private GroupReindexRunnable groupReindexRunnable;
+ private static UUID uuid;
+
+ @Before
+ public void setUp() throws Exception {
+ groupReindexRunnable =
+ new GroupReindexRunnable(indexer, indexTs, ctx, groups, repoManager, allUsers);
+ uuid = UUID.parse("123");
+ groupReference = GroupReference.create(uuid, "123");
+ }
+
+ @Test
+ public void groupIsIndexedWhenItIsCreatedAfterLastGroupReindex() throws Exception {
+ Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now());
+ Timestamp afterCurrentTime = new Timestamp(currentTime.getTime() + 1000L);
+ when(groups.getGroup(uuid)).thenReturn(getInternalGroup(afterCurrentTime));
+
+ Optional<Timestamp> groupLastTs =
+ groupReindexRunnable.indexIfNeeded(groupReference, currentTime);
+ assertThat(groupLastTs.isPresent()).isTrue();
+ assertThat(groupLastTs.get()).isEqualTo(afterCurrentTime);
+ verify(indexer).index(uuid, Operation.INDEX, Optional.empty());
+ }
+
+ @Test
+ public void groupIsNotIndexedWhenItIsCreatedBeforeLastGroupReindex() throws Exception {
+ Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now());
+ Timestamp beforeCurrentTime = new Timestamp(currentTime.getTime() - 1000L);
+ when(groups.getGroup(uuid)).thenReturn(getInternalGroup(beforeCurrentTime));
+
+ Optional<Timestamp> groupLastTs =
+ groupReindexRunnable.indexIfNeeded(groupReference, currentTime);
+ assertThat(groupLastTs.isPresent()).isFalse();
+ verify(indexer, never()).index(uuid, Operation.INDEX, Optional.empty());
+ }
+
+ @Test
+ public void groupIsNotIndexedGroupReferenceNotPresent() {
+ Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now());
+ Optional<Timestamp> groupLastTs =
+ groupReindexRunnable.indexIfNeeded(groupReference, currentTime);
+ assertThat(groupLastTs.isPresent()).isFalse();
+ }
+
+ @Test
+ public void groupIsIndexedWhenNewUserAddedAfterLastGroupReindex() throws Exception {
+ Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now());
+ Timestamp afterCurrentTime = new Timestamp(currentTime.getTime() + 1000L);
+ when(groups.getGroup(uuid)).thenReturn(getInternalGroup(currentTime));
+ when(groups.getMembersAudit(any(), any()))
+ .thenReturn(
+ Collections.singletonList(
+ AccountGroupMemberAudit.builder()
+ .addedBy(Account.Id.tryParse("1").get())
+ .addedOn(afterCurrentTime)
+ .memberId(Account.Id.tryParse("1").get())
+ .groupId(AccountGroup.Id.parse("1"))
+ .build()));
+ Optional<Timestamp> groupLastTs =
+ groupReindexRunnable.indexIfNeeded(groupReference, currentTime);
+ assertThat(groupLastTs.isPresent()).isTrue();
+ assertThat(groupLastTs.get()).isEqualTo(afterCurrentTime);
+ verify(indexer).index(uuid, Operation.INDEX, Optional.empty());
+ }
+
+ @Test
+ public void groupIsIndexedWhenUserRemovedAfterLastGroupReindex() throws Exception {
+ Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now());
+ Timestamp afterCurrentTime = new Timestamp(currentTime.getTime() + 1000L);
+ Timestamp beforeCurrentTime = new Timestamp(currentTime.getTime() - 1000L);
+
+ AccountGroupMemberAudit accountGroupMemberAudit =
+ AccountGroupMemberAudit.builder()
+ .addedBy(Account.Id.tryParse("1").get())
+ .addedOn(beforeCurrentTime)
+ .memberId(Account.Id.tryParse("1").get())
+ .groupId(AccountGroup.Id.parse("2"))
+ .removed(Account.Id.tryParse("2").get(), afterCurrentTime)
+ .build();
+ when(groups.getGroup(uuid)).thenReturn(getInternalGroup(currentTime));
+ when(groups.getMembersAudit(any(), any()))
+ .thenReturn(Collections.singletonList(accountGroupMemberAudit));
+ Optional<Timestamp> groupLastTs =
+ groupReindexRunnable.indexIfNeeded(groupReference, currentTime);
+ assertThat(groupLastTs.isPresent()).isTrue();
+ assertThat(groupLastTs.get()).isEqualTo(afterCurrentTime);
+ verify(indexer).index(uuid, Operation.INDEX, Optional.empty());
+ }
+
+ @Test
+ public void groupIsIndexedWhenItIsSubGroupAddedAfterLastGroupReindex() throws Exception {
+ Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now());
+ Timestamp afterCurrentTime = new Timestamp(currentTime.getTime() + 1000L);
+ Timestamp beforeCurrentTime = new Timestamp(currentTime.getTime() - 1000L);
+ when(groups.getGroup(uuid)).thenReturn(getInternalGroup(beforeCurrentTime));
+ when(groups.getSubgroupsAudit(any(), any()))
+ .thenReturn(
+ Collections.singletonList(
+ AccountGroupByIdAudit.builder()
+ .groupId(AccountGroup.Id.parse("1"))
+ .includeUuid(UUID.parse("123"))
+ .addedBy(Account.Id.tryParse("1").get())
+ .addedOn(afterCurrentTime)
+ .build()));
+ Optional<Timestamp> groupLastTs =
+ groupReindexRunnable.indexIfNeeded(groupReference, currentTime);
+ assertThat(groupLastTs.isPresent()).isTrue();
+ assertThat(groupLastTs.get()).isEqualTo(afterCurrentTime);
+ verify(indexer).index(uuid, Operation.INDEX, Optional.empty());
+ }
+
+ @Test
+ public void groupIsIndexedWhenItIsSubGroupRemovedAfterLastGroupReindex() throws Exception {
+ Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now());
+ Timestamp afterCurrentTime = new Timestamp(currentTime.getTime() + 1000L);
+ Timestamp beforeCurrentTime = new Timestamp(currentTime.getTime() - 1000L);
+
+ AccountGroupByIdAudit accountGroupByIdAud =
+ AccountGroupByIdAudit.builder()
+ .groupId(AccountGroup.Id.parse("1"))
+ .includeUuid(UUID.parse("123"))
+ .addedBy(Account.Id.tryParse("1").get())
+ .addedOn(beforeCurrentTime)
+ .removed(Account.Id.tryParse("2").get(), afterCurrentTime)
+ .build();
+
+ when(groups.getGroup(uuid)).thenReturn(getInternalGroup(beforeCurrentTime));
+ when(groups.getSubgroupsAudit(any(), any()))
+ .thenReturn(Collections.singletonList(accountGroupByIdAud));
+
+ Optional<Timestamp> groupLastTs =
+ groupReindexRunnable.indexIfNeeded(groupReference, currentTime);
+ assertThat(groupLastTs.isPresent()).isTrue();
+ assertThat(groupLastTs.get()).isEqualTo(afterCurrentTime);
+ verify(indexer).index(uuid, Operation.INDEX, Optional.empty());
+ }
+
+ private Optional<InternalGroup> getInternalGroup(Timestamp timestamp) {
+ return Optional.ofNullable(
+ InternalGroup.builder()
+ .setId(AccountGroup.Id.parse("1"))
+ .setNameKey(AccountGroup.nameKey("Test"))
+ .setOwnerGroupUUID(uuid)
+ .setVisibleToAll(true)
+ .setGroupUUID(UUID.parse("12"))
+ .setCreatedOn(timestamp)
+ .setMembers(ImmutableSet.<Id>builder().build())
+ .setSubgroups(ImmutableSet.<UUID>builder().build())
+ .build());
+ }
+}