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()); + } +}