From 26744dccdc10ff287d48964c75b330a52dc44831 Mon Sep 17 00:00:00 2001 From: Tom Page Date: Thu, 25 Feb 2021 12:24:06 +0000 Subject: [PATCH 1/4] MNT-22094 Store max change set rather than first. --- .../src/main/java/org/alfresco/solr/tracker/AclTracker.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 bc08e05b8..fbae630a0 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 @@ -468,11 +468,15 @@ public class AclTracker extends ActivatableTracker null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1); } - setLastChangeSetIdAndCommitTimeInTrackerState(firstChangeSets.getAclChangeSets(), state); Long maxChangeSetCommitTimeInRepo = firstChangeSets.getMaxChangeSetCommitTime(); Long maxChangeSetIdInRepo = firstChangeSets.getMaxChangeSetId(); + if (maxChangeSetCommitTimeInRepo != null && maxChangeSetIdInRepo != null) { + // We know the server has at least as many transactions as the index. + state.setLastChangeSetCommitTimeOnServer(maxChangeSetCommitTimeInRepo); + state.setLastChangeSetIdOnServer(maxChangeSetIdInRepo); + AclChangeSet maxAclTxInIndex = this.infoSrv.getMaxAclChangeSetIdAndCommitTimeInIndex(); if (maxAclTxInIndex.getCommitTimeMs() > maxChangeSetCommitTimeInRepo) { From c8453ee75fb72f34e6034bfaab8fe9f63719969c Mon Sep 17 00:00:00 2001 From: Tom Page Date: Thu, 25 Feb 2021 15:39:31 +0000 Subject: [PATCH 2/4] 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; From aab1b23b2881a49a83468c835b3257a6e1e2dff2 Mon Sep 17 00:00:00 2001 From: Tom Page Date: Fri, 26 Feb 2021 12:34:22 +0000 Subject: [PATCH 3/4] MNT-22094 Add option to allow start up even if first ACL tx is missing from index. Add new unit test and refactor them to use a real TrackerState object. --- .../org/alfresco/solr/tracker/AclTracker.java | 27 ++++- .../templates/rerank/conf/solrcore.properties | 2 + .../alfresco/solr/tracker/AclTrackerTest.java | 114 ++++++++++++------ 3 files changed, 107 insertions(+), 36 deletions(-) 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 ee435f219..30d4c2557 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 @@ -46,7 +46,6 @@ import java.util.stream.Collectors; import com.google.common.collect.Lists; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.httpclient.AuthenticationException; -import org.alfresco.repo.index.shard.ShardMethodEnum; import org.alfresco.solr.AclReport; import org.alfresco.solr.BoundedDeque; import org.alfresco.solr.InformationServer; @@ -84,6 +83,7 @@ public class AclTracker extends ActivatableTracker // Repository Remote API doesn't accept more than 512 aclChangeSetIds by invocation private static final int MAX_ACL_CHANGE_SET_BATCH_SIZE = 512; + private static final String ALLOW_MISSING_TRANSACTIONS_PROPERTY = "alfresco.aclTracker.allowMissingTransactions"; private int aclTrackerParallelism; @@ -105,6 +105,10 @@ public class AclTracker extends ActivatableTracker // Share run and write locks across all AclTracker threads private static Map RUN_LOCK_BY_CORE = new ConcurrentHashMap<>(); private static Map WRITE_LOCK_BY_CORE = new ConcurrentHashMap<>(); + + /** Allow starting the server even if the initial ACL transaction was not found. */ + private boolean allowMissingInitialAclTransaction = false; + @Override public Semaphore getWriteLock() { @@ -153,6 +157,9 @@ public class AclTracker extends ActivatableTracker RUN_LOCK_BY_CORE.put(coreName, new Semaphore(1, true)); WRITE_LOCK_BY_CORE.put(coreName, new Semaphore(1, true)); + + allowMissingInitialAclTransaction = Boolean.parseBoolean(p.getProperty(ALLOW_MISSING_TRANSACTIONS_PROPERTY, + Boolean.FALSE.toString())); } @Override @@ -413,6 +420,7 @@ public class AclTracker extends ActivatableTracker return; } + // Load the first ACL change sets from the Repository. AclChangeSets firstChangeSets = client.getAclChangeSets(null, 0L, null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1); @@ -447,7 +455,17 @@ public class AclTracker extends ActivatableTracker 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"); + String exceptionMessage = "Initial ACL transaction from DB with Id=" + firstAclTxId + + " and Timestamp=" + firstAclTxCommitTime + " was not found in Solr core."; + if (allowMissingInitialAclTransaction) + { + LOGGER.error(exceptionMessage); + LOGGER.error("Ignoring missing ACL Transaction and continuing to start up as {} set to true.", ALLOW_MISSING_TRANSACTIONS_PROPERTY); + } + else + { + throw new AlfrescoRuntimeException(exceptionMessage); + } } else if (setSize == 1) { @@ -935,4 +953,9 @@ public class AclTracker extends ActivatableTracker super.invalidateState(); infoSrv.clearProcessedAclChangeSets(); } + + public void setAllowMissingInitialAclTransaction(boolean allowMissingInitialAclTransaction) + { + this.allowMissingInitialAclTransaction = allowMissingInitialAclTransaction; + } } diff --git a/search-services/alfresco-search/src/main/resources/solr/instance/templates/rerank/conf/solrcore.properties b/search-services/alfresco-search/src/main/resources/solr/instance/templates/rerank/conf/solrcore.properties index fad380b0c..10633186d 100644 --- a/search-services/alfresco-search/src/main/resources/solr/instance/templates/rerank/conf/solrcore.properties +++ b/search-services/alfresco-search/src/main/resources/solr/instance/templates/rerank/conf/solrcore.properties @@ -137,6 +137,8 @@ alfresco.aclBatchSize=100 alfresco.contentReadBatchSize=100 alfresco.contentUpdateBatchSize=1000 alfresco.cascadeNodeBatchSize=10 +# Allow starting Solr with an existing index even if it does not match the data in the DB (this should not be enabled for production systems). +alfresco.aclTracker.allowMissingTransactions=false # Trackers thread pools # Keep Content Tracker max threads to 1/4 of other values, 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 index 1bec433b6..b7b32bd32 100644 --- 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 @@ -29,7 +29,8 @@ 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; @@ -48,104 +49,149 @@ import org.mockito.Mock; /** Unit tests for the {@link AclTracker}. */ public class AclTrackerTest { + /** The class under test. */ @InjectMocks - private AclTracker aclTracker = new AclTracker(); + private AclTracker aclTracker; + /** The class that gets information from the Repository. */ @Mock - private SOLRAPIClient solrAPIClient; + private SOLRAPIClient repositoryClient; + /** The class that gets information from Solr. */ @Mock - private InformationServer informationServer; - @Mock - private TrackerState trackerState; + private InformationServer solrInformationServer; @Before public void setUp() { + aclTracker = new AclTracker(); openMocks(this); } - /** Check that during the first run (with an empty index) then verification is successful. */ + /** Check that during the first run (with an empty repository and index) then verification is successful. */ @Test public void testCheckRepoAndIndexConsistency_firstRun_success() throws Exception { + TrackerState trackerState = new TrackerState(); AclChangeSets firstChangeSets = new AclChangeSets(emptyList()); - when(solrAPIClient.getAclChangeSets(null, 0L, + when(repositoryClient.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); + assertTrue("Expected first ACL transaction to have been checked.", trackerState.isCheckedFirstAclTransactionTime()); + assertTrue("Expected last ACL transaction to have been checked.", trackerState.isCheckedLastAclTransactionTime()); } /** 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); + TrackerState trackerState = new TrackerState(); + trackerState.setLastGoodChangeSetCommitTimeInIndex(8000L); + trackerState.setCheckedFirstAclTransactionTime(true); + trackerState.setCheckedLastAclTransactionTime(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); + verifyNoInteractions(repositoryClient, solrInformationServer); + } + + /** Check that during the first run (with data in the repo but an empty index) then verification is successful. */ + @Test + public void testCheckRepoAndIndexConsistency_populatedRepoEmptyIndex_success() throws Exception + { + TrackerState trackerState = new TrackerState(); + AclChangeSets firstChangeSets = new AclChangeSets(asList(new AclChangeSet(1, 1000, 2)), 8000L, 8L); + when(repositoryClient.getAclChangeSets(null, 0L, + null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1)).thenReturn(firstChangeSets); + + // Call the method under test. + aclTracker.checkRepoAndIndexConsistency(trackerState); + + assertTrue("Expected first ACL transaction to have been checked.", trackerState.isCheckedFirstAclTransactionTime()); + assertTrue("Expected last ACL transaction to have been checked.", trackerState.isCheckedLastAclTransactionTime()); + assertEquals("Expected last good change set commit time to be loaded from repository.", + trackerState.getLastGoodChangeSetCommitTimeInIndex(), 1000L); + assertEquals("Expected last change set commit time to be loaded from repository.", + trackerState.getLastChangeSetCommitTimeOnServer(), 1000L); + assertEquals("Expected last change set id to be loaded from repository.", + trackerState.getLastChangeSetIdOnServer(), 1); } /** Check that after downtime the validation is successful. */ @Test public void testCheckRepoAndIndexConsistency_afterRestart_success() throws Exception { - when(trackerState.getLastGoodChangeSetCommitTimeInIndex()).thenReturn(8000L); + TrackerState trackerState = new TrackerState(); + trackerState.setLastGoodChangeSetCommitTimeInIndex(8000L); AclChangeSets firstChangeSets = new AclChangeSets(asList(new AclChangeSet(1, 1000, 2)), 8000L, 8L); - when(solrAPIClient.getAclChangeSets(null, 0L, + when(repositoryClient.getAclChangeSets(null, 0L, null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1)).thenReturn(firstChangeSets); - when(informationServer.getAclTxDocsSize("1", "1000")).thenReturn(1); + when(solrInformationServer.getAclTxDocsSize("1", "1000")).thenReturn(1); // The index is behind the repo. AclChangeSet lastIndexedChangeSet = new AclChangeSet(7, 7000, 7); - when(informationServer.getMaxAclChangeSetIdAndCommitTimeInIndex()).thenReturn(lastIndexedChangeSet); + when(solrInformationServer.getMaxAclChangeSetIdAndCommitTimeInIndex()).thenReturn(lastIndexedChangeSet); // Call the method under test. aclTracker.checkRepoAndIndexConsistency(trackerState); - verify(trackerState).setCheckedFirstAclTransactionTime(true); - verify(trackerState).setCheckedLastAclTransactionTime(true); + assertTrue("Expected first ACL transaction to have been checked.", trackerState.isCheckedFirstAclTransactionTime()); + assertTrue("Expected last ACL transaction to have been checked.", trackerState.isCheckedLastAclTransactionTime()); } - /** Check that if the index is populated but the repository is empty then we get an exception. */ + /** Check that if the index is populated but missing the first ACL transaction then we get an exception. */ @Test(expected = AlfrescoRuntimeException.class) - public void testCheckRepoAndIndexConsistency_populatedIndexEmptyRepo_runtimeException() throws Exception + public void testCheckRepoAndIndexConsistency_indexMissingFirstACLTx_runtimeException() throws Exception { - when(trackerState.getLastGoodChangeSetCommitTimeInIndex()).thenReturn(8000L); + TrackerState trackerState = new TrackerState(); + trackerState.setLastGoodChangeSetCommitTimeInIndex(8000L); AclChangeSets firstChangeSets = new AclChangeSets(asList(new AclChangeSet(1, 1000, 2)), 8000L, 8L); - when(solrAPIClient.getAclChangeSets(null, 0L, + when(repositoryClient.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); + // The first ACL transaction was not found in Solr. + when(solrInformationServer.getAclTxDocsSize("1", "1000")).thenReturn(0); // Call the method under test. aclTracker.checkRepoAndIndexConsistency(trackerState); } + /** Check that allowMissingInitialAclTransaction allows start up if the index is missing the first ACL transaction. */ + @Test + public void testCheckRepoAndIndexConsistency_allowMissingInitialAclTransactionSet_errorIgnored() throws Exception + { + aclTracker.setAllowMissingInitialAclTransaction(true); + + TrackerState trackerState = new TrackerState(); + trackerState.setLastGoodChangeSetCommitTimeInIndex(8000L); + // Pretend that we've already checked the last ACL tx since it's not the purpose of this test. + trackerState.setCheckedLastAclTransactionTime(true); + AclChangeSets firstChangeSets = new AclChangeSets(asList(new AclChangeSet(1, 1000, 2)), 8000L, 8L); + when(repositoryClient.getAclChangeSets(null, 0L, + null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1)).thenReturn(firstChangeSets); + // The first ACL transaction was not found in Solr. + when(solrInformationServer.getAclTxDocsSize("1", "1000")).thenReturn(0); + + // Call the method under test and check no exception is thrown. + 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); + TrackerState trackerState = new TrackerState(); + trackerState.setLastGoodChangeSetCommitTimeInIndex(8000L); AclChangeSets firstChangeSets = new AclChangeSets(asList(new AclChangeSet(1, 1000, 2)), 8000L, 8L); - when(solrAPIClient.getAclChangeSets(null, 0L, + when(repositoryClient.getAclChangeSets(null, 0L, null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1)).thenReturn(firstChangeSets); - when(informationServer.getAclTxDocsSize("1", "1000")).thenReturn(1); + when(solrInformationServer.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); + when(solrInformationServer.getMaxAclChangeSetIdAndCommitTimeInIndex()).thenReturn(lastIndexedChangeSet); // Call the method under test. aclTracker.checkRepoAndIndexConsistency(trackerState); From 5ad4cff64d7a9733a4d2456e0c96bdb8368cd500 Mon Sep 17 00:00:00 2001 From: Tom Page Date: Fri, 26 Feb 2021 12:50:00 +0000 Subject: [PATCH 4/4] Revert new property, but add an extra log line. Solr does continue to track ACLs, but the index may be corrupted. --- .../org/alfresco/solr/tracker/AclTracker.java | 27 +++---------------- .../templates/rerank/conf/solrcore.properties | 2 -- .../alfresco/solr/tracker/AclTrackerTest.java | 20 -------------- 3 files changed, 3 insertions(+), 46 deletions(-) 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 30d4c2557..832d4df13 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 @@ -83,7 +83,6 @@ public class AclTracker extends ActivatableTracker // Repository Remote API doesn't accept more than 512 aclChangeSetIds by invocation private static final int MAX_ACL_CHANGE_SET_BATCH_SIZE = 512; - private static final String ALLOW_MISSING_TRANSACTIONS_PROPERTY = "alfresco.aclTracker.allowMissingTransactions"; private int aclTrackerParallelism; @@ -105,10 +104,6 @@ public class AclTracker extends ActivatableTracker // Share run and write locks across all AclTracker threads private static Map RUN_LOCK_BY_CORE = new ConcurrentHashMap<>(); private static Map WRITE_LOCK_BY_CORE = new ConcurrentHashMap<>(); - - /** Allow starting the server even if the initial ACL transaction was not found. */ - private boolean allowMissingInitialAclTransaction = false; - @Override public Semaphore getWriteLock() { @@ -157,9 +152,6 @@ public class AclTracker extends ActivatableTracker RUN_LOCK_BY_CORE.put(coreName, new Semaphore(1, true)); WRITE_LOCK_BY_CORE.put(coreName, new Semaphore(1, true)); - - allowMissingInitialAclTransaction = Boolean.parseBoolean(p.getProperty(ALLOW_MISSING_TRANSACTIONS_PROPERTY, - Boolean.FALSE.toString())); } @Override @@ -454,18 +446,10 @@ public class AclTracker extends ActivatableTracker "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("Notice that SOLR will continue to track the repository, but the index may be corrupted."); LOGGER.error("You can also check your SOLR connection details in solrcore.properties."); - String exceptionMessage = "Initial ACL transaction from DB with Id=" + firstAclTxId - + " and Timestamp=" + firstAclTxCommitTime + " was not found in Solr core."; - if (allowMissingInitialAclTransaction) - { - LOGGER.error(exceptionMessage); - LOGGER.error("Ignoring missing ACL Transaction and continuing to start up as {} set to true.", ALLOW_MISSING_TRANSACTIONS_PROPERTY); - } - else - { - throw new AlfrescoRuntimeException(exceptionMessage); - } + throw new AlfrescoRuntimeException("Initial ACL transaction from DB with Id=" + firstAclTxId + + " and Timestamp=" + firstAclTxCommitTime + " was not found in Solr core."); } else if (setSize == 1) { @@ -953,9 +937,4 @@ public class AclTracker extends ActivatableTracker super.invalidateState(); infoSrv.clearProcessedAclChangeSets(); } - - public void setAllowMissingInitialAclTransaction(boolean allowMissingInitialAclTransaction) - { - this.allowMissingInitialAclTransaction = allowMissingInitialAclTransaction; - } } diff --git a/search-services/alfresco-search/src/main/resources/solr/instance/templates/rerank/conf/solrcore.properties b/search-services/alfresco-search/src/main/resources/solr/instance/templates/rerank/conf/solrcore.properties index 10633186d..fad380b0c 100644 --- a/search-services/alfresco-search/src/main/resources/solr/instance/templates/rerank/conf/solrcore.properties +++ b/search-services/alfresco-search/src/main/resources/solr/instance/templates/rerank/conf/solrcore.properties @@ -137,8 +137,6 @@ alfresco.aclBatchSize=100 alfresco.contentReadBatchSize=100 alfresco.contentUpdateBatchSize=1000 alfresco.cascadeNodeBatchSize=10 -# Allow starting Solr with an existing index even if it does not match the data in the DB (this should not be enabled for production systems). -alfresco.aclTracker.allowMissingTransactions=false # Trackers thread pools # Keep Content Tracker max threads to 1/4 of other values, 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 index b7b32bd32..b34cb07b2 100644 --- 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 @@ -158,26 +158,6 @@ public class AclTrackerTest aclTracker.checkRepoAndIndexConsistency(trackerState); } - /** Check that allowMissingInitialAclTransaction allows start up if the index is missing the first ACL transaction. */ - @Test - public void testCheckRepoAndIndexConsistency_allowMissingInitialAclTransactionSet_errorIgnored() throws Exception - { - aclTracker.setAllowMissingInitialAclTransaction(true); - - TrackerState trackerState = new TrackerState(); - trackerState.setLastGoodChangeSetCommitTimeInIndex(8000L); - // Pretend that we've already checked the last ACL tx since it's not the purpose of this test. - trackerState.setCheckedLastAclTransactionTime(true); - AclChangeSets firstChangeSets = new AclChangeSets(asList(new AclChangeSet(1, 1000, 2)), 8000L, 8L); - when(repositoryClient.getAclChangeSets(null, 0L, - null, INITIAL_MAX_ACL_CHANGE_SET_ID, 1)).thenReturn(firstChangeSets); - // The first ACL transaction was not found in Solr. - when(solrInformationServer.getAclTxDocsSize("1", "1000")).thenReturn(0); - - // Call the method under test and check no exception is thrown. - 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