Merge pull request #1476 from Alfresco/fix/MNT-22878_Search_Services_2.x_does_not_return_DB_TX_status_and_DB_TX_elements_in_nodeReport_of_SOLR_Admin_REST_API

[MNT-22878] Amend NodeReport admin action on master/standalone instances
This commit is contained in:
Andrea Gazzarini
2022-08-09 19:13:19 +02:00
committed by GitHub
5 changed files with 167 additions and 47 deletions

View File

@@ -32,8 +32,7 @@ import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.solr.adapters.IOpenBitSet; import org.alfresco.solr.adapters.IOpenBitSet;
import org.alfresco.solr.client.SOLRAPIClientFactory; import org.alfresco.solr.client.SOLRAPIClientFactory;
import org.alfresco.solr.config.ConfigUtil; import org.alfresco.solr.config.ConfigUtil;
import org.alfresco.solr.io.interceptor.SharedSecretRequestInterceptor; import org.alfresco.solr.tracker.AbstractTracker;
import org.alfresco.solr.security.SecretSharedPropertyCollector;
import org.alfresco.solr.tracker.AclTracker; import org.alfresco.solr.tracker.AclTracker;
import org.alfresco.solr.tracker.ActivatableTracker; import org.alfresco.solr.tracker.ActivatableTracker;
import org.alfresco.solr.tracker.ShardStatePublisher; import org.alfresco.solr.tracker.ShardStatePublisher;
@@ -48,7 +47,6 @@ import org.alfresco.solr.utils.Utils;
import org.alfresco.util.Pair; import org.alfresco.util.Pair;
import org.alfresco.util.shard.ExplicitShardingPolicy; import org.alfresco.util.shard.ExplicitShardingPolicy;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.http.HttpRequestInterceptor;
import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CoreAdminParams; import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.params.SolrParams;
@@ -1027,12 +1025,12 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
coreNames().stream() coreNames().stream()
.filter(coreName -> requestedCoreName == null || coreName.equals(requestedCoreName)) .filter(coreName -> requestedCoreName == null || coreName.equals(requestedCoreName))
.filter(trackerRegistry::hasTrackersForCore) .filter(trackerRegistry::hasTrackersForCore)
.map(coreName -> new Pair<>(coreName, coreStatePublisher(coreName))) .map(coreName -> new Pair<>(coreName, nodeStatusChecker(coreName)))
.filter(coreNameAndPublisher -> coreNameAndPublisher.getSecond() != null) .filter(coreNameAndNodeChecker -> coreNameAndNodeChecker.getSecond() != null)
.forEach(coreNameAndPublisher -> .forEach(coreNameAndNodeChecker ->
report.add( report.add(
coreNameAndPublisher.getFirst(), coreNameAndNodeChecker.getFirst(),
buildNodeReport(coreNameAndPublisher.getSecond(), nodeid))); buildNodeReport(coreNameAndNodeChecker.getSecond(), nodeid)));
return report; return report;
} }
@@ -2102,14 +2100,19 @@ public class AlfrescoCoreAdminHandler extends CoreAdminHandler
} }
/** /**
* Returns, for the given core, the component which is in charge to publish the core state. * Returns, for the given core, the tracker which is in charge to check the nodes status.
* Depending on the shard nature, master/standalone or slave, the tracker instance could be different.
* In addition, also the information that a given tracker returns about a given node, could differ (e.g.
* minimal in case of a slave node, detailed for master or standalone nodes).
* *
* @param coreName the owning core name. * @param coreName the owning core name.
* @return the component which is in charge to publish the core state. * @return the component which is in charge to check the nodes status.
*/ */
ShardStatePublisher coreStatePublisher(String coreName) AbstractTracker nodeStatusChecker(String coreName)
{ {
return trackerRegistry.getTrackerForCore(coreName, ShardStatePublisher.class); return isMasterOrStandalone(coreName)
? trackerRegistry.getTrackerForCore(coreName, MetadataTracker.class)
: trackerRegistry.getTrackerForCore(coreName, ShardStatePublisher.class);
} }
/** /**

View File

@@ -105,34 +105,24 @@ class HandlerReportHelper
} }
} }
static NamedList<Object> buildNodeReport(MetadataTracker tracker, Node node) throws JSONException static NamedList<Object> buildNodeReport(AbstractTracker tracker, Node node) throws JSONException
{ {
NodeReport nodeReport = tracker.checkNode(node); return buildNodeReport(tracker, node.getId());
NamedList<Object> nr = new SimpleOrderedMap<>();
nr.add("Node DBID", nodeReport.getDbid());
nr.add("DB TX", nodeReport.getDbTx());
nr.add("DB TX status", nodeReport.getDbNodeStatus().toString());
if (nodeReport.getIndexLeafDoc() != null)
{
nr.add("Leaf tx in Index", nodeReport.getIndexLeafTx());
}
if (nodeReport.getIndexAuxDoc() != null)
{
nr.add("Aux tx in Index", nodeReport.getIndexAuxTx());
}
nr.add("Indexed Node Doc Count", nodeReport.getIndexedNodeDocCount());
return nr;
} }
static NamedList<Object> buildNodeReport(ShardStatePublisher publisher, Long dbid) throws JSONException static NamedList<Object> buildNodeReport(AbstractTracker tracker, long dbid) throws JSONException
{ {
NodeReport nodeReport = publisher.checkNode(dbid); NodeReport nodeReport = tracker.checkNode(dbid);
NamedList<Object> payload = new SimpleOrderedMap<>(); NamedList<Object> payload = new SimpleOrderedMap<>();
payload.add("Node DBID", nodeReport.getDbid()); payload.add("Node DBID", nodeReport.getDbid());
if (publisher.isOnMasterOrStandalone()) boolean isOnMasterOrStandaloneMode =
tracker instanceof MetadataTracker
|| (tracker instanceof ShardStatePublisher
&& ((ShardStatePublisher)tracker).isOnMasterOrStandalone());
if (isOnMasterOrStandaloneMode)
{ {
ofNullable(nodeReport.getDbTx()).ifPresent(value -> payload.add("DB TX", value)); ofNullable(nodeReport.getDbTx()).ifPresent(value -> payload.add("DB TX", value));
ofNullable(nodeReport.getDbNodeStatus()).map(Object::toString).ifPresent(value -> payload.add("DB TX Status", value)); ofNullable(nodeReport.getDbNodeStatus()).map(Object::toString).ifPresent(value -> payload.add("DB TX Status", value));

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Search Services * Alfresco Search Services
* %% * %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited * Copyright (C) 2005 - 2022 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -26,14 +26,37 @@
package org.alfresco.solr; package org.alfresco.solr;
import org.alfresco.repo.search.adaptor.QueryConstants;
import org.alfresco.solr.client.Node;
import org.alfresco.solr.client.SOLRAPIQueueClient;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.LegacyNumericRangeQuery;
import org.apache.lucene.search.TermQuery;
import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException;
import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.params.CoreAdminParams; import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.handler.admin.CoreAdminHandler; import org.apache.solr.handler.admin.CoreAdminHandler;
import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.response.SolrQueryResponse;
import org.junit.After;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static java.util.Optional.of;
import static org.alfresco.solr.AlfrescoSolrUtils.MAX_WAIT_TIME;
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;
@SolrTestCaseJ4.SuppressSSL @SolrTestCaseJ4.SuppressSSL
public class AdminHandlerIT extends AbstractAlfrescoSolrIT public class AdminHandlerIT extends AbstractAlfrescoSolrIT
{ {
@@ -46,6 +69,21 @@ public class AdminHandlerIT extends AbstractAlfrescoSolrIT
admin = getMultiCoreHandler(); admin = 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();
SOLRAPIQueueClient.NODE_CONTENT_MAP.clear();
clearIndex();
assertU(commit());
}
@Test(expected = SolrException.class) @Test(expected = SolrException.class)
public void testUnhandled() throws Exception public void testUnhandled() throws Exception
{ {
@@ -63,6 +101,73 @@ public class AdminHandlerIT extends AbstractAlfrescoSolrIT
requestAction("removeCore"); requestAction("removeCore");
} }
@Test
public void nodeReportOnMasterOrStandaloneContainsTxInfo() throws Exception
{
var aclChangeSet = getAclChangeSet(1);
var acl = getAcl(aclChangeSet);
indexAclChangeSet(aclChangeSet,
singletonList(acl),
singletonList(getAclReaders(aclChangeSet, acl, singletonList("joel"), singletonList("phil"), null)));
var waitForQuery =
new BooleanQuery.Builder()
.add(new BooleanClause(new TermQuery(new Term(QueryConstants.FIELD_SOLR4_ID, "TRACKER!STATE!ACLTX")), BooleanClause.Occur.MUST))
.add(new BooleanClause(LegacyNumericRangeQuery.newLongRange(
QueryConstants.FIELD_S_ACLTXID, aclChangeSet.getId(),
aclChangeSet.getId() + 1,
true,
false), BooleanClause.Occur.MUST))
.build();
waitForDocCount(waitForQuery, 1, MAX_WAIT_TIME);
var txn = getTransaction(0, 4);
var node = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED);
var nodeMetadata = getNodeMetaData(node, txn, acl, "mike", null, false);
indexTransaction(txn, singletonList(node), singletonList(nodeMetadata));
makeSureTransactionHasBeenIndexed(txn.getId());
var request = req(CoreAdminParams.ACTION, "nodereport", CoreAdminParams.NAME, getCore().getName());
var params =
new ModifiableSolrParams(request.getParams())
.set("nodeid", Long.toString(node.getId()));
request.setParams(params);
var response = new SolrQueryResponse();
admin.handleRequestBody(request, response);
var data = response.getValues();
var report =
ofNullable(data.get("report"))
.map(NamedList.class::cast)
.orElseThrow(() -> new AssertionError("'report' section not in response. Response was " + data));
var collection =
of(report.get("collection1"))
.map(NamedList.class::cast)
.orElseThrow(() -> new AssertionError("'collection' section not in response. Response was " + data));
of(collection.get("Node DBID"))
.map(Long.class::cast)
.filter(dbid -> node.getId() == dbid)
.orElseThrow(() -> new AssertionError("'Node DBID' mismatch or not found. Expected '" + node.getId() + "'Response was " + data));
of(collection.get("DB TX"))
.map(Long.class::cast)
.filter(dbtx -> txn.getId() == dbtx)
.orElseThrow(() -> new AssertionError("'DB TX' mismatch or not found. Expected '" + txn.getId() + "' Response was " + data));
of(collection.get("DB TX Status"))
.map(String.class::cast)
.filter("UPDATED"::equals)
.orElseThrow(() -> new AssertionError("'DB TX' mismatch or not found. Expected 'UPDATED', Response was " + data));
}
@Test @Test
public void testhandledReports() throws Exception public void testhandledReports() throws Exception
{ {
@@ -77,4 +182,14 @@ public class AdminHandlerIT extends AbstractAlfrescoSolrIT
CoreAdminParams.NAME, getCore().getName()), CoreAdminParams.NAME, getCore().getName()),
new SolrQueryResponse()); new SolrQueryResponse());
} }
private void makeSureTransactionHasBeenIndexed(long transactionId) throws Exception
{
//Check for the TXN state stamp.
BooleanQuery.Builder builder = new BooleanQuery.Builder();
builder.add(new BooleanClause(new TermQuery(new Term(QueryConstants.FIELD_SOLR4_ID, "TRACKER!STATE!TX")), BooleanClause.Occur.MUST));
builder.add(new BooleanClause(LegacyNumericRangeQuery.newLongRange(QueryConstants.FIELD_S_TXID, transactionId, transactionId + 1, true, false), BooleanClause.Occur.MUST));
BooleanQuery waitForQuery = builder.build();
waitForDocCount(waitForQuery, 1, MAX_WAIT_TIME);
}
} }

View File

@@ -226,7 +226,7 @@ public class AlfrescoCoreAdminHandlerIT
when(trackerRegistry.getTrackerForCore(anyString(), eq(ShardStatePublisher.class))) when(trackerRegistry.getTrackerForCore(anyString(), eq(ShardStatePublisher.class)))
.thenReturn(coreStatePublisher); .thenReturn(coreStatePublisher);
assertSame(coreStatePublisher, alfrescoCoreAdminHandler.coreStatePublisher("ThisIsTheCoreName")); assertSame(coreStatePublisher, alfrescoCoreAdminHandler.nodeStatusChecker("ThisIsTheCoreName"));
} }
@Test @Test
@@ -236,7 +236,7 @@ public class AlfrescoCoreAdminHandlerIT
when(trackerRegistry.getTrackerForCore(anyString(), eq(ShardStatePublisher.class))).thenReturn(coreStateTracker); when(trackerRegistry.getTrackerForCore(anyString(), eq(ShardStatePublisher.class))).thenReturn(coreStateTracker);
assertSame(coreStateTracker, alfrescoCoreAdminHandler.coreStatePublisher("ThisIsTheCoreName")); assertSame(coreStateTracker, alfrescoCoreAdminHandler.nodeStatusChecker("ThisIsTheCoreName"));
} }
@Test @Test

View File

@@ -27,6 +27,7 @@
package org.alfresco.solr.client; package org.alfresco.solr.client;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
@@ -39,7 +40,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import org.alfresco.httpclient.Response; import org.alfresco.httpclient.Response;
import org.alfresco.repo.dictionary.NamespaceDAO; import org.alfresco.repo.dictionary.NamespaceDAO;
@@ -96,7 +96,7 @@ public class SOLRAPIQueueClient extends SOLRAPIClient
.peek(aclChangeSet -> { .peek(aclChangeSet -> {
maxTime.set(Math.max(aclChangeSet.getCommitTimeMs(), maxTime.get())); maxTime.set(Math.max(aclChangeSet.getCommitTimeMs(), maxTime.get()));
maxId.set(Math.max(aclChangeSet.getId(), maxId.get()));}) maxId.set(Math.max(aclChangeSet.getId(), maxId.get()));})
.collect(Collectors.toList()), maxTime.get(), maxId.get()); .collect(toList()), maxTime.get(), maxId.get());
} }
return new AclChangeSets( return new AclChangeSets(
@@ -106,7 +106,7 @@ public class SOLRAPIQueueClient extends SOLRAPIClient
.peek(aclChangeSet -> { .peek(aclChangeSet -> {
maxTime.set(Math.max(aclChangeSet.getCommitTimeMs(), maxTime.get())); maxTime.set(Math.max(aclChangeSet.getCommitTimeMs(), maxTime.get()));
maxId.set(Math.max(aclChangeSet.getId(), maxId.get()));}) maxId.set(Math.max(aclChangeSet.getId(), maxId.get()));})
.collect(Collectors.toList()), maxTime.get(), maxId.get()); .collect(toList()), maxTime.get(), maxId.get());
} }
/** /**
@@ -129,7 +129,7 @@ public class SOLRAPIQueueClient extends SOLRAPIClient
.map(AclChangeSet::getId) .map(AclChangeSet::getId)
.map(ACL_MAP::get) .map(ACL_MAP::get)
.flatMap(Collection::stream) .flatMap(Collection::stream)
.collect(Collectors.toList()); .collect(toList());
} }
/** /**
@@ -148,7 +148,7 @@ public class SOLRAPIQueueClient extends SOLRAPIClient
return acls.stream() return acls.stream()
.map(Acl::getId) .map(Acl::getId)
.map(ACL_READERS_MAP::get) .map(ACL_READERS_MAP::get)
.collect(Collectors.toList()); .collect(toList());
} }
@@ -191,7 +191,7 @@ public class SOLRAPIQueueClient extends SOLRAPIClient
.peek(txn -> { .peek(txn -> {
maxTime.set(Math.max(txn.getCommitTimeMs(), maxTime.get())); maxTime.set(Math.max(txn.getCommitTimeMs(), maxTime.get()));
maxId.set(Math.max(txn.getId(), maxId.get()));}) maxId.set(Math.max(txn.getId(), maxId.get()));})
.collect(Collectors.toList()), maxTime.get(), maxId.get()); .collect(toList()), maxTime.get(), maxId.get());
} }
return new Transactions( return new Transactions(
@@ -201,7 +201,7 @@ public class SOLRAPIQueueClient extends SOLRAPIClient
.peek(txn -> { .peek(txn -> {
maxTime.set(Math.max(txn.getCommitTimeMs(), maxTime.get())); maxTime.set(Math.max(txn.getCommitTimeMs(), maxTime.get()));
maxId.set(Math.max(txn.getId(), maxId.get()));}) maxId.set(Math.max(txn.getId(), maxId.get()));})
.collect(Collectors.toList()), maxTime.get(), maxId.get()); .collect(toList()), maxTime.get(), maxId.get());
} }
public List<Node> getNodes(GetNodesParameters parameters, int maxResults) throws IOException, JSONException public List<Node> getNodes(GetNodesParameters parameters, int maxResults) throws IOException, JSONException
@@ -211,10 +211,22 @@ public class SOLRAPIQueueClient extends SOLRAPIClient
throw new ConnectException("THROWING EXCEPTION, better be ready!"); throw new ConnectException("THROWING EXCEPTION, better be ready!");
} }
return parameters.getTransactionIds().stream() return parameters.getTransactionIds() != null
.map(NODE_MAP::get) ? parameters.getTransactionIds().stream()
.flatMap(Collection::stream) .map(NODE_MAP::get)
.collect(Collectors.toList()); .flatMap(Collection::stream)
.collect(toList())
: NODE_MAP.values()
.stream()
.flatMap(Collection::stream)
.filter(node -> {
var fromNodeId = parameters.getFromNodeId();
var toNodeId = parameters.getToNodeId();
return (fromNodeId == null || node.getId() >= fromNodeId)
&&
(toNodeId == null || node.getId() <= toNodeId);})
.collect(toList());
} }
@Override @Override
@@ -230,7 +242,7 @@ public class SOLRAPIQueueClient extends SOLRAPIClient
identifiers.stream() identifiers.stream()
.map(NODE_META_DATA_MAP::get) .map(NODE_META_DATA_MAP::get)
.map(metadata -> getOnlyRequestedMetadata(metadata, params)) .map(metadata -> getOnlyRequestedMetadata(metadata, params))
.collect(Collectors.toList())) .collect(toList()))
.orElseGet(() -> .orElseGet(() ->
ofNullable(params.getFromNodeId()) ofNullable(params.getFromNodeId())
.map(NODE_META_DATA_MAP::get) .map(NODE_META_DATA_MAP::get)