mirror of
https://github.com/Alfresco/SearchServices.git
synced 2026-09-16 18:12:56 +00:00
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.
This commit is contained in:
+25
-2
@@ -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<String, Semaphore> RUN_LOCK_BY_CORE = new ConcurrentHashMap<>();
|
||||
private static Map<String, Semaphore> 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;
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -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,
|
||||
|
||||
+80
-34
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user