From c8453ee75fb72f34e6034bfaab8fe9f63719969c Mon Sep 17 00:00:00 2001 From: Tom Page Date: Thu, 25 Feb 2021 15:39:31 +0000 Subject: [PATCH] MNT-22094 Add unit tests for AclTracker. Minor refactor to the way firstChangeSets is loaded and fix to call state.setCheckedFirstAclTransactionTime(true) rather than state.setCheckedFirstTransactionTime(true). --- .../org/alfresco/solr/tracker/AclTracker.java | 77 +++++---- .../alfresco/solr/tracker/AclTrackerTest.java | 153 ++++++++++++++++++ .../alfresco/solr/client/AclChangeSets.java | 2 +- 3 files changed, 190 insertions(+), 42 deletions(-) create mode 100644 search-services/alfresco-search/src/test/java/org/alfresco/solr/tracker/AclTrackerTest.java diff --git a/search-services/alfresco-search/src/main/java/org/alfresco/solr/tracker/AclTracker.java b/search-services/alfresco-search/src/main/java/org/alfresco/solr/tracker/AclTracker.java index fbae630a0..ee435f219 100644 --- a/search-services/alfresco-search/src/main/java/org/alfresco/solr/tracker/AclTracker.java +++ b/search-services/alfresco-search/src/main/java/org/alfresco/solr/tracker/AclTracker.java @@ -77,7 +77,7 @@ public class AclTracker extends ActivatableTracker private static final int DEFAULT_ACL_TRACKER_MAX_PARALLELISM = 32; private static final long DEFAULT_ACL_TRACKER_TIMESTEP = TIME_STEP_1_HR_IN_MS; - private static final long INITIAL_MAX_ACL_CHANGE_SET_ID = 2000L; + protected static final long INITIAL_MAX_ACL_CHANGE_SET_ID = 2000L; private static final int MAX_NUMBER_OF_ACL_CHANGE_SETS = 2000; private static final long MAX_TIME_STEP = TIME_STEP_32_DAYS_IN_MS; @@ -405,17 +405,23 @@ public class AclTracker extends ActivatableTracker /** * Checks the first and last TX time */ - private void checkRepoAndIndexConsistency(TrackerState state) throws AuthenticationException, IOException, JSONException + protected void checkRepoAndIndexConsistency(TrackerState state) throws AuthenticationException, IOException, JSONException { - AclChangeSets firstChangeSets = null; + if (state.getLastGoodChangeSetCommitTimeInIndex() != 0 && state.isCheckedFirstAclTransactionTime() && state.isCheckedLastAclTransactionTime()) + { + // Verification done previously. + return; + } + + AclChangeSets firstChangeSets = client.getAclChangeSets(null, 0L, + null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1); + if (state.getLastGoodChangeSetCommitTimeInIndex() == 0) { state.setCheckedLastAclTransactionTime(true); state.setCheckedFirstAclTransactionTime(true); LOGGER.info("[CORE {}] - No acl transactions found - no verification required", coreName); - - firstChangeSets = client.getAclChangeSets(null, 0L, - null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1); + if (!firstChangeSets.getAclChangeSets().isEmpty()) { AclChangeSet firstChangeSet = firstChangeSets.getAclChangeSets().get(0); @@ -425,49 +431,38 @@ public class AclTracker extends ActivatableTracker } } - if (!state.isCheckedFirstAclTransactionTime()) + if (!state.isCheckedFirstAclTransactionTime() && !firstChangeSets.getAclChangeSets().isEmpty()) { - firstChangeSets = client.getAclChangeSets(null, 0L, - null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1); - if (!firstChangeSets.getAclChangeSets().isEmpty()) + AclChangeSet firstAclChangeSet = firstChangeSets.getAclChangeSets().get(0); + long firstAclTxId = firstAclChangeSet.getId(); + long firstAclTxCommitTime = firstAclChangeSet.getCommitTimeMs(); + int setSize = this.infoSrv.getAclTxDocsSize(Long.toString(firstAclTxId), + Long.toString(firstAclTxCommitTime)); + + if (setSize == 0) { - AclChangeSet firstAclChangeSet= firstChangeSets.getAclChangeSets().get(0); - long firstAclTxId = firstAclChangeSet.getId(); - long firstAclTxCommitTime = firstAclChangeSet.getCommitTimeMs(); - int setSize = this.infoSrv.getAclTxDocsSize(Long.toString(firstAclTxId), - Long.toString(firstAclTxCommitTime)); - - if (setSize == 0) - { - LOGGER.error("[CORE {}] First acl transaction was not found with the correct timestamp.", coreName); - LOGGER.error("SOLR has successfully connected to your repository " + - "however the SOLR indexes and repository database do not match."); - LOGGER.error("If this is a new or rebuilt database your SOLR indexes " + - "also need to be re-built to match the database."); - LOGGER.error("You can also check your SOLR connection details in solrcore.properties."); - throw new AlfrescoRuntimeException("Initial acl transaction not found with correct timestamp"); - } - else if (setSize == 1) - { - state.setCheckedFirstTransactionTime(true); - LOGGER.info("[CORE {}] Verified first acl transaction and timestamp in index", coreName); - } - else - { - LOGGER.warn("[CORE {}] Duplicate initial acl transaction found with correct timestamp", coreName); - } + LOGGER.error("[CORE {}] First acl transaction was not found with the correct timestamp.", coreName); + LOGGER.error("SOLR has successfully connected to your repository " + + "however the SOLR indexes and repository database do not match."); + LOGGER.error("If this is a new or rebuilt database your SOLR indexes " + + "also need to be re-built to match the database."); + LOGGER.error("You can also check your SOLR connection details in solrcore.properties."); + throw new AlfrescoRuntimeException("Initial acl transaction not found with correct timestamp"); + } + else if (setSize == 1) + { + state.setCheckedFirstAclTransactionTime(true); + LOGGER.info("[CORE {}] Verified first acl transaction and timestamp in index", coreName); + } + else + { + LOGGER.warn("[CORE {}] Duplicate initial acl transaction found with correct timestamp", coreName); } } // Checks that the last aclTxId in solr is <= last aclTxId in repo if (!state.isCheckedLastAclTransactionTime()) { - if (firstChangeSets == null) - { - firstChangeSets = client.getAclChangeSets(null, 0L, - null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1); - } - Long maxChangeSetCommitTimeInRepo = firstChangeSets.getMaxChangeSetCommitTime(); Long maxChangeSetIdInRepo = firstChangeSets.getMaxChangeSetId(); diff --git a/search-services/alfresco-search/src/test/java/org/alfresco/solr/tracker/AclTrackerTest.java b/search-services/alfresco-search/src/test/java/org/alfresco/solr/tracker/AclTrackerTest.java new file mode 100644 index 000000000..1bec433b6 --- /dev/null +++ b/search-services/alfresco-search/src/test/java/org/alfresco/solr/tracker/AclTrackerTest.java @@ -0,0 +1,153 @@ +/* + * #%L + * Alfresco Search Services + * %% + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.solr.tracker; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; + +import static org.alfresco.solr.tracker.AclTracker.INITIAL_MAX_ACL_CHANGE_SET_ID; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.solr.InformationServer; +import org.alfresco.solr.TrackerState; +import org.alfresco.solr.client.AclChangeSet; +import org.alfresco.solr.client.AclChangeSets; +import org.alfresco.solr.client.SOLRAPIClient; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +/** Unit tests for the {@link AclTracker}. */ +public class AclTrackerTest +{ + @InjectMocks + private AclTracker aclTracker = new AclTracker(); + @Mock + private SOLRAPIClient solrAPIClient; + @Mock + private InformationServer informationServer; + @Mock + private TrackerState trackerState; + + @Before + public void setUp() + { + openMocks(this); + } + + /** Check that during the first run (with an empty index) then verification is successful. */ + @Test + public void testCheckRepoAndIndexConsistency_firstRun_success() throws Exception + { + AclChangeSets firstChangeSets = new AclChangeSets(emptyList()); + when(solrAPIClient.getAclChangeSets(null, 0L, + null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1)).thenReturn(firstChangeSets); + when(informationServer.getAclTxDocsSize("1", "1000")).thenReturn(1); + + // Call the method under test. + aclTracker.checkRepoAndIndexConsistency(trackerState); + + verify(trackerState).setCheckedFirstAclTransactionTime(true); + verify(trackerState).setCheckedLastAclTransactionTime(true); + } + + /** Check that subsequent checks of a running index don't make expensive requests. */ + @Test + public void testCheckRepoAndIndexConsistency_alreadyInitialised_success() throws Exception + { + when(trackerState.getLastGoodChangeSetCommitTimeInIndex()).thenReturn(8000L); + when(trackerState.isCheckedFirstAclTransactionTime()).thenReturn(true); + when(trackerState.isCheckedLastAclTransactionTime()).thenReturn(true); + + // Call the method under test. + aclTracker.checkRepoAndIndexConsistency(trackerState); + + // Check that we don't make any expensive calls to the index or the repo. + verifyNoInteractions(solrAPIClient, informationServer); + } + + /** Check that after downtime the validation is successful. */ + @Test + public void testCheckRepoAndIndexConsistency_afterRestart_success() throws Exception + { + when(trackerState.getLastGoodChangeSetCommitTimeInIndex()).thenReturn(8000L); + AclChangeSets firstChangeSets = new AclChangeSets(asList(new AclChangeSet(1, 1000, 2)), 8000L, 8L); + when(solrAPIClient.getAclChangeSets(null, 0L, + null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1)).thenReturn(firstChangeSets); + when(informationServer.getAclTxDocsSize("1", "1000")).thenReturn(1); + + // The index is behind the repo. + AclChangeSet lastIndexedChangeSet = new AclChangeSet(7, 7000, 7); + when(informationServer.getMaxAclChangeSetIdAndCommitTimeInIndex()).thenReturn(lastIndexedChangeSet); + + // Call the method under test. + aclTracker.checkRepoAndIndexConsistency(trackerState); + + verify(trackerState).setCheckedFirstAclTransactionTime(true); + verify(trackerState).setCheckedLastAclTransactionTime(true); + } + + /** Check that if the index is populated but the repository is empty then we get an exception. */ + @Test(expected = AlfrescoRuntimeException.class) + public void testCheckRepoAndIndexConsistency_populatedIndexEmptyRepo_runtimeException() throws Exception + { + when(trackerState.getLastGoodChangeSetCommitTimeInIndex()).thenReturn(8000L); + AclChangeSets firstChangeSets = new AclChangeSets(asList(new AclChangeSet(1, 1000, 2)), 8000L, 8L); + when(solrAPIClient.getAclChangeSets(null, 0L, + null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1)).thenReturn(firstChangeSets); + // The first ACL transaction was not found in the repository. + when(informationServer.getAclTxDocsSize("1", "1000")).thenReturn(0); + + AclChangeSet lastIndexedChangeSet = new AclChangeSet(8, 8000, 8); + when(informationServer.getMaxAclChangeSetIdAndCommitTimeInIndex()).thenReturn(lastIndexedChangeSet); + + // Call the method under test. + aclTracker.checkRepoAndIndexConsistency(trackerState); + } + + /** Check that if the last ACL in the index is after the last ACL in the repository then we get an exception. */ + @Test (expected = AlfrescoRuntimeException.class) + public void testCheckRepoAndIndexConsistency_indexAheadOfRepo_runtimeException() throws Exception + { + when(trackerState.getLastGoodChangeSetCommitTimeInIndex()).thenReturn(8000L); + AclChangeSets firstChangeSets = new AclChangeSets(asList(new AclChangeSet(1, 1000, 2)), 8000L, 8L); + when(solrAPIClient.getAclChangeSets(null, 0L, + null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1)).thenReturn(firstChangeSets); + when(informationServer.getAclTxDocsSize("1", "1000")).thenReturn(1); + + // The index contains an ACL after the last one from the server (id 8 at time 8000L). + AclChangeSet lastIndexedChangeSet = new AclChangeSet(9, 9000, 9); + when(informationServer.getMaxAclChangeSetIdAndCommitTimeInIndex()).thenReturn(lastIndexedChangeSet); + + // Call the method under test. + aclTracker.checkRepoAndIndexConsistency(trackerState); + } +} diff --git a/search-services/alfresco-solrclient-lib/src/main/java/org/alfresco/solr/client/AclChangeSets.java b/search-services/alfresco-solrclient-lib/src/main/java/org/alfresco/solr/client/AclChangeSets.java index 052064051..3af03c36f 100644 --- a/search-services/alfresco-solrclient-lib/src/main/java/org/alfresco/solr/client/AclChangeSets.java +++ b/search-services/alfresco-solrclient-lib/src/main/java/org/alfresco/solr/client/AclChangeSets.java @@ -43,7 +43,7 @@ public class AclChangeSets private Long maxChangeSetId; - AclChangeSets(List aclChangeSets, Long maxChangeSetCommitTime, Long maxChangeSetId) + public AclChangeSets(List aclChangeSets, Long maxChangeSetCommitTime, Long maxChangeSetId) { this.aclChangeSets = (aclChangeSets == null ? null : new ArrayList<>(aclChangeSets)); this.maxChangeSetCommitTime = maxChangeSetCommitTime;