mirror of
https://github.com/Alfresco/SearchServices.git
synced 2026-09-16 18:12:56 +00:00
SEARCH-2330: enable/disable indexing (#856)
* [ SEARCH-2330 ] AlfrescoCoreAdminHandler START/STOP (no logic) + Unit tests * [ SEARCH-2330 ] Activatable + ActivatableTracker * [ SEARCH-2330 ] AbstractShardInformationPublisher and NodeStatePublisher removed (replaced by ShardStatePublisher) * [ SEARCH-2330 ] Test fixes * [ SEARCH-2330 ] Fix ClassCastException on ShardStatePublisher * [ SEARCH-2330 ] Fix Unnecessary Stubbing * [ SEARCH-2330 ] ActivatableTrackerTest * [ SEARCH-2330 ] Fix action changes (it depends on the indexing state) * [ SEARCH-2330 ] Purge on stop/start * [ SEARCH-2330 ] Rollback on scheduled maintenance + Unit tests * [ SEARCH-2330 ] Persist indexing state across core reloads * [ SEARCH-2330 ] Minor renaming on tests * [ SEARCH-2330 ] Fix Unit test failures * [ SEARCH-2330 ] Fix E2E * [ SEARCH-2330 ] Fix Retry expectation * [ SEARCH-2330 ] Fix Json Path in retry expectaction * [ SEARCH-2330 ] Fix ClassCastException * [ SEARCH-2330 ] Review comments addressed * [ SEARCH-2330 ] Review comments addressed (additional comments in the code)
This commit is contained in:
+36
-26
@@ -28,6 +28,7 @@ package org.alfresco.test.search.functional.searchServices.solr.admin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -38,6 +39,8 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* End to end tests for SOLR Admin actions REST API, available from:
|
||||
*
|
||||
@@ -556,9 +559,11 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
RestResponse response = restClient.withParams("txid=" + txid).withSolrAdminAPI().getAction("purge");
|
||||
|
||||
checkResponseStatusOk(response);
|
||||
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action.status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
|
||||
DEFAULT_CORE_NAMES.forEach(core -> {
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action." + core + ".status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -566,7 +571,7 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test(priority = 25)
|
||||
public void testPurgeCore() throws Exception
|
||||
public void testPurgeCore()
|
||||
{
|
||||
final Integer txid = 1;
|
||||
|
||||
@@ -578,7 +583,7 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
|
||||
checkResponseStatusOk(response);
|
||||
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action.status");
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action." + core + ".status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -599,9 +604,11 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
RestResponse response = restClient.withSolrAdminAPI().getAction("purge");
|
||||
|
||||
checkResponseStatusOk(response);
|
||||
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action.status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
|
||||
DEFAULT_CORE_NAMES.forEach(core -> {
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action." + core + ".status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -661,9 +668,11 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
RestResponse response = restClient.withParams("txid=" + txid).withSolrAdminAPI().getAction("reindex");
|
||||
|
||||
checkResponseStatusOk(response);
|
||||
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action.status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
|
||||
DEFAULT_CORE_NAMES.forEach(core -> {
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action." + core + ".status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -671,7 +680,7 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test(priority = 30)
|
||||
public void testReindexCore() throws Exception
|
||||
public void testReindexCore()
|
||||
{
|
||||
Integer txid = 1;
|
||||
|
||||
@@ -683,7 +692,7 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
|
||||
checkResponseStatusOk(response);
|
||||
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action.status");
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action." + core + ".status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -705,12 +714,12 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
|
||||
checkResponseStatusOk(response);
|
||||
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action.status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
|
||||
DEFAULT_CORE_NAMES.forEach(core -> {
|
||||
List<String> errorNodeList = response.getResponse().body().jsonPath().get("action." + core);
|
||||
Assert.assertEquals(errorNodeList, Arrays.asList(), "Expected no error nodes,");
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action." + core + ".status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
|
||||
List<String> errorNodeList = response.getResponse().body().jsonPath().get("action." + core + "['Error Nodes']");
|
||||
Assert.assertEquals(errorNodeList, emptyList(), "Expected no error nodes,");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -719,7 +728,7 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test(priority = 32)
|
||||
public void testRetryCore() throws Exception
|
||||
public void testRetryCore()
|
||||
{
|
||||
DEFAULT_CORE_NAMES.forEach(core -> {
|
||||
|
||||
@@ -729,11 +738,11 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
|
||||
checkResponseStatusOk(response);
|
||||
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action.status");
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action." + core + ".status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
|
||||
List<String> errorNodeList = response.getResponse().body().jsonPath().get("action." + core);
|
||||
Assert.assertEquals(errorNodeList, Arrays.asList(), "Expected no error nodes,");
|
||||
List<String> errorNodeList = response.getResponse().body().jsonPath().get("action." + core + "['Error Nodes']");
|
||||
Assert.assertEquals(errorNodeList, emptyList(), "Expected no error nodes,");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -755,9 +764,10 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
RestResponse response = restClient.withParams("txid=" + txid).withSolrAdminAPI().getAction("index");
|
||||
|
||||
checkResponseStatusOk(response);
|
||||
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action.status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
DEFAULT_CORE_NAMES.forEach(core -> {
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action." + core + ".status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -777,7 +787,7 @@ public class SolrE2eAdminTest extends AbstractE2EFunctionalTest
|
||||
|
||||
checkResponseStatusOk(response);
|
||||
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action.status");
|
||||
String actionStatus = response.getResponse().body().jsonPath().get("action." + core + ".status");
|
||||
Assert.assertEquals(actionStatus, "scheduled");
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
+205
-57
@@ -33,12 +33,12 @@ import org.alfresco.solr.adapters.IOpenBitSet;
|
||||
import org.alfresco.solr.client.SOLRAPIClientFactory;
|
||||
import org.alfresco.solr.config.ConfigUtil;
|
||||
import org.alfresco.solr.tracker.AclTracker;
|
||||
import org.alfresco.solr.tracker.AbstractShardInformationPublisher;
|
||||
import org.alfresco.solr.tracker.ActivatableTracker;
|
||||
import org.alfresco.solr.tracker.ShardStatePublisher;
|
||||
import org.alfresco.solr.tracker.DBIDRangeRouter;
|
||||
import org.alfresco.solr.tracker.DocRouter;
|
||||
import org.alfresco.solr.tracker.IndexHealthReport;
|
||||
import org.alfresco.solr.tracker.MetadataTracker;
|
||||
import org.alfresco.solr.tracker.NodeStatePublisher;
|
||||
import org.alfresco.solr.tracker.SolrTrackerScheduler;
|
||||
import org.alfresco.solr.tracker.Tracker;
|
||||
import org.alfresco.solr.tracker.TrackerRegistry;
|
||||
@@ -81,6 +81,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.LongToIntFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -103,6 +104,7 @@ import static org.alfresco.solr.HandlerReportHelper.buildAclTxReport;
|
||||
import static org.alfresco.solr.HandlerReportHelper.buildNodeReport;
|
||||
import static org.alfresco.solr.HandlerReportHelper.buildTrackerReport;
|
||||
import static org.alfresco.solr.HandlerReportHelper.buildTxReport;
|
||||
import static org.alfresco.solr.utils.Utils.isNotNullAndNotEmpty;
|
||||
import static org.alfresco.solr.utils.Utils.isNullOrEmpty;
|
||||
import static org.alfresco.solr.utils.Utils.notNullOrEmpty;
|
||||
|
||||
@@ -138,10 +140,10 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
|
||||
private static final String REPORT = "report";
|
||||
private static final String SUMMARY = "Summary";
|
||||
private static final String ARG_ACLTXID = "acltxid";
|
||||
static final String ARG_ACLTXID = "acltxid";
|
||||
static final String ARG_TXID = "txid";
|
||||
private static final String ARG_ACLID = "aclid";
|
||||
private static final String ARG_NODEID = "nodeid";
|
||||
static final String ARG_ACLID = "aclid";
|
||||
static final String ARG_NODEID = "nodeid";
|
||||
private static final String ARG_QUERY = "query";
|
||||
private static final String DATA_DIR_ROOT = "data.dir.root";
|
||||
public static final String ALFRESCO_DEFAULTS = "create.alfresco.defaults";
|
||||
@@ -167,7 +169,8 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
private static final String ACTION_STATUS_ERROR = "error";
|
||||
static final String ACTION_STATUS_SCHEDULED = "scheduled";
|
||||
static final String ACTION_STATUS_NOT_SCHEDULED = "notScheduled";
|
||||
|
||||
static final String ADDITIONAL_INFO = "additionalInfo";
|
||||
static final String WARNING = "WARNING";
|
||||
static final String DRY_RUN_PARAMETER_NAME = "dryRun";
|
||||
static final String FROM_TX_COMMIT_TIME_PARAMETER_NAME = "fromTxCommitTime";
|
||||
static final String TO_TX_COMMIT_TIME_PARAMETER_NAME = "toTxCommitTime";
|
||||
@@ -197,7 +200,7 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
TrackerRegistry trackerRegistry;
|
||||
ConcurrentHashMap<String, InformationServer> informationServers;
|
||||
|
||||
private final static List<String> CORE_PARAMETER_NAMES = asList(CoreAdminParams.CORE, "coreName", "index");
|
||||
final static List<String> CORE_PARAMETER_NAMES = asList(CoreAdminParams.CORE, "coreName", "index");
|
||||
|
||||
public AlfrescoCoreAdminHandler()
|
||||
{
|
||||
@@ -495,6 +498,14 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
ofNullable(params.get("resource"))
|
||||
.orElse("log4j.properties")));
|
||||
break;
|
||||
case "ENABLE-INDEXING":
|
||||
case "ENABLEINDEXING":
|
||||
rsp.add(ACTION_LABEL, actionEnableIndexing(params));
|
||||
break;
|
||||
case "DISABLE-INDEXING":
|
||||
case "DISABLEINDEXING":
|
||||
rsp.add(ACTION_LABEL, actionDisableIndexing(params));
|
||||
break;
|
||||
default:
|
||||
super.handleCustomAction(req, rsp);
|
||||
break;
|
||||
@@ -1388,8 +1399,6 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
* - toCalTx, optional: to ACL transaction Id to filter report results
|
||||
*
|
||||
* - report.core: multiple Objects with the details of the report ("core" is the name of the Core)
|
||||
*
|
||||
* @throws JSONException
|
||||
*/
|
||||
private NamedList<Object> actionREPORT(SolrParams params) throws JSONException
|
||||
{
|
||||
@@ -1444,16 +1453,30 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
* @return Response including the action result:
|
||||
* - status: scheduled, as it will be executed by Trackers on the next maintenance operation
|
||||
*/
|
||||
private NamedList<Object> actionPURGE(SolrParams params)
|
||||
NamedList<Object> actionPURGE(SolrParams params)
|
||||
{
|
||||
final NamedList<Object> response = new SimpleOrderedMap<>();
|
||||
Consumer<String> purgeOnSpecificCore = coreName -> {
|
||||
final MetadataTracker metadataTracker = trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class);
|
||||
final AclTracker aclTracker = trackerRegistry.getTrackerForCore(coreName, AclTracker.class);
|
||||
|
||||
apply(params, ARG_TXID, metadataTracker::addTransactionToPurge);
|
||||
apply(params, ARG_ACLTXID, aclTracker::addAclChangeSetToPurge);
|
||||
apply(params, ARG_NODEID, metadataTracker::addNodeToPurge);
|
||||
apply(params, ARG_ACLID, aclTracker::addAclToPurge);
|
||||
final NamedList<Object> coreResponse = new SimpleOrderedMap<>();
|
||||
|
||||
if (metadataTracker.isEnabled() & aclTracker.isEnabled())
|
||||
{
|
||||
apply(params, ARG_TXID, metadataTracker::addTransactionToPurge);
|
||||
apply(params, ARG_ACLTXID, aclTracker::addAclChangeSetToPurge);
|
||||
apply(params, ARG_NODEID, metadataTracker::addNodeToPurge);
|
||||
apply(params, ARG_ACLID, aclTracker::addAclToPurge);
|
||||
coreResponse.add(ACTION_STATUS_LABEL, ACTION_STATUS_SCHEDULED);
|
||||
}
|
||||
else
|
||||
{
|
||||
coreResponse.add(ACTION_STATUS_LABEL, ACTION_STATUS_NOT_SCHEDULED);
|
||||
coreResponse.add(ADDITIONAL_INFO, "Trackers have been disabled: the purge request cannot be executed; please enable indexing and then resubmit this command.");
|
||||
}
|
||||
|
||||
response.add(coreName, coreResponse);
|
||||
};
|
||||
|
||||
String requestedCoreName = coreName(params);
|
||||
@@ -1463,8 +1486,11 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
.filter(this::isMasterOrStandalone)
|
||||
.forEach(purgeOnSpecificCore);
|
||||
|
||||
NamedList<Object> response = new SimpleOrderedMap<>();
|
||||
response.add(ACTION_STATUS_LABEL, ACTION_STATUS_SCHEDULED);
|
||||
if (response.size() == 0)
|
||||
{
|
||||
addAlertMessage(response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -1484,18 +1510,31 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
* @return Response including the action result:
|
||||
* - action.status: scheduled, as it will be executed by Trackers on the next maintenance operation
|
||||
*/
|
||||
private NamedList<Object> actionREINDEX(SolrParams params)
|
||||
NamedList<Object> actionREINDEX(SolrParams params)
|
||||
{
|
||||
final NamedList<Object> response = new SimpleOrderedMap<>();
|
||||
Consumer<String> reindexOnSpecificCore = coreName -> {
|
||||
final MetadataTracker metadataTracker = trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class);
|
||||
final AclTracker aclTracker = trackerRegistry.getTrackerForCore(coreName, AclTracker.class);
|
||||
final NamedList<Object> coreResponse = new SimpleOrderedMap<>();
|
||||
|
||||
apply(params, ARG_TXID, metadataTracker::addTransactionToReindex);
|
||||
apply(params, ARG_ACLTXID, aclTracker::addAclChangeSetToReindex);
|
||||
apply(params, ARG_NODEID, metadataTracker::addNodeToReindex);
|
||||
apply(params, ARG_ACLID, aclTracker::addAclToReindex);
|
||||
if (metadataTracker.isEnabled() & aclTracker.isEnabled())
|
||||
{
|
||||
apply(params, ARG_TXID, metadataTracker::addTransactionToReindex);
|
||||
apply(params, ARG_ACLTXID, aclTracker::addAclChangeSetToReindex);
|
||||
apply(params, ARG_NODEID, metadataTracker::addNodeToReindex);
|
||||
apply(params, ARG_ACLID, aclTracker::addAclToReindex);
|
||||
|
||||
ofNullable(params.get(ARG_QUERY)).ifPresent(metadataTracker::addQueryToReindex);
|
||||
coreResponse.add(ACTION_STATUS_LABEL, ACTION_STATUS_SCHEDULED);
|
||||
ofNullable(params.get(ARG_QUERY)).ifPresent(metadataTracker::addQueryToReindex);
|
||||
}
|
||||
else
|
||||
{
|
||||
coreResponse.add(ACTION_STATUS_LABEL, ACTION_STATUS_NOT_SCHEDULED);
|
||||
coreResponse.add(ADDITIONAL_INFO, "Trackers have been disabled: the REINDEX request cannot be executed; please enable indexing and then resubmit this command.");
|
||||
}
|
||||
|
||||
response.add(coreName, coreResponse);
|
||||
};
|
||||
|
||||
String requestedCoreName = coreName(params);
|
||||
@@ -1505,8 +1544,11 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
.filter(this::isMasterOrStandalone)
|
||||
.forEach(reindexOnSpecificCore);
|
||||
|
||||
NamedList<Object> response = new SimpleOrderedMap<>();
|
||||
response.add(ACTION_STATUS_LABEL, ACTION_STATUS_SCHEDULED);
|
||||
if (response.size() == 0)
|
||||
{
|
||||
addAlertMessage(response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -1520,35 +1562,41 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
* - action.status: scheduled, as it will be executed by Trackers on the next maintenance operation
|
||||
* - core: list of Document Ids with error that are going to reindexed
|
||||
*/
|
||||
private NamedList<Object> actionRETRY(SolrParams params)
|
||||
NamedList<Object> actionRETRY(SolrParams params)
|
||||
{
|
||||
NamedList<Object> response = new SimpleOrderedMap<>();
|
||||
|
||||
final Consumer<String> retryOnSpecificCore = coreName -> {
|
||||
MetadataTracker tracker = trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class);
|
||||
InformationServer srv = informationServers.get(coreName);
|
||||
final NamedList<Object> coreResponse = new SimpleOrderedMap<>();
|
||||
|
||||
try
|
||||
if (tracker.isEnabled())
|
||||
{
|
||||
for (Long nodeid : srv.getErrorDocIds())
|
||||
try
|
||||
{
|
||||
tracker.addNodeToReindex(nodeid);
|
||||
for (Long nodeid : srv.getErrorDocIds())
|
||||
{
|
||||
tracker.addNodeToReindex(nodeid);
|
||||
}
|
||||
coreResponse.add("Error Nodes", srv.getErrorDocIds());
|
||||
coreResponse.add(ACTION_STATUS_LABEL, ACTION_STATUS_SCHEDULED);
|
||||
} catch (Exception exception)
|
||||
{
|
||||
LOGGER.error("I/O Exception while adding Node to reindex.", exception);
|
||||
coreResponse.add(ACTION_STATUS_LABEL, ACTION_STATUS_ERROR);
|
||||
coreResponse.add(ACTION_ERROR_MESSAGE_LABEL, exception.getMessage());
|
||||
coreResponse.add(ACTION_STATUS_LABEL, ACTION_STATUS_NOT_SCHEDULED);
|
||||
}
|
||||
response.add(coreName, srv.getErrorDocIds());
|
||||
}
|
||||
catch (Exception exception)
|
||||
else
|
||||
{
|
||||
LOGGER.error("I/O Exception while adding Node to reindex.", exception);
|
||||
response.add(ACTION_STATUS_LABEL, ACTION_STATUS_ERROR);
|
||||
response.add(ACTION_ERROR_MESSAGE_LABEL, exception.getMessage());
|
||||
|
||||
coreResponse.add(ACTION_STATUS_LABEL, ACTION_STATUS_NOT_SCHEDULED);
|
||||
coreResponse.add(ADDITIONAL_INFO, "Trackers have been disabled: the RETRY request cannot be executed; please enable indexing and then resubmit this command.");
|
||||
}
|
||||
};
|
||||
|
||||
if (Objects.equals(response.get(ACTION_STATUS_LABEL), ACTION_STATUS_ERROR))
|
||||
{
|
||||
return response;
|
||||
}
|
||||
response.add(coreName, coreResponse);
|
||||
};
|
||||
|
||||
String requestedCoreName = coreName(params);
|
||||
|
||||
@@ -1557,7 +1605,11 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
.filter(this::isMasterOrStandalone)
|
||||
.forEach(retryOnSpecificCore);
|
||||
|
||||
response.add(ACTION_STATUS_LABEL, ACTION_STATUS_SCHEDULED);
|
||||
if (response.size() == 0)
|
||||
{
|
||||
addAlertMessage(response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -1576,16 +1628,29 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
* @return Response including the action result:
|
||||
* - action.status: scheduled, as it will be executed by Trackers on the next maintenance operation
|
||||
*/
|
||||
private NamedList<Object> actionINDEX(SolrParams params)
|
||||
NamedList<Object> actionINDEX(SolrParams params)
|
||||
{
|
||||
final NamedList<Object> response = new SimpleOrderedMap<>();
|
||||
Consumer<String> indexOnSpecificCore = coreName -> {
|
||||
final MetadataTracker metadataTracker = trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class);
|
||||
final AclTracker aclTracker = trackerRegistry.getTrackerForCore(coreName, AclTracker.class);
|
||||
final NamedList<Object> coreResponse = new SimpleOrderedMap<>();
|
||||
|
||||
apply(params, ARG_TXID, metadataTracker::addTransactionToIndex);
|
||||
apply(params, ARG_ACLTXID, aclTracker::addAclChangeSetToIndex);
|
||||
apply(params, ARG_NODEID, metadataTracker::addNodeToIndex);
|
||||
apply(params, ARG_ACLID, aclTracker::addAclToIndex);
|
||||
if (metadataTracker.isEnabled() & aclTracker.isEnabled())
|
||||
{
|
||||
apply(params, ARG_TXID, metadataTracker::addTransactionToIndex);
|
||||
apply(params, ARG_ACLTXID, aclTracker::addAclChangeSetToIndex);
|
||||
apply(params, ARG_NODEID, metadataTracker::addNodeToIndex);
|
||||
apply(params, ARG_ACLID, aclTracker::addAclToIndex);
|
||||
coreResponse.add(ACTION_STATUS_LABEL, ACTION_STATUS_SCHEDULED);
|
||||
}
|
||||
else
|
||||
{
|
||||
coreResponse.add(ACTION_STATUS_LABEL, ACTION_STATUS_NOT_SCHEDULED);
|
||||
coreResponse.add(ADDITIONAL_INFO, "Trackers have been disabled: the INDEX request cannot be executed; please enable indexing and then resubmit this command.");
|
||||
}
|
||||
|
||||
response.add(coreName, coreResponse);
|
||||
};
|
||||
|
||||
String requestedCoreName = coreName(params);
|
||||
@@ -1595,11 +1660,24 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
.filter(this::isMasterOrStandalone)
|
||||
.forEach(indexOnSpecificCore);
|
||||
|
||||
NamedList<Object> response = new SimpleOrderedMap<>();
|
||||
response.add(ACTION_STATUS_LABEL, ACTION_STATUS_SCHEDULED);
|
||||
if (response.size() == 0)
|
||||
{
|
||||
addAlertMessage(response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
NamedList<Object> actionDisableIndexing(SolrParams params) throws JSONException
|
||||
{
|
||||
return executeTrackerSubsystemLifecycleAction(params, this::disableIndexingOnSpecificCore);
|
||||
}
|
||||
|
||||
NamedList<Object> actionEnableIndexing(SolrParams params) throws JSONException
|
||||
{
|
||||
return executeTrackerSubsystemLifecycleAction(params, this::enableIndexingOnSpecificCore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find transactions and acls missing or duplicated in the cores and
|
||||
* add them to be reindexed on the next maintenance operation
|
||||
@@ -1644,24 +1722,30 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
boolean dryRun = params.getBool(DRY_RUN_PARAMETER_NAME, true);
|
||||
int maxTransactionsToSchedule = getMaxTransactionToSchedule(params);
|
||||
|
||||
MetadataTracker metadataTracker = trackerRegistry.getTrackerForCore(requestedCoreName, MetadataTracker.class);
|
||||
AclTracker aclTracker = trackerRegistry.getTrackerForCore(requestedCoreName, AclTracker.class);
|
||||
final boolean actualDryRun = dryRun | (metadataTracker == null || metadataTracker.isDisabled()) || (aclTracker == null || aclTracker.isDisabled());
|
||||
|
||||
LOGGER.debug("FIX Admin request on core {}, parameters: " +
|
||||
FROM_TX_COMMIT_TIME_PARAMETER_NAME + " = {}, " +
|
||||
TO_TX_COMMIT_TIME_PARAMETER_NAME + " = {}, " +
|
||||
DRY_RUN_PARAMETER_NAME + " = {}, " +
|
||||
MAX_TRANSACTIONS_TO_SCHEDULE_PARAMETER_NAME + " = {}",
|
||||
"actualDryRun = {} " +
|
||||
MAX_TRANSACTIONS_TO_SCHEDULE_PARAMETER_NAME + " = {}",
|
||||
requestedCoreName,
|
||||
ofNullable(fromTxCommitTime).map(Object::toString).orElse("N.A."),
|
||||
ofNullable(toTxCommitTime).map(Object::toString).orElse("N.A."),
|
||||
dryRun,
|
||||
actualDryRun,
|
||||
maxTransactionsToSchedule);
|
||||
|
||||
coreNames().stream()
|
||||
.filter(coreName -> requestedCoreName == null || coreName.equals(requestedCoreName))
|
||||
.filter(coreName -> coreName.equals(requestedCoreName))
|
||||
.filter(this::isMasterOrStandalone)
|
||||
.forEach(coreName ->
|
||||
wrapper.response.add(
|
||||
coreName,
|
||||
fixOnSpecificCore(coreName, fromTxCommitTime, toTxCommitTime, dryRun, maxTransactionsToSchedule)));
|
||||
fixOnSpecificCore(coreName, fromTxCommitTime, toTxCommitTime, actualDryRun, maxTransactionsToSchedule)));
|
||||
|
||||
if (wrapper.response.size() > 0)
|
||||
{
|
||||
@@ -1671,7 +1755,14 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
ofNullable(toTxCommitTime).ifPresent(value -> wrapper.response.add(TO_TX_COMMIT_TIME_PARAMETER_NAME, value));
|
||||
|
||||
wrapper.response.add(MAX_TRANSACTIONS_TO_SCHEDULE_PARAMETER_NAME, maxTransactionsToSchedule);
|
||||
wrapper.response.add(ACTION_STATUS_LABEL, dryRun ? ACTION_STATUS_NOT_SCHEDULED : ACTION_STATUS_SCHEDULED);
|
||||
wrapper.response.add(ACTION_STATUS_LABEL, actualDryRun ? ACTION_STATUS_NOT_SCHEDULED : ACTION_STATUS_SCHEDULED);
|
||||
|
||||
// the user wanted a real execution (dryRun = false) but the trackers are disabled.
|
||||
// that adds a message in the response just to inform the user we didn't schedule anything (i.e. we forced a dryRun)
|
||||
if (!dryRun && actualDryRun)
|
||||
{
|
||||
wrapper.response.add(ADDITIONAL_INFO, "Trackers are disabled: a (dryRun = true) has been forced. As consequence of that nothing has been scheduled.");
|
||||
}
|
||||
}
|
||||
|
||||
return wrapper.response;
|
||||
@@ -1701,6 +1792,8 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
try
|
||||
{
|
||||
MetadataTracker metadataTracker = trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class);
|
||||
AclTracker aclTracker = trackerRegistry.getTrackerForCore(coreName, AclTracker.class);
|
||||
|
||||
final IndexHealthReport metadataTrackerIndexHealthReport =
|
||||
metadataTracker.checkIndex(null, fromTxCommitTime, toTxCommitTime);
|
||||
|
||||
@@ -1715,7 +1808,6 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
dryRun,
|
||||
maxTransactionsToSchedule);
|
||||
|
||||
AclTracker aclTracker = trackerRegistry.getTrackerForCore(coreName, AclTracker.class);
|
||||
final IndexHealthReport aclTrackerIndexHealthReport =
|
||||
aclTracker.checkIndex(null, fromTxCommitTime, toTxCommitTime);
|
||||
|
||||
@@ -2009,11 +2101,9 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
* @param coreName the owning core name.
|
||||
* @return the component which is in charge to publish the core state.
|
||||
*/
|
||||
AbstractShardInformationPublisher coreStatePublisher(String coreName)
|
||||
ShardStatePublisher coreStatePublisher(String coreName)
|
||||
{
|
||||
return ofNullable(trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class))
|
||||
.map(AbstractShardInformationPublisher.class::cast)
|
||||
.orElse(trackerRegistry.getTrackerForCore(coreName, NodeStatePublisher.class));
|
||||
return trackerRegistry.getTrackerForCore(coreName, ShardStatePublisher.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2036,7 +2126,7 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
private void addAlertMessage(NamedList<Object> report)
|
||||
{
|
||||
report.add(
|
||||
"WARNING",
|
||||
WARNING,
|
||||
"The requested endpoint is not available on the slave. " +
|
||||
"Please re-submit the same request to the corresponding Master");
|
||||
}
|
||||
@@ -2084,4 +2174,62 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
|
||||
.map(Integer::parseInt)
|
||||
.orElse(Integer.MAX_VALUE)); // Last fallback if we don't have a request param and a value in configuration
|
||||
}
|
||||
|
||||
NamedList<Object> disableIndexingOnSpecificCore(String coreName) {
|
||||
final NamedList<Object> coreResponse = new SimpleOrderedMap<>();
|
||||
trackerRegistry.getTrackersForCore(coreName)
|
||||
.stream()
|
||||
.filter(tracker -> tracker instanceof ActivatableTracker)
|
||||
.map(ActivatableTracker.class::cast)
|
||||
.peek(ActivatableTracker::disable)
|
||||
.forEach(tracker -> coreResponse.add(tracker.getType().toString(), tracker.isEnabled()));
|
||||
return coreResponse;
|
||||
}
|
||||
|
||||
NamedList<Object> enableIndexingOnSpecificCore(String coreName) {
|
||||
final NamedList<Object> coreResponse = new SimpleOrderedMap<>();
|
||||
trackerRegistry.getTrackersForCore(coreName)
|
||||
.stream()
|
||||
.filter(tracker -> tracker instanceof ActivatableTracker)
|
||||
.map(ActivatableTracker.class::cast)
|
||||
.peek(ActivatableTracker::enable)
|
||||
.forEach(tracker -> coreResponse.add(tracker.getType().toString(), tracker.isEnabled()));
|
||||
return coreResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method used for executing the enable/disable indexing/tracking action.
|
||||
*
|
||||
* @param params the input request parameters. The only mandatory parameter is the core name
|
||||
* @param action this can be the "enable" or the "disable" action: it is an "impure" function which takes a core name
|
||||
* executes the enable/disable logic as part of its side-effect, and returns the action response.
|
||||
* @return the action response indicating the result of the enable/disable command on a specific core.
|
||||
* @see #CORE_PARAMETER_NAMES
|
||||
*/
|
||||
private NamedList<Object> executeTrackerSubsystemLifecycleAction(SolrParams params, Function<String, NamedList<Object>> action) throws JSONException
|
||||
{
|
||||
String requestedCoreName = coreName(params);
|
||||
final NamedList<Object> response = new SimpleOrderedMap<>();
|
||||
|
||||
if (isNotNullAndNotEmpty(requestedCoreName))
|
||||
{
|
||||
if (!coreNames().contains(requestedCoreName))
|
||||
{
|
||||
response.add(ACTION_ERROR_MESSAGE_LABEL, UNKNOWN_CORE_MESSAGE + requestedCoreName);
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!isMasterOrStandalone(requestedCoreName)) {
|
||||
response.add(ACTION_ERROR_MESSAGE_LABEL, UNPROCESSABLE_REQUEST_ON_SLAVE_NODES);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
coreNames().stream()
|
||||
.filter(coreName -> requestedCoreName == null || coreName.equals(requestedCoreName))
|
||||
.filter(this::isMasterOrStandalone)
|
||||
.forEach(coreName -> response.add(coreName, action.apply(coreName)));
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
+18
-12
@@ -125,7 +125,7 @@ class HandlerReportHelper
|
||||
return nr;
|
||||
}
|
||||
|
||||
static NamedList<Object> buildNodeReport(AbstractShardInformationPublisher publisher, Long dbid) throws JSONException
|
||||
static NamedList<Object> buildNodeReport(ShardStatePublisher publisher, Long dbid) throws JSONException
|
||||
{
|
||||
NodeReport nodeReport = publisher.checkNode(dbid);
|
||||
|
||||
@@ -159,6 +159,7 @@ class HandlerReportHelper
|
||||
AclTracker aclTracker = trackerRegistry.getTrackerForCore(coreName, AclTracker.class);
|
||||
IndexHealthReport aclReport = aclTracker.checkIndex(toAclTx, fromTime, toTime);
|
||||
NamedList<Object> ihr = new SimpleOrderedMap<>();
|
||||
ihr.add("ACL Tracker", (aclTracker.isEnabled() ? "enabled" : "disabled"));
|
||||
ihr.add("DB acl transaction count", aclReport.getDbAclTransactionCount());
|
||||
ihr.add("Count of duplicated acl transactions in the index", aclReport.getDuplicatedAclTxInIndex()
|
||||
.cardinality());
|
||||
@@ -188,6 +189,7 @@ class HandlerReportHelper
|
||||
// Metadata
|
||||
MetadataTracker metadataTracker = trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class);
|
||||
IndexHealthReport metaReport = metadataTracker.checkIndex(toTx, fromTime, toTime);
|
||||
ihr.add("Metadata Tracker", (metadataTracker.isEnabled() ? "enabled" : "disabled"));
|
||||
ihr.add("DB transaction count", metaReport.getDbTransactionCount());
|
||||
ihr.add("Count of duplicated transactions in the index", metaReport.getDuplicatedTxInIndex()
|
||||
.cardinality());
|
||||
@@ -248,7 +250,7 @@ class HandlerReportHelper
|
||||
NamedList<Object> coreSummary = new SimpleOrderedMap<>();
|
||||
coreSummary.addAll((SimpleOrderedMap<Object>) srv.getCoreStats());
|
||||
|
||||
NodeStatePublisher statePublisher = trackerRegistry.getTrackerForCore(cname, NodeStatePublisher.class);
|
||||
ShardStatePublisher statePublisher = trackerRegistry.getTrackerForCore(cname, ShardStatePublisher.class);
|
||||
TrackerState trackerState = statePublisher.getTrackerState();
|
||||
long lastIndexTxCommitTime = trackerState.getLastIndexedTxCommitTime();
|
||||
|
||||
@@ -429,17 +431,12 @@ class HandlerReportHelper
|
||||
long remainingContentTimeMillis = 0;
|
||||
srv.addContentOutdatedAndUpdatedCounts(ftsSummary);
|
||||
long cleanCount =
|
||||
ofNullable(ftsSummary.get("Node count with FTSStatus Clean"))
|
||||
ofNullable(ftsSummary.get("Node count whose content is in sync"))
|
||||
.map(Number.class::cast)
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
long dirtyCount =
|
||||
ofNullable(ftsSummary.get("Node count with FTSStatus Dirty"))
|
||||
.map(Number.class::cast)
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
long newCount =
|
||||
ofNullable(ftsSummary.get("Node count with FTSStatus New"))
|
||||
ofNullable(ftsSummary.get("Node count whose content needs to be updated"))
|
||||
.map(Number.class::cast)
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
@@ -450,12 +447,14 @@ class HandlerReportHelper
|
||||
.map(Number::longValue)
|
||||
.orElse(0L);
|
||||
|
||||
long contentYetToSee = nodesInIndex > 0 ? nodesToDo * (cleanCount + dirtyCount + newCount)/nodesInIndex : 0;
|
||||
if (dirtyCount + newCount + contentYetToSee > 0)
|
||||
|
||||
|
||||
long contentYetToSee = nodesInIndex > 0 ? nodesToDo * (cleanCount + dirtyCount)/nodesInIndex : 0;
|
||||
if (dirtyCount + contentYetToSee > 0)
|
||||
{
|
||||
// We now use the elapsed time as seen by the single thread farming out alc indexing
|
||||
double meanContentElapsedIndexTime = srv.getTrackerStats().getMeanContentElapsedIndexTime();
|
||||
remainingContentTimeMillis = (long) ((dirtyCount + newCount + contentYetToSee) * meanContentElapsedIndexTime);
|
||||
remainingContentTimeMillis = (long) ((dirtyCount + contentYetToSee) * meanContentElapsedIndexTime);
|
||||
}
|
||||
now = new Date();
|
||||
end = new Date(now.getTime() + remainingContentTimeMillis);
|
||||
@@ -485,6 +484,8 @@ class HandlerReportHelper
|
||||
}
|
||||
|
||||
ContentTracker contentTrkr = trackerRegistry.getTrackerForCore(cname, ContentTracker.class);
|
||||
CascadeTracker cascadeTracker = trackerRegistry.getTrackerForCore(cname, CascadeTracker.class);
|
||||
|
||||
TrackerState contentTrkrState = contentTrkr.getTrackerState();
|
||||
// Leave ModelTracker out of this check, because it is common
|
||||
boolean aTrackerIsRunning = aclTrkrState.isRunning() || metadataTrkrState.isRunning()
|
||||
@@ -498,6 +499,11 @@ class HandlerReportHelper
|
||||
coreSummary.add("MetadataTracker Active", metadataTrkrState.isRunning());
|
||||
coreSummary.add("AclTracker Active", aclTrkrState.isRunning());
|
||||
|
||||
coreSummary.add("ContentTracker Enabled", contentTrkr.isEnabled());
|
||||
coreSummary.add("MetadataTracker Enabled", metaTrkr.isEnabled());
|
||||
coreSummary.add("AclTracker Enabled", aclTrkr.isEnabled());
|
||||
coreSummary.add("CascadeTracker Enabled", cascadeTracker.isEnabled());
|
||||
|
||||
// TX
|
||||
|
||||
coreSummary.add("Last Index TX Commit Time", lastIndexTxCommitTime);
|
||||
|
||||
+23
-5
@@ -30,6 +30,7 @@ import static java.util.Arrays.asList;
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
import static org.alfresco.solr.SolrInformationServer.CASCADE_TRACKER_ENABLED;
|
||||
import static org.alfresco.solr.tracker.ActivatableTracker.INDEXING_ENABLED_PERSISTENT_FLAG_ACROSS_RELOADS;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -46,12 +47,13 @@ import org.alfresco.solr.SolrKeyResourceLoader;
|
||||
import org.alfresco.solr.client.SOLRAPIClient;
|
||||
import org.alfresco.solr.client.SOLRAPIClientFactory;
|
||||
import org.alfresco.solr.tracker.AclTracker;
|
||||
import org.alfresco.solr.tracker.ActivatableTracker;
|
||||
import org.alfresco.solr.tracker.CascadeTracker;
|
||||
import org.alfresco.solr.tracker.CommitTracker;
|
||||
import org.alfresco.solr.tracker.ContentTracker;
|
||||
import org.alfresco.solr.tracker.MetadataTracker;
|
||||
import org.alfresco.solr.tracker.ModelTracker;
|
||||
import org.alfresco.solr.tracker.NodeStatePublisher;
|
||||
import org.alfresco.solr.tracker.ShardStatePublisher;
|
||||
import org.alfresco.solr.tracker.SolrTrackerScheduler;
|
||||
import org.alfresco.solr.tracker.Tracker;
|
||||
import org.alfresco.solr.tracker.TrackerRegistry;
|
||||
@@ -190,7 +192,7 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
|
||||
{
|
||||
LOGGER.info("SearchServices Core Trackers have been explicitly disabled on core \"{}\" through \"enable.alfresco.tracking\" configuration property.", core.getName());
|
||||
|
||||
NodeStatePublisher statePublisher = new NodeStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
|
||||
ShardStatePublisher statePublisher = new ShardStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
|
||||
trackerRegistry.register(core.getName(), statePublisher);
|
||||
scheduler.schedule(statePublisher, core.getName(), coreProperties);
|
||||
trackers.add(statePublisher);
|
||||
@@ -205,7 +207,7 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
|
||||
{
|
||||
LOGGER.info("SearchServices Core Trackers have been disabled on core \"{}\" because it is a slave core.", core.getName());
|
||||
|
||||
NodeStatePublisher statePublisher = new NodeStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
|
||||
ShardStatePublisher statePublisher = new ShardStatePublisher(false, coreProperties, repositoryClient, core.getName(), informationServer);
|
||||
trackerRegistry.register(core.getName(), statePublisher);
|
||||
scheduler.schedule(statePublisher, core.getName(), coreProperties);
|
||||
trackers.add(statePublisher);
|
||||
@@ -264,9 +266,9 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
|
||||
trackerRegistry,
|
||||
scheduler);
|
||||
|
||||
NodeStatePublisher coreStateTracker =
|
||||
ShardStatePublisher coreStateTracker =
|
||||
registerAndSchedule(
|
||||
new NodeStatePublisher(true, props, repositoryClient, core.getName(), srv),
|
||||
new ShardStatePublisher(true, props, repositoryClient, core.getName(), srv),
|
||||
core,
|
||||
props,
|
||||
trackerRegistry,
|
||||
@@ -288,6 +290,22 @@ public class SolrCoreLoadListener extends AbstractSolrEventListener
|
||||
trackers.add(cascadeTracker);
|
||||
}
|
||||
|
||||
boolean indexingHasBeenEnabled =
|
||||
Boolean.parseBoolean(props.getProperty(INDEXING_ENABLED_PERSISTENT_FLAG_ACROSS_RELOADS, "true"));
|
||||
|
||||
if (indexingHasBeenEnabled)
|
||||
{
|
||||
trackers.stream().map(ActivatableTracker.class::cast).forEach(ActivatableTracker::enable);
|
||||
LOGGER.info("SearchServices Core trackers (i.e. indexing) have been enabled.");
|
||||
}
|
||||
else
|
||||
{
|
||||
trackers.stream().map(ActivatableTracker.class::cast).forEach(ActivatableTracker::disable);
|
||||
LOGGER.info("SearchServices Core trackers (i.e. indexing) have been disabled. That could happen if you " +
|
||||
"previously disabled the indexing on this core and then you reloaded it. If you want to enable indexing " +
|
||||
"please invoke the \"enable-indexing\" admin action.");
|
||||
}
|
||||
|
||||
//The CommitTracker will acquire these locks in order
|
||||
//The ContentTracker will likely have the longest runs so put it first to ensure the MetadataTracker is not paused while
|
||||
//waiting for the ContentTracker to release it's lock.
|
||||
|
||||
+119
-2
@@ -29,19 +29,29 @@ package org.alfresco.solr.tracker;
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
import static org.alfresco.repo.index.shard.ShardMethodEnum.DB_ID;
|
||||
import static org.alfresco.solr.tracker.DocRouterFactory.SHARD_KEY_KEY;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.alfresco.opencmis.dictionary.CMISStrictDictionaryService;
|
||||
import org.alfresco.repo.dictionary.NamespaceDAO;
|
||||
import org.alfresco.repo.index.shard.ShardMethodEnum;
|
||||
import org.alfresco.repo.search.impl.QueryParserUtils;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.solr.AlfrescoSolrDataModel;
|
||||
import org.alfresco.solr.IndexTrackingShutdownException;
|
||||
import org.alfresco.solr.InformationServer;
|
||||
import org.alfresco.solr.NodeReport;
|
||||
import org.alfresco.solr.TrackerState;
|
||||
import org.alfresco.solr.client.SOLRAPIClient;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -81,6 +91,24 @@ public abstract class AbstractTracker implements Tracker
|
||||
protected final Type type;
|
||||
protected final String trackerId;
|
||||
|
||||
DocRouter docRouter;
|
||||
|
||||
/**
|
||||
* The property to use for determining the shard.
|
||||
* Note that this property is not used by all trackers, it is actually managed by the {@link ShardStatePublisher} and
|
||||
* {@link MetadataTracker}. We put this property here because otherwise we should introduce another supertype layer
|
||||
* for those two trackers.
|
||||
*/
|
||||
protected Optional<QName> shardProperty = Optional.empty();
|
||||
|
||||
/**
|
||||
* The string representation of the shard key.
|
||||
* Note that this property is not used by all trackers, it is actually managed by the {@link ShardStatePublisher} and
|
||||
* {@link MetadataTracker}. We put this property here because otherwise we should introduce another supertype layer
|
||||
* for those two trackers.
|
||||
*/
|
||||
protected Optional<String> shardKey;
|
||||
|
||||
/**
|
||||
* Default constructor, strictly for testing.
|
||||
*/
|
||||
@@ -114,9 +142,14 @@ public abstract class AbstractTracker implements Tracker
|
||||
this.type = type;
|
||||
|
||||
this.trackerId = type + "@" + hashCode();
|
||||
|
||||
shardKey = ofNullable(p.getProperty(SHARD_KEY_KEY));
|
||||
|
||||
firstUpdateShardProperty();
|
||||
|
||||
docRouter = DocRouterFactory.getRouter(p, shardMethod);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Subclasses must implement behaviour that completes the following steps, in order:
|
||||
*
|
||||
@@ -352,4 +385,88 @@ public abstract class AbstractTracker implements Tracker
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shard property using the shard key.
|
||||
*/
|
||||
void updateShardProperty()
|
||||
{
|
||||
shardKey.ifPresent(shardKeyName -> {
|
||||
Optional<QName> updatedShardProperty = getShardProperty(shardKeyName);
|
||||
if (!shardProperty.equals(updatedShardProperty))
|
||||
{
|
||||
if (updatedShardProperty.isEmpty())
|
||||
{
|
||||
LOGGER.warn("The model defining {} property has been disabled", shardKeyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.info("New {} property found for {}", SHARD_KEY_KEY, shardKeyName);
|
||||
}
|
||||
}
|
||||
shardProperty = updatedShardProperty;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the field name, returns the name of the property definition.
|
||||
* If the property definition is not found, Empty optional is returned.
|
||||
*
|
||||
* @param field the field name.
|
||||
* @return the name of the associated property definition if present, Optional.Empty() otherwise
|
||||
*/
|
||||
static Optional<QName> getShardProperty(String field)
|
||||
{
|
||||
if (StringUtils.isBlank(field))
|
||||
{
|
||||
throw new IllegalArgumentException("Sharding property " + SHARD_KEY_KEY + " has not been set.");
|
||||
}
|
||||
|
||||
AlfrescoSolrDataModel dataModel = AlfrescoSolrDataModel.getInstance();
|
||||
NamespaceDAO namespaceDAO = dataModel.getNamespaceDAO();
|
||||
DictionaryService dictionaryService = dataModel.getDictionaryService(CMISStrictDictionaryService.DEFAULT);
|
||||
PropertyDefinition propertyDef = QueryParserUtils.matchPropertyDefinition("http://www.alfresco.org/model/content/1.0",
|
||||
namespaceDAO,
|
||||
dictionaryService,
|
||||
field);
|
||||
|
||||
return ofNullable(propertyDef).map(PropertyDefinition::getName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the {@link org.alfresco.solr.client.Node} associated with the given dbid.
|
||||
*
|
||||
* @param dbid the node identifier.
|
||||
* @return the {@link org.alfresco.solr.client.Node} associated with the given dbid.
|
||||
*/
|
||||
public NodeReport checkNode(Long dbid)
|
||||
{
|
||||
NodeReport nodeReport = new NodeReport();
|
||||
nodeReport.setDbid(dbid);
|
||||
|
||||
this.infoSrv.addCommonNodeReportInfo(nodeReport);
|
||||
|
||||
return nodeReport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link DocRouter} instance in use on this node.
|
||||
*
|
||||
* @return the {@link DocRouter} instance in use on this node.
|
||||
*/
|
||||
public DocRouter getDocRouter()
|
||||
{
|
||||
return this.docRouter;
|
||||
}
|
||||
|
||||
private void firstUpdateShardProperty()
|
||||
{
|
||||
shardKey.ifPresent( shardKeyName -> {
|
||||
updateShardProperty();
|
||||
if (shardProperty.isEmpty())
|
||||
{
|
||||
LOGGER.warn("Sharding property {} was set to {}, but no such property was found.", SHARD_KEY_KEY, shardKeyName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+14
-1
@@ -67,7 +67,7 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Matt Ward
|
||||
**/
|
||||
|
||||
public class AclTracker extends AbstractTracker
|
||||
public class AclTracker extends ActivatableTracker
|
||||
{
|
||||
protected final static Logger LOGGER = LoggerFactory.getLogger(AclTracker.class);
|
||||
|
||||
@@ -363,6 +363,19 @@ public class AclTracker extends AbstractTracker
|
||||
aclsToPurge.offer(aclToPurge);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearScheduledMaintenanceWork()
|
||||
{
|
||||
logAndClear(aclChangeSetsToIndex, "ACL ChangeSets to be indexed");
|
||||
logAndClear(aclsToIndex, "ACLs to be indexed");
|
||||
|
||||
logAndClear(aclChangeSetsToReindex, "ACL ChangeSets to be re-indexed");
|
||||
logAndClear(aclsToReindex, "ACLs to be re-indexed");
|
||||
|
||||
logAndClear(aclChangeSetsToPurge, "ACL ChangeSets to be purged");
|
||||
logAndClear(aclsToPurge, "ACLs to be purged");
|
||||
}
|
||||
|
||||
protected void trackRepository() throws IOException, AuthenticationException, JSONException
|
||||
{
|
||||
checkShutdown();
|
||||
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.solr.tracker;
|
||||
|
||||
import org.alfresco.solr.InformationServer;
|
||||
import org.alfresco.solr.client.SOLRAPIClient;
|
||||
import org.apache.solr.core.CoreDescriptorDecorator;
|
||||
import org.apache.solr.core.SolrCore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Supertype layer for trackers that can be enabled/disabled.
|
||||
*/
|
||||
public abstract class ActivatableTracker extends AbstractTracker
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ActivatableTracker.class);
|
||||
public final static String INDEXING_ENABLED_PERSISTENT_FLAG_ACROSS_RELOADS = "alfresco.trackers.indexingEnabled";
|
||||
|
||||
protected final AtomicBoolean isEnabled;
|
||||
|
||||
protected ActivatableTracker(Type type)
|
||||
{
|
||||
super(type);
|
||||
this.isEnabled = new AtomicBoolean(true);
|
||||
}
|
||||
|
||||
protected ActivatableTracker(Properties properties, SOLRAPIClient client, String coreName, InformationServer informationServer, Type type)
|
||||
{
|
||||
super(properties, client, coreName, informationServer, type);
|
||||
isEnabled = new AtomicBoolean(Boolean.parseBoolean(properties.getProperty(INDEXING_ENABLED_PERSISTENT_FLAG_ACROSS_RELOADS, "true")));
|
||||
|
||||
if (isEnabled.get())
|
||||
{
|
||||
LOGGER.info("[{} / {} / {}] {} Tracker set to enabled at startup.", coreName, trackerId, state, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.info("[{} / {} / {}] {} Tracker set to disabled at startup.", coreName, trackerId, state, type);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setPersistentIndexingStateAcrossReloadsTo(boolean enabled)
|
||||
{
|
||||
infoSrv.getAdminHandler().getCoreContainer().getCoreDescriptor(coreName).setProperty(INDEXING_ENABLED_PERSISTENT_FLAG_ACROSS_RELOADS, String.valueOf(enabled));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables this tracker instance.
|
||||
*/
|
||||
public final void disable()
|
||||
{
|
||||
clearScheduledMaintenanceWork();
|
||||
|
||||
if (isEnabled.compareAndSet(true, false))
|
||||
{
|
||||
setPersistentIndexingStateAcrossReloadsTo(false);
|
||||
if (state != null && state.isRunning())
|
||||
{
|
||||
LOGGER.info("[{} / {} / {}] {} Tracker has been disabled (the change will be effective at the next tracking cycle) and set in rollback mode because it is running.", coreName, trackerId, state, type);
|
||||
setRollback(true, null);
|
||||
}
|
||||
LOGGER.info("[{} / {} / {}] {} Tracker has been disabled. The change will be effective at the next tracking cycle.", coreName, trackerId, state, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.warn("[{} / {} / {}] {} Tracker cannot be disabled because it is already in that state.", coreName, trackerId, state, type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables this tracker instance.
|
||||
*/
|
||||
public final void enable()
|
||||
{
|
||||
if (isEnabled.compareAndSet(false, true))
|
||||
{
|
||||
setPersistentIndexingStateAcrossReloadsTo(true);
|
||||
LOGGER.info("[{} / {} / {}] {} Tracker has been enabled. The change will be effective at the next tracking cycle.", coreName, trackerId, state, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.warn("[{} / {} / {}] {} Tracker cannot be enabled because it is already in that state.", coreName, trackerId, state, type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void track()
|
||||
{
|
||||
if (isEnabled())
|
||||
{
|
||||
super.track();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.trace("[{} / {} / {}] {} Tracker is disabled. That is absolutely ok, that means you disabled the tracking on this core.", coreName, trackerId, state, type);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return isEnabled.get();
|
||||
}
|
||||
|
||||
public boolean isDisabled()
|
||||
{
|
||||
return !isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the scheduled maintenance work collected by this tracker.
|
||||
*/
|
||||
protected void clearScheduledMaintenanceWork()
|
||||
{
|
||||
// Default behaviour is: do nothing
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs out the content of the input collection.
|
||||
*
|
||||
* @param values the collection which (in case is not empty) contains the identifiers (e.g. txid, aclid) the system
|
||||
* is going to clear.
|
||||
* @param kind the kind of identifier (e.g. Transaction, Node ID, ACL ID) in the input collection.
|
||||
*/
|
||||
protected void logAndClear(Collection<Long> values, String kind)
|
||||
{
|
||||
if (values == null || values.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Long> tmp = new ArrayList<>(values);
|
||||
values.clear();
|
||||
|
||||
LOGGER.info("[CORE {}] Scheduled work ({}) that will be cleaned: {}", coreName, kind, new ArrayList<>(tmp));
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -52,14 +52,11 @@ import org.json.JSONException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static org.alfresco.solr.utils.Utils.notNullOrEmpty;
|
||||
|
||||
/*
|
||||
* This tracks Cascading Updates
|
||||
* @author Joel Bernstein
|
||||
*/
|
||||
public class CascadeTracker extends AbstractTracker implements Tracker
|
||||
public class CascadeTracker extends ActivatableTracker
|
||||
{
|
||||
|
||||
protected final static Logger LOGGER = LoggerFactory.getLogger(CascadeTracker.class);
|
||||
|
||||
+23
-20
@@ -112,12 +112,13 @@ public class CommitTracker extends AbstractTracker
|
||||
WRITE_LOCK_BY_CORE.put(coreName, new Semaphore(1, true));
|
||||
}
|
||||
|
||||
public boolean hasMaintenance() throws Exception
|
||||
public boolean hasMaintenance()
|
||||
{
|
||||
return (metadataTracker.hasMaintenance() || aclTracker.hasMaintenance());
|
||||
}
|
||||
|
||||
public int getRollbackCount() {
|
||||
public int getRollbackCount()
|
||||
{
|
||||
return rollbackCount.get();
|
||||
}
|
||||
|
||||
@@ -134,7 +135,6 @@ public class CommitTracker extends AbstractTracker
|
||||
boolean commitNeeded = false;
|
||||
boolean openSearcherNeeded = false;
|
||||
boolean hasMaintenance = hasMaintenance();
|
||||
//System.out.println("############# Commit Tracker doTrack()");
|
||||
|
||||
if((currentTime - lastCommit) > commitInterval || hasMaintenance)
|
||||
{
|
||||
@@ -151,8 +151,6 @@ public class CommitTracker extends AbstractTracker
|
||||
openSearcherNeeded = true;
|
||||
}
|
||||
|
||||
//System.out.println("############# Commit Tracker commit needed");
|
||||
|
||||
try
|
||||
{
|
||||
metadataTracker.getWriteLock().acquire();
|
||||
@@ -161,9 +159,8 @@ public class CommitTracker extends AbstractTracker
|
||||
aclTracker.getWriteLock().acquire();
|
||||
assert(aclTracker.getWriteLock().availablePermits() == 0);
|
||||
|
||||
//See if we need a rollback
|
||||
if(metadataTracker.getRollback() || aclTracker.getRollback()) {
|
||||
|
||||
if(metadataTracker.getRollback() || aclTracker.getRollback())
|
||||
{
|
||||
/*
|
||||
* The metadataTracker and aclTracker will return true if an unhandled exception has occurred during indexing.
|
||||
*
|
||||
@@ -174,30 +171,36 @@ public class CommitTracker extends AbstractTracker
|
||||
* the index, rather then the in-memory state. This keeps the trackers in-sync with index if their work is
|
||||
* rolled back.
|
||||
*/
|
||||
|
||||
doRollback();
|
||||
return;
|
||||
}
|
||||
|
||||
if(hasMaintenance) {
|
||||
// The disable-indexing command should happen while the commit tracker is here.
|
||||
// In that case the (disable-indexing) command clears the maintenance work as much as possible,
|
||||
// however, there's a chance that some work will be still executed: for that reason the check is repeated
|
||||
// later (see below) and in case a rollback is executed
|
||||
if (hasMaintenance)
|
||||
{
|
||||
maintenance();
|
||||
}
|
||||
|
||||
//Do the commit opening the searcher if needed. This will commit all the work done by indexing trackers.
|
||||
//This will return immediately and not wait for searchers to warm
|
||||
boolean searcherOpened = infoSrv.commit(openSearcherNeeded);
|
||||
|
||||
lastCommit = currentTime;
|
||||
if(searcherOpened) {
|
||||
lastSearcherOpened = currentTime;
|
||||
if (metadataTracker.isEnabled() && aclTracker.isEnabled())
|
||||
{
|
||||
boolean searcherOpened = infoSrv.commit(openSearcherNeeded);
|
||||
lastCommit = currentTime;
|
||||
if(searcherOpened)
|
||||
{
|
||||
lastSearcherOpened = currentTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
doRollback();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
//Release the lock on the metadata Tracker
|
||||
metadataTracker.getWriteLock().release();
|
||||
|
||||
//Release the lock on the aclTracker
|
||||
aclTracker.getWriteLock().release();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ import static org.alfresco.solr.utils.Utils.notNullOrEmpty;
|
||||
*
|
||||
* @author Ahmed Owian
|
||||
*/
|
||||
public class ContentTracker extends AbstractTracker implements Tracker
|
||||
public class ContentTracker extends ActivatableTracker
|
||||
{
|
||||
protected final static Logger LOGGER = LoggerFactory.getLogger(ContentTracker.class);
|
||||
|
||||
|
||||
+22
-7
@@ -68,7 +68,7 @@ import static org.alfresco.repo.index.shard.ShardMethodEnum.DB_ID_RANGE;
|
||||
* This tracks two things: transactions and metadata nodes
|
||||
* @author Ahmed Owian
|
||||
*/
|
||||
public class MetadataTracker extends AbstractShardInformationPublisher implements Tracker
|
||||
public class MetadataTracker extends ActivatableTracker
|
||||
{
|
||||
protected final static Logger LOGGER = LoggerFactory.getLogger(MetadataTracker.class);
|
||||
|
||||
@@ -101,8 +101,8 @@ public class MetadataTracker extends AbstractShardInformationPublisher implement
|
||||
private ForkJoinPool forkJoinPool;
|
||||
|
||||
// Share run and write locks across all MetadataTracker threads
|
||||
private static Map<String, Semaphore> RUN_LOCK_BY_CORE = new ConcurrentHashMap<>();
|
||||
private static Map<String, Semaphore> WRITE_LOCK_BY_CORE = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Semaphore> RUN_LOCK_BY_CORE = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Semaphore> WRITE_LOCK_BY_CORE = new ConcurrentHashMap<>();
|
||||
@Override
|
||||
public Semaphore getWriteLock()
|
||||
{
|
||||
@@ -119,7 +119,7 @@ public class MetadataTracker extends AbstractShardInformationPublisher implement
|
||||
* This service is used to find the next available transaction commit time from a given time,
|
||||
* so periods of time where no document updating is happening can be skipped while getting
|
||||
* pending transactions list.
|
||||
*
|
||||
*
|
||||
* {@link org.alfresco.solr.client.SOLRAPIClient#GET_NEXT_TX_COMMIT_TIME}
|
||||
*/
|
||||
private boolean nextTxCommitTimeServiceAvailable = false;
|
||||
@@ -127,8 +127,8 @@ public class MetadataTracker extends AbstractShardInformationPublisher implement
|
||||
/**
|
||||
* Check if txInteravlCommitTimeService is available in the repository.
|
||||
* This service returns the minimum and the maximum commit time for transactions in a node id range,
|
||||
* so method sharding DB_ID_RANGE can skip transactions not relevant for the DB ID range.
|
||||
*
|
||||
* so method sharding DB_ID_RANGE can skip transactions not relevant for the DB ID range.
|
||||
*
|
||||
* {@link org.alfresco.solr.client.SOLRAPIClient#GET_TX_INTERVAL_COMMIT_TIME}
|
||||
*/
|
||||
private boolean txIntervalCommitTimeServiceAvailable = false;
|
||||
@@ -159,7 +159,7 @@ public class MetadataTracker extends AbstractShardInformationPublisher implement
|
||||
public MetadataTracker( Properties p, SOLRAPIClient client, String coreName,
|
||||
InformationServer informationServer, boolean checkRepoServicesAvailability)
|
||||
{
|
||||
super(true, p, client, coreName, informationServer, Tracker.Type.METADATA);
|
||||
super(p, client, coreName, informationServer, Tracker.Type.METADATA);
|
||||
|
||||
transactionDocsBatchSize = Integer.parseInt(p.getProperty("alfresco.transactionDocsBatchSize",
|
||||
String.valueOf(DEFAULT_TRANSACTION_DOCS_BATCH_SIZE)));
|
||||
@@ -1319,6 +1319,19 @@ public class MetadataTracker extends AbstractShardInformationPublisher implement
|
||||
transactionsToIndex.offer(txId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearScheduledMaintenanceWork()
|
||||
{
|
||||
logAndClear(transactionsToIndex, "Transactions to be indexed");
|
||||
logAndClear(nodesToIndex, "Nodes to be indexed");
|
||||
|
||||
logAndClear(transactionsToReindex, "Transactions to be re-indexed");
|
||||
logAndClear(nodesToReindex, "Nodes to be re-indexed");
|
||||
|
||||
logAndClear(transactionsToPurge, "Transactions to be purged");
|
||||
logAndClear(nodesToPurge, "Nodes to be purged");
|
||||
}
|
||||
|
||||
public void addNodeToIndex(Long nodeId)
|
||||
{
|
||||
this.nodesToIndex.offer(nodeId);
|
||||
@@ -1333,4 +1346,6 @@ public class MetadataTracker extends AbstractShardInformationPublisher implement
|
||||
{
|
||||
this.queriesToReindex.offer(query);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ import org.slf4j.LoggerFactory;
|
||||
* deactivate ModelTracker
|
||||
* @enduml
|
||||
*/
|
||||
public class ModelTracker extends AbstractTracker implements Tracker
|
||||
public class ModelTracker extends AbstractTracker
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ModelTracker.class);
|
||||
|
||||
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
package org.alfresco.solr.tracker;
|
||||
|
||||
import static org.alfresco.solr.tracker.Tracker.Type.NODE_STATE_PUBLISHER;
|
||||
|
||||
import org.alfresco.httpclient.AuthenticationException;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.alfresco.solr.SolrInformationServer;
|
||||
import org.alfresco.solr.TrackerState;
|
||||
import org.alfresco.solr.client.SOLRAPIClient;
|
||||
import org.apache.commons.codec.EncoderException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
/**
|
||||
* Despite belonging to the Tracker ecosystem, this component is actually a publisher, which periodically informs
|
||||
* Alfresco about the state of the hosting slave core.
|
||||
* As the name suggests, this worker is scheduled only when the owning core acts as a slave.
|
||||
* It allows Solr's master/slave setup to be used with dynamic shard registration.
|
||||
*
|
||||
* In this scenario the slave is polling a "tracking" Solr node. The tracker below calls
|
||||
* the repo to register the state of the node without pulling any real transactions from the repo.
|
||||
*
|
||||
* This allows the repo to register the replica so that it will be included in queries. But the slave Solr node
|
||||
* will pull its data from a "tracking" Solr node using Solr's master/slave replication, rather then tracking the repository.
|
||||
*
|
||||
* @author Andrea Gazzarini
|
||||
* @since 1.5
|
||||
*/
|
||||
public class NodeStatePublisher extends AbstractShardInformationPublisher
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NodeStatePublisher.class);
|
||||
|
||||
|
||||
// Share run and write locks across all SlaveCoreStatePublisher threads
|
||||
private static final Map<String, Semaphore> RUN_LOCK_BY_CORE = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Semaphore> WRITE_LOCK_BY_CORE = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public Semaphore getWriteLock()
|
||||
{
|
||||
return WRITE_LOCK_BY_CORE.get(coreName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Semaphore getRunLock()
|
||||
{
|
||||
return RUN_LOCK_BY_CORE.get(coreName);
|
||||
}
|
||||
|
||||
public NodeStatePublisher(
|
||||
boolean isMaster,
|
||||
Properties coreProperties,
|
||||
SOLRAPIClient repositoryClient,
|
||||
String name,
|
||||
SolrInformationServer informationServer)
|
||||
{
|
||||
super(isMaster, coreProperties, repositoryClient, name, informationServer, NODE_STATE_PUBLISHER);
|
||||
RUN_LOCK_BY_CORE.put(coreName, new Semaphore(1, true));
|
||||
WRITE_LOCK_BY_CORE.put(coreName, new Semaphore(1, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTrack(String iterationId)
|
||||
{
|
||||
try
|
||||
{
|
||||
ShardState shardstate = getShardState();
|
||||
client.getTransactions(0L, null, 0L, null, 0, shardstate);
|
||||
}
|
||||
catch (EncoderException | IOException | AuthenticationException exception )
|
||||
{
|
||||
LOGGER.error("Unable to publish this node state. " +
|
||||
"A failure condition has been met during the outbound subscription message encoding process. " +
|
||||
"See the stacktrace below for further details.", exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maintenance()
|
||||
{
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMaintenance()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* When running in a slave mode, we need to recreate the tracker state every time.
|
||||
* This because in that context we don't have any tracker updating the state (e.g. lastIndexedChangeSetCommitTime,
|
||||
* lastIndexedChangeSetId)
|
||||
*
|
||||
* @return a new, fresh and up to date instance of {@link TrackerState}.
|
||||
*/
|
||||
@Override
|
||||
public TrackerState getTrackerState()
|
||||
{
|
||||
return infoSrv.getTrackerInitialState();
|
||||
}
|
||||
}
|
||||
+67
-115
@@ -26,35 +26,31 @@
|
||||
|
||||
package org.alfresco.solr.tracker;
|
||||
|
||||
import org.alfresco.opencmis.dictionary.CMISStrictDictionaryService;
|
||||
import org.alfresco.repo.dictionary.NamespaceDAO;
|
||||
import org.alfresco.repo.index.shard.ShardMethodEnum;
|
||||
import org.alfresco.httpclient.AuthenticationException;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.alfresco.repo.index.shard.ShardStateBuilder;
|
||||
import org.alfresco.repo.search.impl.QueryParserUtils;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.solr.AlfrescoCoreAdminHandler;
|
||||
import org.alfresco.solr.AlfrescoSolrDataModel;
|
||||
import org.alfresco.solr.InformationServer;
|
||||
import org.alfresco.solr.NodeReport;
|
||||
import org.alfresco.solr.TrackerState;
|
||||
import org.alfresco.solr.client.SOLRAPIClient;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.codec.EncoderException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import static java.util.Optional.of;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static org.alfresco.solr.tracker.DocRouterFactory.SHARD_KEY_KEY;
|
||||
import static org.alfresco.solr.tracker.Tracker.Type.NODE_STATE_PUBLISHER;
|
||||
|
||||
/**
|
||||
* Superclass for all components which are able to inform Alfresco about the hosting node state.
|
||||
* Despite belonging to the Tracker ecosystem, this component is actually a publisher, which periodically informs
|
||||
* Alfresco about the state of the hosting core.
|
||||
*
|
||||
* This has been introduced in SEARCH-1752 for splitting the dual responsibility of the {@link org.alfresco.solr.tracker.MetadataTracker}.
|
||||
* As consequence of that, this class contains only the members needed for obtaining a valid
|
||||
* {@link org.alfresco.repo.index.shard.ShardState} that can be periodically communicated to Alfresco.
|
||||
@@ -63,113 +59,80 @@ import static org.alfresco.solr.tracker.DocRouterFactory.SHARD_KEY_KEY;
|
||||
* @since 1.5
|
||||
* @see <a href="https://issues.alfresco.com/jira/browse/SEARCH-1752">SEARCH-1752</a>
|
||||
*/
|
||||
public abstract class AbstractShardInformationPublisher extends AbstractTracker
|
||||
public class ShardStatePublisher extends AbstractTracker
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractShardInformationPublisher.class);
|
||||
DocRouter docRouter;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ShardStatePublisher.class);
|
||||
private static final Map<String, Semaphore> RUN_LOCK_BY_CORE = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Semaphore> WRITE_LOCK_BY_CORE = new ConcurrentHashMap<>();
|
||||
|
||||
private final boolean isMaster;
|
||||
|
||||
/** The string representation of the shard key. */
|
||||
private Optional<String> shardKey;
|
||||
|
||||
/** The property to use for determining the shard. */
|
||||
protected Optional<QName> shardProperty = Optional.empty();
|
||||
|
||||
AbstractShardInformationPublisher(
|
||||
public ShardStatePublisher(
|
||||
boolean isMaster,
|
||||
Properties p,
|
||||
SOLRAPIClient client,
|
||||
String coreName,
|
||||
InformationServer informationServer,
|
||||
Type type)
|
||||
InformationServer informationServer)
|
||||
{
|
||||
super(p, client, coreName, informationServer, type);
|
||||
super(p, client, coreName, informationServer, NODE_STATE_PUBLISHER);
|
||||
|
||||
this.isMaster = isMaster;
|
||||
shardKey = ofNullable(p.getProperty(SHARD_KEY_KEY));
|
||||
|
||||
firstUpdateShardProperty();
|
||||
|
||||
docRouter = DocRouterFactory.getRouter(p, shardMethod);
|
||||
RUN_LOCK_BY_CORE.put(coreName, new Semaphore(1, true));
|
||||
WRITE_LOCK_BY_CORE.put(coreName, new Semaphore(1, true));
|
||||
}
|
||||
|
||||
AbstractShardInformationPublisher(Type type)
|
||||
@Override
|
||||
protected void doTrack(String iterationId)
|
||||
{
|
||||
super(type);
|
||||
this.isMaster = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the {@link org.alfresco.solr.client.Node} associated with the given dbid.
|
||||
*
|
||||
* @param dbid the node identifier.
|
||||
* @return the {@link org.alfresco.solr.client.Node} associated with the given dbid.
|
||||
*/
|
||||
public NodeReport checkNode(Long dbid)
|
||||
{
|
||||
NodeReport nodeReport = new NodeReport();
|
||||
nodeReport.setDbid(dbid);
|
||||
|
||||
this.infoSrv.addCommonNodeReportInfo(nodeReport);
|
||||
|
||||
return nodeReport;
|
||||
}
|
||||
|
||||
private void firstUpdateShardProperty()
|
||||
{
|
||||
shardKey.ifPresent( shardKeyName -> {
|
||||
updateShardProperty();
|
||||
if (shardProperty.isEmpty())
|
||||
{
|
||||
LOGGER.warn("Sharding property {} was set to {}, but no such property was found.", SHARD_KEY_KEY, shardKeyName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shard property using the shard key.
|
||||
*/
|
||||
void updateShardProperty()
|
||||
{
|
||||
shardKey.ifPresent(shardKeyName -> {
|
||||
Optional<QName> updatedShardProperty = getShardProperty(shardKeyName);
|
||||
if (!shardProperty.equals(updatedShardProperty))
|
||||
{
|
||||
if (updatedShardProperty.isEmpty())
|
||||
{
|
||||
LOGGER.warn("The model defining {} property has been disabled", shardKeyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.info("New {} property found for {}", SHARD_KEY_KEY, shardKeyName);
|
||||
}
|
||||
}
|
||||
shardProperty = updatedShardProperty;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the field name, returns the name of the property definition.
|
||||
* If the property definition is not found, Empty optional is returned.
|
||||
*
|
||||
* @param field the field name.
|
||||
* @return the name of the associated property definition if present, Optional.Empty() otherwise
|
||||
*/
|
||||
static Optional<QName> getShardProperty(String field)
|
||||
{
|
||||
if (StringUtils.isBlank(field))
|
||||
try
|
||||
{
|
||||
throw new IllegalArgumentException("Sharding property " + SHARD_KEY_KEY + " has not been set.");
|
||||
ShardState shardstate = getShardState();
|
||||
client.getTransactions(0L, null, 0L, null, 0, shardstate);
|
||||
}
|
||||
catch (EncoderException | IOException | AuthenticationException exception )
|
||||
{
|
||||
LOGGER.error("Unable to publish this node state. " +
|
||||
"A failure condition has been met during the outbound subscription message encoding process. " +
|
||||
"See the stacktrace below for further details.", exception);
|
||||
}
|
||||
}
|
||||
|
||||
AlfrescoSolrDataModel dataModel = AlfrescoSolrDataModel.getInstance();
|
||||
NamespaceDAO namespaceDAO = dataModel.getNamespaceDAO();
|
||||
DictionaryService dictionaryService = dataModel.getDictionaryService(CMISStrictDictionaryService.DEFAULT);
|
||||
PropertyDefinition propertyDef = QueryParserUtils.matchPropertyDefinition("http://www.alfresco.org/model/content/1.0",
|
||||
namespaceDAO,
|
||||
dictionaryService,
|
||||
field);
|
||||
@Override
|
||||
public void maintenance()
|
||||
{
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
return ofNullable(propertyDef).map(PropertyDefinition::getName);
|
||||
@Override
|
||||
public boolean hasMaintenance()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* When running in a slave mode, we need to recreate the tracker state every time.
|
||||
* This because in that context we don't have any tracker updating the state (e.g. lastIndexedChangeSetCommitTime,
|
||||
* lastIndexedChangeSetId)
|
||||
*
|
||||
* @return a new, fresh and up to date instance of {@link TrackerState}.
|
||||
*/
|
||||
@Override
|
||||
public TrackerState getTrackerState()
|
||||
{
|
||||
return infoSrv.getTrackerInitialState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Semaphore getWriteLock()
|
||||
{
|
||||
return WRITE_LOCK_BY_CORE.get(coreName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Semaphore getRunLock()
|
||||
{
|
||||
return RUN_LOCK_BY_CORE.get(coreName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,7 +140,6 @@ public abstract class AbstractShardInformationPublisher extends AbstractTracker
|
||||
* {@link MetadataTracker} instance.
|
||||
*
|
||||
* @return the {@link ShardState} instance which stores the current state of the hosting shard.
|
||||
* @see NodeStatePublisher
|
||||
*/
|
||||
ShardState getShardState()
|
||||
{
|
||||
@@ -222,16 +184,6 @@ public abstract class AbstractShardInformationPublisher extends AbstractTracker
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link DocRouter} instance in use on this node.
|
||||
*
|
||||
* @return the {@link DocRouter} instance in use on this node.
|
||||
*/
|
||||
public DocRouter getDocRouter()
|
||||
{
|
||||
return this.docRouter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the hosting core is master or standalone.
|
||||
*
|
||||
@@ -148,10 +148,19 @@ public abstract class Utils
|
||||
*/
|
||||
public static boolean isNullOrEmpty(String value)
|
||||
{
|
||||
return ofNullable(value)
|
||||
.map(String::trim)
|
||||
.map(String::isEmpty)
|
||||
.orElse(true);
|
||||
return value == null || value.trim().length() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the input string is not null and it is not empty.
|
||||
* Note whitespaces are not considered, so if a string contains only whitespaces, it is considered empty.
|
||||
*
|
||||
* @param value the input string.
|
||||
* @return true if the input string is not null and it is not empty.
|
||||
*/
|
||||
public static boolean isNotNullAndNotEmpty(String value)
|
||||
{
|
||||
return value != null && value.trim().length() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+10
@@ -191,6 +191,16 @@ public abstract class AbstractAlfrescoSolrIT implements SolrTestFiles, AlfrescoS
|
||||
h.reload();
|
||||
}
|
||||
|
||||
protected void disableIndexing()
|
||||
{
|
||||
admin.actionDisableIndexing(new ModifiableSolrParams());
|
||||
}
|
||||
|
||||
protected void enableIndexing()
|
||||
{
|
||||
admin.actionEnableIndexing(new ModifiableSolrParams());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated as testHarness is used
|
||||
* Get admin core handler
|
||||
|
||||
+7
-8
@@ -62,7 +62,7 @@ import org.alfresco.solr.tracker.DocRouter;
|
||||
import org.alfresco.solr.tracker.IndexHealthReport;
|
||||
import org.alfresco.solr.tracker.MetadataTracker;
|
||||
import org.alfresco.solr.tracker.PropertyRouter;
|
||||
import org.alfresco.solr.tracker.NodeStatePublisher;
|
||||
import org.alfresco.solr.tracker.ShardStatePublisher;
|
||||
import org.alfresco.solr.tracker.TrackerRegistry;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.common.params.CoreAdminParams;
|
||||
@@ -219,23 +219,22 @@ public class AlfrescoCoreAdminHandlerIT
|
||||
}
|
||||
|
||||
@Test
|
||||
public void coreIsMaster_thenCoreStatePublisherInstanceCorrespondsToMetadataTracker()
|
||||
public void coreIsMaster_thenCoreStatePublisherInstanceCorrespondsToShardStatePublisher()
|
||||
{
|
||||
MetadataTracker coreStatePublisher = mock(MetadataTracker.class);
|
||||
ShardStatePublisher coreStatePublisher = mock(ShardStatePublisher.class);
|
||||
|
||||
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class)))
|
||||
when(trackerRegistry.getTrackerForCore(anyString(), eq(ShardStatePublisher.class)))
|
||||
.thenReturn(coreStatePublisher);
|
||||
|
||||
assertSame(coreStatePublisher, alfrescoCoreAdminHandler.coreStatePublisher("ThisIsTheCoreName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void coreIsSlave_thenCoreStatePublisherInstanceCorrespondsToSlaveCoreStatePublisher()
|
||||
public void coreIsSlave_thenCoreStatePublisherInstanceCorrespondsToShardStatePublisher()
|
||||
{
|
||||
NodeStatePublisher coreStateTracker = mock(NodeStatePublisher.class);
|
||||
ShardStatePublisher coreStateTracker = mock(ShardStatePublisher.class);
|
||||
|
||||
when(trackerRegistry.getTrackerForCore(anyString(), eq(MetadataTracker.class))).thenReturn(null);
|
||||
when(trackerRegistry.getTrackerForCore(anyString(), eq(NodeStatePublisher.class))).thenReturn(coreStateTracker);
|
||||
when(trackerRegistry.getTrackerForCore(anyString(), eq(ShardStatePublisher.class))).thenReturn(coreStateTracker);
|
||||
|
||||
assertSame(coreStateTracker, alfrescoCoreAdminHandler.coreStatePublisher("ThisIsTheCoreName"));
|
||||
}
|
||||
|
||||
+655
-25
@@ -26,37 +26,21 @@
|
||||
|
||||
package org.alfresco.solr;
|
||||
|
||||
import org.alfresco.solr.adapters.IOpenBitSet;
|
||||
import org.alfresco.solr.adapters.SolrOpenBitSetAdapter;
|
||||
import org.alfresco.solr.tracker.AclTracker;
|
||||
import org.alfresco.solr.tracker.IndexHealthReport;
|
||||
import org.alfresco.solr.tracker.MetadataTracker;
|
||||
import org.alfresco.solr.tracker.TrackerRegistry;
|
||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.apache.solr.core.CoreContainer;
|
||||
import org.apache.solr.core.SolrCore;
|
||||
import org.apache.solr.core.SolrResourceLoader;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static java.util.Optional.of;
|
||||
import static java.util.stream.IntStream.range;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ACL_TX_IN_INDEX_NOT_IN_DB;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ACTION_ERROR_MESSAGE_LABEL;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ACTION_STATUS_LABEL;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ACTION_STATUS_NOT_SCHEDULED;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ACTION_STATUS_SCHEDULED;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ADDITIONAL_INFO;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ALFRESCO_CORE_NAME;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ARCHIVE_CORE_NAME;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ARG_ACLID;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ARG_ACLTXID;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ARG_NODEID;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.ARG_TXID;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.CORE_PARAMETER_NAMES;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.DRY_RUN_PARAMETER_NAME;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.DUPLICATED_ACL_TX_IN_INDEX;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.DUPLICATED_TX_IN_INDEX;
|
||||
@@ -69,13 +53,53 @@ import static org.alfresco.solr.AlfrescoCoreAdminHandler.TO_TX_COMMIT_TIME_PARAM
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.TX_IN_INDEX_NOT_IN_DB;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.UNKNOWN_CORE_MESSAGE;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.UNPROCESSABLE_REQUEST_ON_SLAVE_NODES;
|
||||
import static org.alfresco.solr.AlfrescoCoreAdminHandler.VERSION_CORE_NAME;
|
||||
import static org.apache.solr.common.params.CoreAdminParams.ACTION;
|
||||
import static org.apache.solr.common.params.CoreAdminParams.CORE;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.alfresco.solr.adapters.IOpenBitSet;
|
||||
import org.alfresco.solr.adapters.SolrOpenBitSetAdapter;
|
||||
import org.alfresco.solr.client.SOLRAPIClient;
|
||||
import org.alfresco.solr.tracker.AclTracker;
|
||||
import org.alfresco.solr.tracker.IndexHealthReport;
|
||||
import org.alfresco.solr.tracker.MetadataTracker;
|
||||
import org.alfresco.solr.tracker.TrackerRegistry;
|
||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.apache.solr.common.util.SimpleOrderedMap;
|
||||
import org.apache.solr.core.CoreContainer;
|
||||
import org.apache.solr.core.SolrCore;
|
||||
import org.apache.solr.core.SolrResourceLoader;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
import org.apache.solr.response.SolrQueryResponse;
|
||||
import org.json.JSONException;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class AlfrescoCoreAdminHandlerTest
|
||||
{
|
||||
@@ -97,7 +121,7 @@ public class AlfrescoCoreAdminHandlerTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noTargetCoreInParams()
|
||||
public void noTargetCoreToFixInParams()
|
||||
{
|
||||
assertEquals(0, params.size());
|
||||
|
||||
@@ -106,7 +130,7 @@ public class AlfrescoCoreAdminHandlerTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unknownTargetCoreInParams()
|
||||
public void unknownTargetCoreToFixInParams()
|
||||
{
|
||||
String invalidCoreName = "thisIsAnInvalidOrAtLeastUnknownCoreName";
|
||||
params.set(CORE, invalidCoreName);
|
||||
@@ -213,6 +237,86 @@ public class AlfrescoCoreAdminHandlerTest
|
||||
assertEquals(ACTION_STATUS_NOT_SCHEDULED, actionResponse.get(ACTION_STATUS_LABEL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void masterOrStandaloneNodeWithTrackersDisabled_DryRunParameterShouldBeForcedToTrue()
|
||||
{
|
||||
class TestMetadataTracker extends MetadataTracker {
|
||||
|
||||
protected TestMetadataTracker() {
|
||||
super(new Properties(), mock(SOLRAPIClient.class), ALFRESCO_CORE_NAME, mock(InformationServer.class));
|
||||
this.state = new TrackerState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTrack(String iterationId) {
|
||||
// Nothing to be done here, it's a fake implementation.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setPersistentIndexingStateAcrossReloadsTo(boolean enabled) {
|
||||
// Nothing to be done here, it's a fake implementation.
|
||||
}
|
||||
}
|
||||
|
||||
class TestAclTracker extends AclTracker {
|
||||
|
||||
protected TestAclTracker() {
|
||||
super(new Properties(), mock(SOLRAPIClient.class), ALFRESCO_CORE_NAME, mock(InformationServer.class));
|
||||
this.state = new TrackerState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTrack(String iterationId) {
|
||||
// Nothing to be done here, it's a fake implementation.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setPersistentIndexingStateAcrossReloadsTo(boolean enabled) {
|
||||
// Nothing to be done here, it's a fake implementation.
|
||||
}
|
||||
}
|
||||
|
||||
admin = new AlfrescoCoreAdminHandler() {
|
||||
@Override
|
||||
NamedList<Object> fixOnSpecificCore(
|
||||
String coreName,
|
||||
Long fromTxCommitTime,
|
||||
Long toTxCommitTime,
|
||||
boolean dryRun,
|
||||
int maxTransactionsToSchedule) {
|
||||
return new NamedList<>(); // dummy entry
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isMasterOrStandalone(String coreName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
admin.trackerRegistry = registry;
|
||||
final MetadataTracker metadataTracker = new TestMetadataTracker();
|
||||
final AclTracker aclTracker = new TestAclTracker();
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, MetadataTracker.class)).thenReturn(metadataTracker);
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, AclTracker.class)).thenReturn(aclTracker);
|
||||
when(registry.getTrackersForCore(ALFRESCO_CORE_NAME)).thenReturn(List.of(metadataTracker, aclTracker));
|
||||
|
||||
params.set(CORE, ALFRESCO_CORE_NAME);
|
||||
|
||||
// Let's disable tracking on Alfresco
|
||||
admin.actionDisableIndexing(params);
|
||||
|
||||
params.set(DRY_RUN_PARAMETER_NAME, false);
|
||||
|
||||
NamedList<Object> actionResponse = admin.actionFIX(params);
|
||||
assertEquals(false, actionResponse.get(DRY_RUN_PARAMETER_NAME));
|
||||
assertEquals(ACTION_STATUS_NOT_SCHEDULED, actionResponse.get(ACTION_STATUS_LABEL));
|
||||
assertNotNull(
|
||||
"There should be a message which informs the requestor about the actual dryRun execution",
|
||||
actionResponse.get(ADDITIONAL_INFO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void masterOrStandaloneNode_explicitDryRunParameterIsEchoed()
|
||||
{
|
||||
@@ -482,6 +586,532 @@ public class AlfrescoCoreAdminHandlerTest
|
||||
.orElseThrow(() -> new RuntimeException(MISSING_ACL_TX_IN_INDEX + " section not found in response.")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableIndexingActionParameter_shouldTriggerTheDisableIndexingAction()
|
||||
{
|
||||
final AtomicBoolean invocationMarker = new AtomicBoolean();
|
||||
admin = new AlfrescoCoreAdminHandler() {
|
||||
@Override
|
||||
NamedList<Object> fixOnSpecificCore(
|
||||
String coreName,
|
||||
Long fromTxCommitTime,
|
||||
Long toTxCommitTime,
|
||||
boolean dryRun,
|
||||
int maxTransactionsToSchedule) {
|
||||
return new NamedList<>(); // dummy entry
|
||||
}
|
||||
|
||||
@Override
|
||||
NamedList<Object> actionDisableIndexing(SolrParams params) throws JSONException {
|
||||
invocationMarker.set(true);
|
||||
return new SimpleOrderedMap<>();
|
||||
}
|
||||
};
|
||||
|
||||
params.set(ACTION, "DISABLE-INDEXING");
|
||||
|
||||
SolrQueryRequest request = mock(SolrQueryRequest.class);
|
||||
when(request.getParams()).thenReturn(params);
|
||||
|
||||
admin.handleCustomAction(request, mock(SolrQueryResponse.class));
|
||||
|
||||
assertTrue(invocationMarker.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableIndexingActionParameter_shouldTriggerTheIndexingEnabling()
|
||||
{
|
||||
final AtomicBoolean invocationMarker = new AtomicBoolean();
|
||||
admin = new AlfrescoCoreAdminHandler() {
|
||||
@Override
|
||||
NamedList<Object> fixOnSpecificCore(
|
||||
String coreName,
|
||||
Long fromTxCommitTime,
|
||||
Long toTxCommitTime,
|
||||
boolean dryRun,
|
||||
int maxTransactionsToSchedule) {
|
||||
return new NamedList<>(); // dummy entry
|
||||
}
|
||||
|
||||
@Override
|
||||
NamedList<Object> actionEnableIndexing(SolrParams params) throws JSONException {
|
||||
invocationMarker.set(true);
|
||||
return new SimpleOrderedMap<>();
|
||||
}
|
||||
};
|
||||
|
||||
params.set(ACTION, "ENABLE-INDEXING");
|
||||
|
||||
SolrQueryRequest request = mock(SolrQueryRequest.class);
|
||||
when(request.getParams()).thenReturn(params);
|
||||
|
||||
admin.handleCustomAction(request, mock(SolrQueryResponse.class));
|
||||
|
||||
assertTrue(invocationMarker.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unknownCoreNameInDisableIndexingCommand_shouldReturnAnErrorResponse()
|
||||
{
|
||||
String unknownCoreName = "ThisShouldBeAnInexistentCore";
|
||||
CORE_PARAMETER_NAMES.forEach(parameter -> {
|
||||
params.set(parameter, unknownCoreName);
|
||||
|
||||
NamedList<?> response = admin.actionDisableIndexing(params);
|
||||
assertEquals(UNKNOWN_CORE_MESSAGE + unknownCoreName, response.get(ACTION_ERROR_MESSAGE_LABEL));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unknownCoreNameInEnableIndexingCommand_shouldReturnAnErrorResponse()
|
||||
{
|
||||
String unknownCoreName = "ThisShouldBeAnInexistentCore";
|
||||
CORE_PARAMETER_NAMES.forEach(parameter -> {
|
||||
params.set(parameter, unknownCoreName);
|
||||
|
||||
NamedList<?> response = admin.actionEnableIndexing(params);
|
||||
assertEquals(UNKNOWN_CORE_MESSAGE + unknownCoreName, response.get(ACTION_ERROR_MESSAGE_LABEL));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableIndexingOnSpecificSlaveCore_shouldReturnAnErrorResponse()
|
||||
{
|
||||
// The admin handler detects if a core is slave, master or standalone by checking
|
||||
// the trackers installed on it. If no trackers have been registered, then the core is considered a slave.
|
||||
assertFalse(admin.isMasterOrStandalone(ALFRESCO_CORE_NAME));
|
||||
CORE_PARAMETER_NAMES.forEach(parameter -> {
|
||||
params.set(parameter, ALFRESCO_CORE_NAME);
|
||||
|
||||
NamedList<?> response = admin.actionDisableIndexing(params);
|
||||
assertEquals(UNPROCESSABLE_REQUEST_ON_SLAVE_NODES, response.get(ACTION_ERROR_MESSAGE_LABEL));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableIndexingOnSpecificSlaveCore_shouldReturnAnErrorResponse()
|
||||
{
|
||||
// The admin handler detects if a core is slave, master or standalone by checking
|
||||
// the trackers installed on it. If no trackers have been registered, then the core is considered a slave.
|
||||
assertFalse(admin.isMasterOrStandalone(ALFRESCO_CORE_NAME));
|
||||
CORE_PARAMETER_NAMES.forEach(parameter -> {
|
||||
params.set(parameter, ALFRESCO_CORE_NAME);
|
||||
|
||||
NamedList<?> response = admin.actionEnableIndexing(params);
|
||||
assertEquals(UNPROCESSABLE_REQUEST_ON_SLAVE_NODES, response.get(ACTION_ERROR_MESSAGE_LABEL));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableIndexingWithoutIndicatingSpecificCore_shouldHaveNoEffectIfAllCoresAreSlave()
|
||||
{
|
||||
admin = spy(new AlfrescoCoreAdminHandler());
|
||||
admin.trackerRegistry = registry;
|
||||
when(registry.getCoreNames()).thenReturn(Set.of(ALFRESCO_CORE_NAME, ARCHIVE_CORE_NAME));
|
||||
admin.actionDisableIndexing(params);
|
||||
|
||||
verify(admin, times(0)).disableIndexingOnSpecificCore(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableIndexingWithoutIndicatingSpecificCore_shouldHaveNoEffectIfAllCoresAreSlave()
|
||||
{
|
||||
admin = spy(new AlfrescoCoreAdminHandler());
|
||||
admin.trackerRegistry = registry;
|
||||
when(registry.getCoreNames()).thenReturn(Set.of(ALFRESCO_CORE_NAME, ARCHIVE_CORE_NAME));
|
||||
admin.actionEnableIndexing(params);
|
||||
|
||||
verify(admin, times(0)).enableIndexingOnSpecificCore(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableIndexingWithoutIndicatingSpecificCore_shouldAffectOnlyMasterOrStandaloneCores()
|
||||
{
|
||||
admin = spy(new AlfrescoCoreAdminHandler());
|
||||
admin.trackerRegistry = registry;
|
||||
when(registry.getCoreNames()).thenReturn(Set.of(ALFRESCO_CORE_NAME, ARCHIVE_CORE_NAME, VERSION_CORE_NAME));
|
||||
|
||||
// "alfresco" and "archive" are master/standalone cores, "version" is a slave core
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, MetadataTracker.class)).thenReturn(mock(MetadataTracker.class));
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, MetadataTracker.class)).thenReturn(mock(MetadataTracker.class));
|
||||
when(registry.getTrackerForCore(VERSION_CORE_NAME, MetadataTracker.class)).thenReturn(null);
|
||||
|
||||
admin.actionDisableIndexing(params);
|
||||
|
||||
verify(admin, times(1)).disableIndexingOnSpecificCore(ALFRESCO_CORE_NAME);
|
||||
verify(admin, times(1)).disableIndexingOnSpecificCore(ARCHIVE_CORE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableIndexingWithoutIndicatingSpecificCore_shouldAffectOnlyMasterOrStandaloneCores()
|
||||
{
|
||||
admin = spy(new AlfrescoCoreAdminHandler());
|
||||
admin.trackerRegistry = registry;
|
||||
when(registry.getCoreNames()).thenReturn(Set.of(ALFRESCO_CORE_NAME, ARCHIVE_CORE_NAME, VERSION_CORE_NAME));
|
||||
|
||||
// "alfresco" and "archive" are master/standalone cores, "version" is a slave core
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, MetadataTracker.class)).thenReturn(mock(MetadataTracker.class));
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, MetadataTracker.class)).thenReturn(mock(MetadataTracker.class));
|
||||
when(registry.getTrackerForCore(VERSION_CORE_NAME, MetadataTracker.class)).thenReturn(null);
|
||||
|
||||
admin.actionEnableIndexing(params);
|
||||
|
||||
verify(admin, times(1)).enableIndexingOnSpecificCore(ALFRESCO_CORE_NAME);
|
||||
verify(admin, times(1)).enableIndexingOnSpecificCore(ARCHIVE_CORE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retryActionOnSlaveNode_shouldReturnWarningMessage()
|
||||
{
|
||||
admin.coreNames().forEach(coreName -> assertFalse(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
NamedList<Object> actionResponse = admin.actionRETRY(params);
|
||||
assertNotNull(actionResponse.get(AlfrescoCoreAdminHandler.WARNING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retryActionWhenIndexingIsDisabled_shouldReturnAnInfoMessage()
|
||||
{
|
||||
// That is not true: each core has an its own InformationServer instance
|
||||
// However for this specific test we don't care
|
||||
InformationServer srv = mock(InformationServer.class);
|
||||
admin.informationServers = new ConcurrentHashMap<>();
|
||||
admin.informationServers.put(ALFRESCO_CORE_NAME, srv);
|
||||
admin.informationServers.put(ARCHIVE_CORE_NAME, srv);
|
||||
|
||||
// That is not true: each core has an its own MetadataTracker instance
|
||||
// However for this specific test we don't care
|
||||
MetadataTracker metadataTracker = mock(MetadataTracker.class);
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, MetadataTracker.class)).thenReturn(metadataTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, MetadataTracker.class)).thenReturn(metadataTracker);
|
||||
|
||||
when(metadataTracker.isEnabled()).thenReturn(false);
|
||||
|
||||
admin.coreNames().forEach(coreName -> assertTrue(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
final NamedList<Object> actionResponse = admin.actionRETRY(params);
|
||||
admin.coreNames()
|
||||
.stream()
|
||||
.map(actionResponse::get)
|
||||
.map(NamedList.class::cast)
|
||||
.forEach(response -> {
|
||||
assertEquals(ACTION_STATUS_NOT_SCHEDULED, response.get(ACTION_STATUS_LABEL));
|
||||
assertNotNull(response.get(ADDITIONAL_INFO));
|
||||
});
|
||||
|
||||
verifyNoInteractions(srv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retryActionWhenIndexingIsEnabled_shouldCollectThingsToReindex() throws Exception
|
||||
{
|
||||
final Set<Long> alfrescoErrorNodeIds = Set.of(123452L, 13579L, 24680L, 98765L);
|
||||
final Set<Long> archiveErrorNodeIds = Set.of(1234520L, 913579L, 124680L, 598765L);
|
||||
|
||||
InformationServer alfrescoInformationServer = mock(InformationServer.class);
|
||||
InformationServer archiveInformationServer = mock(InformationServer.class);
|
||||
|
||||
admin.informationServers = new ConcurrentHashMap<>();
|
||||
admin.informationServers.put(ALFRESCO_CORE_NAME, alfrescoInformationServer);
|
||||
admin.informationServers.put(ARCHIVE_CORE_NAME, archiveInformationServer);
|
||||
|
||||
when(alfrescoInformationServer.getErrorDocIds()).thenReturn(alfrescoErrorNodeIds);
|
||||
when(archiveInformationServer.getErrorDocIds()).thenReturn(archiveErrorNodeIds);
|
||||
|
||||
MetadataTracker alfrescoMetadataTracker = mock(MetadataTracker.class);
|
||||
MetadataTracker archiveMetadataTracker = mock(MetadataTracker.class);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, MetadataTracker.class)).thenReturn(alfrescoMetadataTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, MetadataTracker.class)).thenReturn(archiveMetadataTracker);
|
||||
|
||||
when(alfrescoMetadataTracker.isEnabled()).thenReturn(true);
|
||||
when(archiveMetadataTracker.isEnabled()).thenReturn(true);
|
||||
|
||||
admin.coreNames().forEach(coreName -> assertTrue(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
final NamedList<Object> actionResponse = admin.actionRETRY(params);
|
||||
admin.coreNames()
|
||||
.stream()
|
||||
.map(actionResponse::get)
|
||||
.map(NamedList.class::cast)
|
||||
.forEach(response -> {
|
||||
assertEquals(ACTION_STATUS_SCHEDULED, response.get(ACTION_STATUS_LABEL));
|
||||
});
|
||||
|
||||
alfrescoErrorNodeIds.forEach(id -> verify(alfrescoMetadataTracker).addNodeToReindex(id));
|
||||
archiveErrorNodeIds.forEach(id -> verify(archiveMetadataTracker).addNodeToReindex(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexActionOnSlaveNode_shouldReturnWarningMessage()
|
||||
{
|
||||
admin.coreNames().forEach(coreName -> assertFalse(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
NamedList<Object> actionResponse = admin.actionINDEX(params);
|
||||
assertNotNull(actionResponse.get(AlfrescoCoreAdminHandler.WARNING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexActionWhenIndexingIsDisabled_shouldReturnAnInfoMessage()
|
||||
{
|
||||
MetadataTracker metadataTracker = mock(MetadataTracker.class);
|
||||
AclTracker aclTracker = mock(AclTracker.class);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, MetadataTracker.class)).thenReturn(metadataTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, MetadataTracker.class)).thenReturn(metadataTracker);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, AclTracker.class)).thenReturn(aclTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, AclTracker.class)).thenReturn(aclTracker);
|
||||
|
||||
when(metadataTracker.isEnabled()).thenReturn(false);
|
||||
when(aclTracker.isEnabled()).thenReturn(false);
|
||||
|
||||
admin.coreNames().forEach(coreName -> assertTrue(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
final NamedList<Object> actionResponse = admin.actionINDEX(params);
|
||||
admin.coreNames()
|
||||
.stream()
|
||||
.map(actionResponse::get)
|
||||
.map(NamedList.class::cast)
|
||||
.forEach(response -> {
|
||||
assertEquals(ACTION_STATUS_NOT_SCHEDULED, response.get(ACTION_STATUS_LABEL));
|
||||
assertNotNull(response.get(ADDITIONAL_INFO));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexActionWhenIndexingIsEnabled_shouldCollectThingsToReindex()
|
||||
{
|
||||
final String txIdParam = "123452";
|
||||
final String aclTxIdParam = "13579";
|
||||
final String nodeIdParam = "24680";
|
||||
final String aclIdParam = "98765";
|
||||
|
||||
params.set(ARG_TXID, txIdParam);
|
||||
params.set(ARG_ACLTXID, aclTxIdParam);
|
||||
params.set(ARG_NODEID, nodeIdParam);
|
||||
params.set(ARG_ACLID, aclIdParam);
|
||||
|
||||
MetadataTracker alfrescoMetadataTracker = mock(MetadataTracker.class);
|
||||
AclTracker alfrescoAclTracker = mock(AclTracker.class);
|
||||
|
||||
MetadataTracker archiveMetadataTracker = mock(MetadataTracker.class);
|
||||
AclTracker archiveAclTracker = mock(AclTracker.class);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, MetadataTracker.class)).thenReturn(alfrescoMetadataTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, MetadataTracker.class)).thenReturn(archiveMetadataTracker);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, AclTracker.class)).thenReturn(alfrescoAclTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, AclTracker.class)).thenReturn(archiveAclTracker);
|
||||
|
||||
when(alfrescoMetadataTracker.isEnabled()).thenReturn(true);
|
||||
when(alfrescoAclTracker.isEnabled()).thenReturn(true);
|
||||
|
||||
when(archiveMetadataTracker.isEnabled()).thenReturn(true);
|
||||
when(archiveAclTracker.isEnabled()).thenReturn(true);
|
||||
|
||||
admin.coreNames().forEach(coreName -> assertTrue(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
final NamedList<Object> actionResponse = admin.actionINDEX(params);
|
||||
admin.coreNames()
|
||||
.stream()
|
||||
.map(actionResponse::get)
|
||||
.map(NamedList.class::cast)
|
||||
.forEach(response -> {
|
||||
assertEquals(ACTION_STATUS_SCHEDULED, response.get(ACTION_STATUS_LABEL));
|
||||
});
|
||||
|
||||
verify(alfrescoMetadataTracker).addTransactionToIndex(Long.parseLong(txIdParam));
|
||||
verify(alfrescoMetadataTracker).addNodeToIndex(Long.parseLong(nodeIdParam));
|
||||
verify(alfrescoAclTracker).addAclChangeSetToIndex(Long.parseLong(aclTxIdParam));
|
||||
verify(alfrescoAclTracker).addAclToIndex(Long.parseLong(aclIdParam));
|
||||
|
||||
verify(archiveMetadataTracker).addTransactionToIndex(Long.parseLong(txIdParam));
|
||||
verify(archiveMetadataTracker).addNodeToIndex(Long.parseLong(nodeIdParam));
|
||||
verify(archiveAclTracker).addAclChangeSetToIndex(Long.parseLong(aclTxIdParam));
|
||||
verify(archiveAclTracker).addAclToIndex(Long.parseLong(aclIdParam));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reindexActionOnSlaveNode_shouldReturnWarningMessage()
|
||||
{
|
||||
admin.coreNames().forEach(coreName -> assertFalse(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
NamedList<Object> actionResponse = admin.actionREINDEX(params);
|
||||
assertNotNull(actionResponse.get(AlfrescoCoreAdminHandler.WARNING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reindexActionWhenIndexingIsDisabled_shouldReturnAnInfoMessage()
|
||||
{
|
||||
MetadataTracker metadataTracker = mock(MetadataTracker.class);
|
||||
AclTracker aclTracker = mock(AclTracker.class);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, MetadataTracker.class)).thenReturn(metadataTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, MetadataTracker.class)).thenReturn(metadataTracker);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, AclTracker.class)).thenReturn(aclTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, AclTracker.class)).thenReturn(aclTracker);
|
||||
|
||||
when(metadataTracker.isEnabled()).thenReturn(false);
|
||||
when(aclTracker.isEnabled()).thenReturn(false);
|
||||
|
||||
admin.coreNames().forEach(coreName -> assertTrue(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
final NamedList<Object> actionResponse = admin.actionREINDEX(params);
|
||||
admin.coreNames()
|
||||
.stream()
|
||||
.map(actionResponse::get)
|
||||
.map(NamedList.class::cast)
|
||||
.forEach(response -> {
|
||||
assertEquals(ACTION_STATUS_NOT_SCHEDULED, response.get(ACTION_STATUS_LABEL));
|
||||
assertNotNull(response.get(ADDITIONAL_INFO));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reindexActionWhenIndexingIsEnabled_shouldCollectThingsToReindex()
|
||||
{
|
||||
final String txIdParam = "123452";
|
||||
final String aclTxIdParam = "13579";
|
||||
final String nodeIdParam = "24680";
|
||||
final String aclIdParam = "98765";
|
||||
|
||||
params.set(ARG_TXID, txIdParam);
|
||||
params.set(ARG_ACLTXID, aclTxIdParam);
|
||||
params.set(ARG_NODEID, nodeIdParam);
|
||||
params.set(ARG_ACLID, aclIdParam);
|
||||
|
||||
MetadataTracker alfrescoMetadataTracker = mock(MetadataTracker.class);
|
||||
AclTracker alfrescoAclTracker = mock(AclTracker.class);
|
||||
|
||||
MetadataTracker archiveMetadataTracker = mock(MetadataTracker.class);
|
||||
AclTracker archiveAclTracker = mock(AclTracker.class);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, MetadataTracker.class)).thenReturn(alfrescoMetadataTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, MetadataTracker.class)).thenReturn(archiveMetadataTracker);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, AclTracker.class)).thenReturn(alfrescoAclTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, AclTracker.class)).thenReturn(archiveAclTracker);
|
||||
|
||||
when(alfrescoMetadataTracker.isEnabled()).thenReturn(true);
|
||||
when(alfrescoAclTracker.isEnabled()).thenReturn(true);
|
||||
|
||||
when(archiveMetadataTracker.isEnabled()).thenReturn(true);
|
||||
when(archiveAclTracker.isEnabled()).thenReturn(true);
|
||||
|
||||
admin.coreNames().forEach(coreName -> assertTrue(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
final NamedList<Object> actionResponse = admin.actionREINDEX(params);
|
||||
admin.coreNames()
|
||||
.stream()
|
||||
.map(actionResponse::get)
|
||||
.map(NamedList.class::cast)
|
||||
.forEach(response -> {
|
||||
assertEquals(ACTION_STATUS_SCHEDULED, response.get(ACTION_STATUS_LABEL));
|
||||
});
|
||||
|
||||
verify(alfrescoMetadataTracker).addTransactionToReindex(Long.parseLong(txIdParam));
|
||||
verify(alfrescoMetadataTracker).addNodeToReindex(Long.parseLong(nodeIdParam));
|
||||
verify(alfrescoAclTracker).addAclChangeSetToReindex(Long.parseLong(aclTxIdParam));
|
||||
verify(alfrescoAclTracker).addAclToReindex(Long.parseLong(aclIdParam));
|
||||
|
||||
verify(archiveMetadataTracker).addTransactionToReindex(Long.parseLong(txIdParam));
|
||||
verify(archiveMetadataTracker).addNodeToReindex(Long.parseLong(nodeIdParam));
|
||||
verify(archiveAclTracker).addAclChangeSetToReindex(Long.parseLong(aclTxIdParam));
|
||||
verify(archiveAclTracker).addAclToReindex(Long.parseLong(aclIdParam));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void purgeActionOnSlaveNode_shouldReturnWarningMessage()
|
||||
{
|
||||
admin.coreNames().forEach(coreName -> assertFalse(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
NamedList<Object> actionResponse = admin.actionPURGE(params);
|
||||
assertNotNull(actionResponse.get(AlfrescoCoreAdminHandler.WARNING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void purgeActionWhenIndexingIsDisabled_shouldReturnAnInfoMessage()
|
||||
{
|
||||
MetadataTracker metadataTracker = mock(MetadataTracker.class);
|
||||
AclTracker aclTracker = mock(AclTracker.class);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, MetadataTracker.class)).thenReturn(metadataTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, MetadataTracker.class)).thenReturn(metadataTracker);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, AclTracker.class)).thenReturn(aclTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, AclTracker.class)).thenReturn(aclTracker);
|
||||
|
||||
when(metadataTracker.isEnabled()).thenReturn(false);
|
||||
when(aclTracker.isEnabled()).thenReturn(false);
|
||||
|
||||
admin.coreNames().forEach(coreName -> assertTrue(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
final NamedList<Object> actionResponse = admin.actionPURGE(params);
|
||||
admin.coreNames()
|
||||
.stream()
|
||||
.map(actionResponse::get)
|
||||
.map(NamedList.class::cast)
|
||||
.forEach(response -> {
|
||||
assertEquals(ACTION_STATUS_NOT_SCHEDULED, response.get(ACTION_STATUS_LABEL));
|
||||
assertNotNull(response.get(ADDITIONAL_INFO));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void purgeActionWhenIndexingIsEnabled_shouldCollectTransactionsToPurge()
|
||||
{
|
||||
final String txIdParam = "123452";
|
||||
final String aclTxIdParam = "13579";
|
||||
final String nodeIdParam = "24680";
|
||||
final String aclIdParam = "98765";
|
||||
|
||||
params.set(ARG_TXID, txIdParam);
|
||||
params.set(ARG_ACLTXID, aclTxIdParam);
|
||||
params.set(ARG_NODEID, nodeIdParam);
|
||||
params.set(ARG_ACLID, aclIdParam);
|
||||
|
||||
MetadataTracker alfrescoMetadataTracker = mock(MetadataTracker.class);
|
||||
AclTracker alfrescoAclTracker = mock(AclTracker.class);
|
||||
|
||||
MetadataTracker archiveMetadataTracker = mock(MetadataTracker.class);
|
||||
AclTracker archiveAclTracker = mock(AclTracker.class);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, MetadataTracker.class)).thenReturn(alfrescoMetadataTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, MetadataTracker.class)).thenReturn(archiveMetadataTracker);
|
||||
|
||||
when(registry.getTrackerForCore(ALFRESCO_CORE_NAME, AclTracker.class)).thenReturn(alfrescoAclTracker);
|
||||
when(registry.getTrackerForCore(ARCHIVE_CORE_NAME, AclTracker.class)).thenReturn(archiveAclTracker);
|
||||
|
||||
when(alfrescoMetadataTracker.isEnabled()).thenReturn(true);
|
||||
when(alfrescoAclTracker.isEnabled()).thenReturn(true);
|
||||
|
||||
when(archiveMetadataTracker.isEnabled()).thenReturn(true);
|
||||
when(archiveAclTracker.isEnabled()).thenReturn(true);
|
||||
|
||||
admin.coreNames().forEach(coreName -> assertTrue(admin.isMasterOrStandalone(coreName)));
|
||||
|
||||
final NamedList<Object> actionResponse = admin.actionPURGE(params);
|
||||
admin.coreNames()
|
||||
.stream()
|
||||
.map(actionResponse::get)
|
||||
.map(NamedList.class::cast)
|
||||
.forEach(response -> {
|
||||
assertEquals(ACTION_STATUS_SCHEDULED, response.get(ACTION_STATUS_LABEL));
|
||||
});
|
||||
|
||||
verify(alfrescoMetadataTracker).addTransactionToPurge(Long.parseLong(txIdParam));
|
||||
verify(alfrescoMetadataTracker).addNodeToPurge(Long.parseLong(nodeIdParam));
|
||||
verify(alfrescoAclTracker).addAclChangeSetToPurge(Long.parseLong(aclTxIdParam));
|
||||
verify(alfrescoAclTracker).addAclToPurge(Long.parseLong(aclIdParam));
|
||||
|
||||
verify(archiveMetadataTracker).addTransactionToPurge(Long.parseLong(txIdParam));
|
||||
verify(archiveMetadataTracker).addNodeToPurge(Long.parseLong(nodeIdParam));
|
||||
verify(archiveAclTracker).addAclChangeSetToPurge(Long.parseLong(aclTxIdParam));
|
||||
verify(archiveAclTracker).addAclToPurge(Long.parseLong(aclIdParam));
|
||||
}
|
||||
|
||||
private <T> void assertThatExplicitParameterIsEchoed(String parameterName, T parameterValue)
|
||||
{
|
||||
admin = new AlfrescoCoreAdminHandler() {
|
||||
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
package org.alfresco.solr;
|
||||
|
||||
import org.alfresco.solr.client.Acl;
|
||||
import org.alfresco.solr.client.AclChangeSet;
|
||||
import org.alfresco.solr.client.AclReaders;
|
||||
import org.alfresco.solr.client.Node;
|
||||
import org.alfresco.solr.client.NodeMetaData;
|
||||
import org.alfresco.solr.client.SOLRAPIQueueClient;
|
||||
import org.alfresco.solr.client.Transaction;
|
||||
import org.alfresco.solr.tracker.ActivatableTracker;
|
||||
import org.alfresco.solr.tracker.Tracker;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.TermQuery;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.junit.After;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.quartz.SchedulerException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getAcl;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getAclChangeSet;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getAclReaders;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getNode;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getNodeMetaData;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.getTransaction;
|
||||
import static org.alfresco.solr.AlfrescoSolrUtils.indexAclChangeSet;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@SolrTestCaseJ4.SuppressSSL
|
||||
public class AlfrescoIndexingStatePersistenceAcrossReloadsIT extends AbstractAlfrescoSolrIT
|
||||
{
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception
|
||||
{
|
||||
initAlfrescoCore("schema.xml");
|
||||
admin = (AlfrescoCoreAdminHandler)getCore().getCoreContainer().getMultiCoreHandler();
|
||||
}
|
||||
|
||||
@After
|
||||
public void clearQueue()
|
||||
{
|
||||
SOLRAPIQueueClient.NODE_META_DATA_MAP.clear();
|
||||
SOLRAPIQueueClient.TRANSACTION_QUEUE.clear();
|
||||
SOLRAPIQueueClient.ACL_CHANGE_SET_QUEUE.clear();
|
||||
SOLRAPIQueueClient.ACL_READERS_MAP.clear();
|
||||
SOLRAPIQueueClient.ACL_MAP.clear();
|
||||
SOLRAPIQueueClient.NODE_MAP.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexingStateAcrossReloads() throws Exception
|
||||
{
|
||||
long localId = 0L;
|
||||
|
||||
AclChangeSet aclChangeSet = getAclChangeSet(1, ++localId);
|
||||
|
||||
Acl acl = getAcl(aclChangeSet);
|
||||
|
||||
AclReaders aclReaders = getAclReaders(aclChangeSet, acl, singletonList("joel"), singletonList("phil"), null);
|
||||
|
||||
indexAclChangeSet(aclChangeSet,
|
||||
singletonList(acl),
|
||||
singletonList(aclReaders));
|
||||
|
||||
int numNodes = 1;
|
||||
List<Node> nodes = new ArrayList<>();
|
||||
List<NodeMetaData> nodeMetaDatas = new ArrayList<>();
|
||||
|
||||
Transaction bigTxn = getTransaction(0, numNodes, ++localId);
|
||||
|
||||
for(int i=0; i<numNodes; i++)
|
||||
{
|
||||
Node node = getNode(bigTxn, acl, Node.SolrApiNodeStatus.UPDATED);
|
||||
nodes.add(node);
|
||||
NodeMetaData nodeMetaData = getNodeMetaData(node, bigTxn, acl, "mike", null, false);
|
||||
nodeMetaDatas.add(nodeMetaData);
|
||||
}
|
||||
|
||||
indexTransaction(bigTxn, nodes, nodeMetaDatas);
|
||||
waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", "world")), numNodes, 100000);
|
||||
|
||||
Collection<Tracker> trackers = getTrackers();
|
||||
|
||||
disableIndexing();
|
||||
|
||||
// Make sure trackers have been disabled
|
||||
Collection<ActivatableTracker> activatableTrackers =
|
||||
getTrackers().stream()
|
||||
.filter(tracker -> tracker instanceof ActivatableTracker)
|
||||
.map(ActivatableTracker.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertFalse(activatableTrackers.isEmpty());
|
||||
activatableTrackers.forEach(tracker -> assertTrue(tracker.isDisabled()));
|
||||
|
||||
// Reload the core
|
||||
reloadAndAssertCorrect(trackers, trackers.size(), getJobsCount());
|
||||
|
||||
// Make sure indexing is disabled in the reloaded core
|
||||
Collection<ActivatableTracker> activatableTrackersBelongingToReloadedCore =
|
||||
getTrackers().stream()
|
||||
.filter(tracker -> tracker instanceof ActivatableTracker)
|
||||
.map(ActivatableTracker.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertFalse(activatableTrackersBelongingToReloadedCore.isEmpty());
|
||||
activatableTrackersBelongingToReloadedCore.forEach(tracker -> assertTrue(tracker.isDisabled()));
|
||||
|
||||
// Re-enable indexing
|
||||
enableIndexing();
|
||||
|
||||
// Make sure tracking has been enabled
|
||||
activatableTrackersBelongingToReloadedCore.forEach(tracker -> assertTrue(tracker.isEnabled()));
|
||||
|
||||
Transaction bigTxn2 = getTransaction(0, numNodes, ++localId);
|
||||
for(int i=0; i<numNodes; i++)
|
||||
{
|
||||
Node node = getNode(bigTxn2, acl, Node.SolrApiNodeStatus.UPDATED);
|
||||
nodes.add(node);
|
||||
NodeMetaData nodeMetaData = getNodeMetaData(node, bigTxn2, acl, "mike", null, false);
|
||||
nodeMetaDatas.add(nodeMetaData);
|
||||
}
|
||||
|
||||
indexTransaction(bigTxn2, nodes, nodeMetaDatas);
|
||||
waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", "world")), numNodes * 2, 100000);
|
||||
}
|
||||
|
||||
private void reloadAndAssertCorrect(Collection<Tracker> trackers, int numOfTrackers, int jobs) throws Exception
|
||||
{
|
||||
reload();
|
||||
//Give it a little time to shutdown properly and recover.
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
|
||||
Collection<Tracker> reloadedTrackers = getTrackers();
|
||||
assertEquals("After a reload the number of trackers should be the same", numOfTrackers, getTrackers().size());
|
||||
assertEquals("After a reload the number of jobs should be the same", jobs, getJobsCount());
|
||||
|
||||
trackers.forEach(tracker -> assertFalse("The reloaded trackers should be different.", reloadedTrackers.contains(tracker)));
|
||||
}
|
||||
|
||||
private int getJobsCount() throws SchedulerException
|
||||
{
|
||||
return admin.getScheduler().getJobsCount();
|
||||
}
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.solr.tracker;
|
||||
|
||||
import org.alfresco.solr.InformationServer;
|
||||
import org.alfresco.solr.TrackerState;
|
||||
import org.alfresco.solr.client.SOLRAPIClient;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class ActivatableTrackerTest
|
||||
{
|
||||
private static class TestActivatableTracker extends ActivatableTracker {
|
||||
|
||||
protected TestActivatableTracker(Properties properties, TrackerState state) {
|
||||
super(properties, mock(SOLRAPIClient.class), "thisIsTheCoreName", mock(InformationServer.class), Type.NODE_STATE_PUBLISHER);
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTrack(String iterationId) {
|
||||
// Nothing to be done here, it's a fake implementation.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setPersistentIndexingStateAcrossReloadsTo(boolean enabled) {
|
||||
// Nothing to be done here, it's a fake implementation.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maintenance() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMaintenance() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Semaphore getWriteLock() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Semaphore getRunLock() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ActivatableTracker tracker;
|
||||
private TrackerState state;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
state = new TrackerState();
|
||||
state.setRunning(false);
|
||||
tracker = spy(new TestActivatableTracker(new Properties(), state));
|
||||
assertFalse(tracker.state.isRunning());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enabledShouldBeTheDefaultState()
|
||||
{
|
||||
assertTrue(tracker.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trackersCanBeExplicitlyDisabled()
|
||||
{
|
||||
assertTrue(tracker.isEnabled());
|
||||
|
||||
tracker.disable();
|
||||
|
||||
assertFalse(tracker.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disablingATracker_shouldClearTheScheduledMaintenanceWork()
|
||||
{
|
||||
assertTrue(tracker.isEnabled());
|
||||
|
||||
tracker.disable();
|
||||
|
||||
assertFalse(tracker.isEnabled());
|
||||
verify(tracker).clearScheduledMaintenanceWork();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableIsIdempotent()
|
||||
{
|
||||
assertTrue(tracker.isEnabled());
|
||||
|
||||
tracker.enable();
|
||||
|
||||
assertTrue(tracker.isEnabled());
|
||||
|
||||
tracker.disable();
|
||||
|
||||
assertFalse(tracker.isEnabled());
|
||||
|
||||
tracker.enable();
|
||||
assertTrue(tracker.isEnabled());
|
||||
|
||||
tracker.enable();
|
||||
assertTrue(tracker.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableIsIdempotent()
|
||||
{
|
||||
assertTrue(tracker.isEnabled());
|
||||
|
||||
tracker.disable();
|
||||
assertFalse(tracker.isEnabled());
|
||||
|
||||
tracker.disable();
|
||||
assertFalse(tracker.isEnabled());
|
||||
|
||||
tracker.enable();
|
||||
assertTrue(tracker.isEnabled());
|
||||
|
||||
tracker.disable();
|
||||
assertFalse(tracker.isEnabled());
|
||||
|
||||
tracker.disable();
|
||||
assertFalse(tracker.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableIndexingOnRunningTracker_shouldDisableTheTrackerAnSetItInRollbackMode()
|
||||
{
|
||||
state.setRunning(true);
|
||||
assertTrue(tracker.isEnabled());
|
||||
assertTrue(tracker.state.isRunning());
|
||||
|
||||
tracker.disable();
|
||||
|
||||
state.setRunning(true);
|
||||
assertTrue(tracker.state.isRunning());
|
||||
|
||||
assertFalse(tracker.isEnabled());
|
||||
verify(tracker).setRollback(true, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertActivatableTrackersList() {
|
||||
Stream.of(MetadataTracker.class, AclTracker.class, ContentTracker.class, CascadeTracker.class)
|
||||
.forEach(clazz -> assertTrue("Warning: " + clazz + " is supposed to be enabled/disabled", ActivatableTracker.class.isAssignableFrom(clazz)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertAlwaysActivatedTrackersList() {
|
||||
Stream.of(CommitTracker.class, ModelTracker.class)
|
||||
.forEach(clazz -> assertFalse("Warning: " + clazz + " is not supposed to be enabled/disabled", ActivatableTracker.class.isAssignableFrom(clazz)));
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -71,7 +71,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A partial state of {@link org.alfresco.solr.TrackerState} is exposed through two interfaces: AdminHandler.SUMMARY and
|
||||
* {@link MetadataTracker#getShardState}.
|
||||
* {@link ShardStatePublisher#getShardState}.
|
||||
*
|
||||
* This test makes sure that state is consistent across the two mentioned approaches. That is, properties returned by the
|
||||
* Core SUMMARY must have the same value of the same properties in the ShardState.
|
||||
@@ -109,10 +109,10 @@ public class AlfrescoSolrTrackerStateIT extends AbstractAlfrescoSolrIT
|
||||
public void shardStateMustBeConsistentWithCoreSummaryStats() throws Exception {
|
||||
SolrCore core = getCore();
|
||||
|
||||
MetadataTracker tracker =
|
||||
ShardStatePublisher tracker =
|
||||
of(coreAdminHandler(core))
|
||||
.map(AlfrescoCoreAdminHandler::getTrackerRegistry)
|
||||
.map(registry -> registry.getTrackerForCore(core.getName(), MetadataTracker.class))
|
||||
.map(registry -> registry.getTrackerForCore(core.getName(), ShardStatePublisher.class))
|
||||
.orElseThrow(() -> new IllegalStateException("Cannot retrieve the Metadata tracker on this test core."));
|
||||
|
||||
// 1. First consistency check: ShardState must have the same values of CoreAdmin.SUMMARY report
|
||||
|
||||
+3
-3
@@ -67,7 +67,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A partial state of {@link org.alfresco.solr.TrackerState} is exposed through two interfaces: AdminHandler.SUMMARY and
|
||||
* {@link MetadataTracker#getShardState}.
|
||||
* {@link ShardStatePublisher#getShardState}.
|
||||
* This test makes sure that state is consistent across the two mentioned approaches. That is, properties returned by the
|
||||
* Core SUMMARY must have the same value of the same properties in the ShardState.
|
||||
*
|
||||
@@ -99,10 +99,10 @@ public class DistributedAlfrescoSolrTrackerStateIT extends AbstractAlfrescoDistr
|
||||
putHandleDefaults();
|
||||
|
||||
getCores(solrShards).forEach(core -> {
|
||||
MetadataTracker tracker =
|
||||
ShardStatePublisher tracker =
|
||||
of(coreAdminHandler(core))
|
||||
.map(AlfrescoCoreAdminHandler::getTrackerRegistry)
|
||||
.map(registry -> registry.getTrackerForCore(core.getName(), MetadataTracker.class))
|
||||
.map(registry -> registry.getTrackerForCore(core.getName(), ShardStatePublisher.class))
|
||||
.orElseThrow(() -> new IllegalStateException("Cannot retrieve the Metadata tracker on this test core."));
|
||||
|
||||
// 1. First consistency check: ShardState must have the same values of CoreAdmin.SUMMARY report
|
||||
|
||||
+71
-60
@@ -1,43 +1,43 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
package org.alfresco.solr;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
package org.alfresco.solr;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class was moved from org.alfresco.solr.tracker.CoreTracker
|
||||
* The data in this class is relevant for a particular Solr index.
|
||||
*/
|
||||
public class TrackerState
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(TrackerState.class);
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(TrackerState.class);
|
||||
|
||||
private volatile long lastChangeSetIdOnServer;
|
||||
|
||||
@@ -61,22 +61,24 @@ public class TrackerState
|
||||
|
||||
private volatile boolean running = false;
|
||||
|
||||
private boolean enabled;
|
||||
|
||||
private volatile boolean checkedFirstTransactionTime = false;
|
||||
private volatile boolean checkedFirstAclTransactionTime = false;
|
||||
private volatile boolean checkedLastAclTransactionTime = false;
|
||||
private volatile boolean checkedLastTransactionTime = false;
|
||||
|
||||
private volatile boolean check = false;
|
||||
private volatile boolean check = false;
|
||||
// Handle Thread Safe operations
|
||||
private volatile AtomicInteger trackerCycles = new AtomicInteger(0);
|
||||
|
||||
private volatile AtomicInteger trackerCycles = new AtomicInteger(0);
|
||||
|
||||
private long timeToStopIndexing;
|
||||
|
||||
private long lastGoodChangeSetCommitTimeInIndex;
|
||||
|
||||
private long lastGoodTxCommitTimeInIndex;
|
||||
|
||||
private long timeBeforeWhichThereCanBeNoHoles;
|
||||
private long timeBeforeWhichThereCanBeNoHoles;
|
||||
private volatile long lastStartTime = 0;
|
||||
|
||||
public long getLastChangeSetIdOnServer()
|
||||
@@ -237,19 +239,19 @@ public class TrackerState
|
||||
public void setLastGoodTxCommitTimeInIndex(long lastGoodTxCommitTimeInIndex)
|
||||
{
|
||||
this.lastGoodTxCommitTimeInIndex = lastGoodTxCommitTimeInIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public int getTrackerCycles()
|
||||
{
|
||||
return this.trackerCycles.get();
|
||||
}
|
||||
|
||||
public synchronized void incrementTrackerCycles()
|
||||
{
|
||||
log.debug("incrementTrackerCycles from :" + trackerCycles);
|
||||
this.trackerCycles.incrementAndGet();
|
||||
log.debug("incremented TrackerCycles to :" + trackerCycles);
|
||||
}
|
||||
public int getTrackerCycles()
|
||||
{
|
||||
return this.trackerCycles.get();
|
||||
}
|
||||
|
||||
public synchronized void incrementTrackerCycles()
|
||||
{
|
||||
log.debug("incrementTrackerCycles from :" + trackerCycles);
|
||||
this.trackerCycles.incrementAndGet();
|
||||
log.debug("incremented TrackerCycles to :" + trackerCycles);
|
||||
}
|
||||
|
||||
public long getTimeBeforeWhichThereCanBeNoHoles()
|
||||
{
|
||||
@@ -261,6 +263,14 @@ public class TrackerState
|
||||
this.timeBeforeWhichThereCanBeNoHoles = timeBeforeWhichThereCanBeNoHoles;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
@@ -284,10 +294,11 @@ public class TrackerState
|
||||
+ ", checkedLastTransactionTime=" + this.checkedLastTransactionTime
|
||||
+ ", checkedLastAclTransactionTime=" + this.checkedLastAclTransactionTime
|
||||
+ ", check=" + check
|
||||
+ ", enabled=" + enabled
|
||||
+ ", timeToStopIndexing=" + timeToStopIndexing
|
||||
+ ", lastGoodChangeSetCommitTimeInIndex=" + lastGoodChangeSetCommitTimeInIndex
|
||||
+ ", lastGoodTxCommitTimeInIndex=" + lastGoodTxCommitTimeInIndex
|
||||
+ ", timeBeforeWhichThereCanBeNoHoles=" + timeBeforeWhichThereCanBeNoHoles
|
||||
+ ", timeBeforeWhichThereCanBeNoHoles=" + timeBeforeWhichThereCanBeNoHoles
|
||||
+ ",trackerCycles= " + trackerCycles + " ]";
|
||||
}
|
||||
|
||||
@@ -319,15 +330,15 @@ public class TrackerState
|
||||
public void setCheckedLastAclTransactionTime(boolean checkedLastAclTransactionTime)
|
||||
{
|
||||
this.checkedLastAclTransactionTime = checkedLastAclTransactionTime;
|
||||
}
|
||||
}
|
||||
|
||||
public long getLastStartTime()
|
||||
{
|
||||
return this.lastStartTime;
|
||||
}
|
||||
|
||||
public void setLastStartTime(long lastStartTime)
|
||||
{
|
||||
this.lastStartTime = lastStartTime;
|
||||
public long getLastStartTime()
|
||||
{
|
||||
return this.lastStartTime;
|
||||
}
|
||||
|
||||
public void setLastStartTime(long lastStartTime)
|
||||
{
|
||||
this.lastStartTime = lastStartTime;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user