Skip to content

Commit

Permalink
SOLR-15694, SOLR-15715: Node roles and dedicated query coordinator nodes
Browse files Browse the repository at this point in the history
Co-authored-by: Noble Paul <noble@apache.org>
  • Loading branch information
Ishan Chattopadhyaya and noblepaul committed Oct 23, 2023
1 parent 962c926 commit 2088d74
Show file tree
Hide file tree
Showing 44 changed files with 2,147 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public int distributedProcess(ResponseBuilder rb) throws IOException {

// Send out a request to each shard and merge the responses into our AnalyticsRequestManager
reqManager.shardStream.sendRequests(rb.req.getCore().getCoreDescriptor().getCollectionName(),
rb.req.getCore().getCoreContainer().getZkController().getZkServerAddress());
rb.req.getCoreContainer().getZkController().getZkServerAddress());

reqManager.sendShards = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public void setContext(ResultContext context) {
}
leafContexts = searcher.getTopReaderContext().leaves();
if (threadManager != null) {
threadManager.setExecutor(context.getRequest().getCore().getCoreContainer().getUpdateShardHandler().getUpdateExecutor());
threadManager.setExecutor(context.getRequest().getCoreContainer().getUpdateShardHandler().getUpdateExecutor());
}

rerankingQueriesFromContext = SolrQueryRequestContextUtils.getScoringQueries(req);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public LTRQParser(String qstr, SolrParams localParams, SolrParams params,
@Override
public Query parse() throws SyntaxError {
if (threadManager != null) {
threadManager.setExecutor(req.getCore().getCoreContainer().getUpdateShardHandler().getUpdateExecutor());
threadManager.setExecutor(req.getCoreContainer().getUpdateShardHandler().getUpdateExecutor());
}
// ReRanking Model
final String[] modelNames = localParams.getParams(LTRQParserPlugin.MODEL);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.solr.api;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrCore;
import org.apache.solr.servlet.CoordinatorHttpSolrCall;
import org.apache.solr.servlet.SolrDispatchFilter;

public class CoordinatorV2HttpSolrCall extends V2HttpCall {
private String collectionName;
CoordinatorHttpSolrCall.Factory factory;

public CoordinatorV2HttpSolrCall(
CoordinatorHttpSolrCall.Factory factory,
SolrDispatchFilter solrDispatchFilter,
CoreContainer cc,
HttpServletRequest request,
HttpServletResponse response,
boolean retry) {
super(solrDispatchFilter, cc, request, response, retry);
this.factory = factory;
}

@Override
protected SolrCore getCoreByCollection(String collectionName, boolean isPreferLeader) {
this.collectionName = collectionName;
SolrCore core = super.getCoreByCollection(collectionName, isPreferLeader);
if (core != null) return core;
if (!path.endsWith("/select")) return null;
return CoordinatorHttpSolrCall.getCore(factory, this, collectionName, isPreferLeader);
}

@Override
protected void init() throws Exception {
super.init();
if (action == SolrDispatchFilter.Action.PROCESS && core != null) {
solrReq = CoordinatorHttpSolrCall.wrappedReq(solrReq, collectionName, this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
Expand Down Expand Up @@ -55,6 +56,8 @@
import org.apache.solr.common.params.CollectionAdminParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
import org.apache.solr.core.NodeRoles;
import org.apache.solr.handler.ClusterAPI;
import org.apache.solr.util.NumberUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
Expand Down Expand Up @@ -233,7 +236,12 @@ private static boolean existCoreName(String coreName, Slice slice) {
return false;
}

public static List<String> getLiveOrLiveAndCreateNodeSetList(final Set<String> liveNodes, final ZkNodeProps message, final Random random) {
public static List<String> getLiveOrLiveAndCreateNodeSetList(
final Set<String> liveNodes,
final ZkNodeProps message,
final Random random,
DistribStateManager zk) {

List<String> nodeList;
final String createNodeSetStr = message.getStr(CREATE_NODE_SET);
final List<String> createNodeList = (createNodeSetStr == null) ? null :
Expand All @@ -248,13 +256,28 @@ public static List<String> getLiveOrLiveAndCreateNodeSetList(final Set<String> l
Collections.shuffle(nodeList, random);
}
} else {
nodeList = new ArrayList<>(liveNodes);
nodeList = new ArrayList<>(filterNonDataNodes(zk, liveNodes));
Collections.shuffle(nodeList, random);
}

return nodeList;
}

public static Collection<String> filterNonDataNodes(
DistribStateManager zk, Collection<String> liveNodes) {
try {
List<String> noData = ClusterAPI.getNodesByRole(NodeRoles.Role.DATA, NodeRoles.MODE_OFF, zk);
if (noData.isEmpty()) {
return liveNodes;
} else {
liveNodes = new HashSet<>(liveNodes);
liveNodes.removeAll(noData);
return liveNodes;
}
} catch (Exception e) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR, "Error fetching roles from Zookeeper", e);
}
}
/**
* <b>Note:</b> where possible, the {@link #usePolicyFramework(DocCollection, SolrCloudManager)} method should
* be used instead of this method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public RoutedAliasTypes getRoutedAliasType() {
@Override
public void validateRouteValue(AddUpdateCommand cmd) throws SolrException {
if (this.aliases == null) {
updateParsedCollectionAliases(cmd.getReq().getCore().getCoreContainer().getZkController().zkStateReader, false);
updateParsedCollectionAliases(cmd.getReq().getCoreContainer().getZkController().zkStateReader, false);
}

Object fieldValue = cmd.getSolrInputDocument().getFieldValue(getRouteField());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,12 @@ public static List<ReplicaPosition> buildReplicaPositions(SolrCloudManager cloud
// but (for now) require that each core goes on a distinct node.

List<ReplicaPosition> replicaPositions;
List<String> nodeList = Assign.getLiveOrLiveAndCreateNodeSetList(clusterState.getLiveNodes(), message, OverseerCollectionMessageHandler.RANDOM);
List<String> nodeList =
Assign.getLiveOrLiveAndCreateNodeSetList(
clusterState.getLiveNodes(),
message,
OverseerCollectionMessageHandler.RANDOM,
cloudManager.getDistribStateManager());
if (nodeList.isEmpty()) {
log.warn("It is unusual to create a collection ({}) without cores.", collectionName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,12 @@ private RestoreContext(ZkNodeProps message, OverseerCollectionMessageHandler ocm
this.backupCollectionState = this.backupManager.readCollectionState(this.backupCollection);

this.shardHandler = ocmh.shardHandlerFactory.getShardHandler();
this.nodeList = Assign.getLiveOrLiveAndCreateNodeSetList(
zkStateReader.getClusterState().getLiveNodes(), message, OverseerCollectionMessageHandler.RANDOM);
this.nodeList =
Assign.getLiveOrLiveAndCreateNodeSetList(
zkStateReader.getClusterState().getLiveNodes(),
message,
OverseerCollectionMessageHandler.RANDOM,
container.getZkController().getSolrCloudManager().getDistribStateManager());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ private String createAllRequiredCollections(AddUpdateCommand cmd, CandidateColle

SolrQueryRequest req = cmd.getReq();
SolrCore core = req.getCore();
CoreContainer coreContainer = core.getCoreContainer();
CoreContainer coreContainer = req.getCoreContainer();
do {
switch (targetCollectionDesc.getCreationType()) {
case NONE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ public void validateRouteValue(AddUpdateCommand cmd) throws SolrException {
} catch (DateTimeParseException e) {
startTime = DateMathParser.parseMath(new Date(), start).toInstant();
SolrCore core = cmd.getReq().getCore();
ZkStateReader zkStateReader = core.getCoreContainer().getZkController().zkStateReader;
ZkStateReader zkStateReader = cmd.getReq().getCoreContainer().getZkController().zkStateReader;
Aliases aliases = zkStateReader.getAliases();
Map<String, String> props = new HashMap<>(aliases.getCollectionAliasProperties(aliasName));
start = DateTimeFormatter.ISO_INSTANT.format(startTime);
Expand Down
1 change: 1 addition & 0 deletions solr/core/src/java/org/apache/solr/core/CoreContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ public CoreLoadFailure(CoreDescriptor cd, Exception loadFailure) {
protected volatile AutoscalingHistoryHandler autoscalingHistoryHandler;

private volatile SolrClientCache solrClientCache;
public final NodeRoles nodeRoles = new NodeRoles(System.getProperty(NodeRoles.NODE_ROLES_PROP));

private final ObjectCache objectCache = new ObjectCache();

Expand Down
152 changes: 152 additions & 0 deletions solr/core/src/java/org/apache/solr/core/NodeRoles.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.core;

import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.ImmutableSet;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.StringUtils;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.util.StrUtils;

public class NodeRoles {
public static final String NODE_ROLES_PROP = "solr.node.roles";

/** Roles to be assumed on nodes that don't have roles specified for them at startup */
public static final String DEFAULT_ROLES_STRING = "data:on,overseer:allowed";

// Map of roles to mode that are applicable for this node.
private Map<Role, String> nodeRoles;

public NodeRoles(String rolesString) {
Map<Role, String> roles = new EnumMap<>(Role.class);
if (StringUtils.isEmpty(rolesString)) {
rolesString = DEFAULT_ROLES_STRING;
}
List<String> rolesList = StrUtils.splitSmart(rolesString, ',');
for (String s : rolesList) {
List<String> roleMode = StrUtils.splitSmart(s, ':');
Role r = Role.getRole(roleMode.get(0));
String m = roleMode.get(1);
if (r.supportedModes().contains(m)) {
roles.put(r, m);
} else {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Unknown role mode '" + roleMode.get(1) + "' for role '" + r + "'");
}
}
for (Role r : Role.values()) {
if (!roles.containsKey(r)) {
roles.put(r, r.modeWhenRoleIsAbsent());
}
}
nodeRoles = Collections.unmodifiableMap(roles);
}

public Map<Role, String> getRoles() {
return nodeRoles;
}

public String getRoleMode(Role role) {
return nodeRoles.get(role);
}

public boolean isOverseerAllowedOrPreferred() {
String roleMode = nodeRoles.get(Role.OVERSEER);
return MODE_ALLOWED.equals(roleMode) || MODE_PREFERRED.equals(roleMode);
}

public static final String MODE_ON = "on";
public static final String MODE_OFF = "off";
public static final String MODE_ALLOWED = "allowed";
public static final String MODE_PREFERRED = "preferred";
public static final String MODE_DISALLOWED = "disallowed";

public enum Role {
DATA("data") {
@Override
public Set<String> supportedModes() {
return ImmutableSet.of(MODE_ON, MODE_OFF);
}

@Override
public String modeWhenRoleIsAbsent() {
return MODE_OFF;
}
},
OVERSEER("overseer") {
@Override
public Set<String> supportedModes() {
return ImmutableSet.of(MODE_ALLOWED, MODE_PREFERRED, MODE_DISALLOWED);
}

@Override
public String modeWhenRoleIsAbsent() {
return MODE_DISALLOWED;
}
},

COORDINATOR("coordinator") {
@Override
public String modeWhenRoleIsAbsent() {
return MODE_OFF;
}

@Override
public Set<String> supportedModes() {
return ImmutableSet.of(MODE_ON, MODE_OFF);
}
};

public final String roleName;

Role(String name) {
this.roleName = name;
}

public static Role getRole(String value) {
for (Role role : Role.values()) {
if (value.equals(role.roleName)) return role;
}
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown role: " + value);
}

public abstract Set<String> supportedModes();

/** Default mode for a role in nodes where this role is not specified. */
public abstract String modeWhenRoleIsAbsent();

@Override
public String toString() {
return roleName;
}
}

public static String getZNodeForRole(Role role) {
return ZkStateReader.NODE_ROLES + "/" + role.roleName;
}

public static String getZNodeForRoleMode(Role role, String mode) {
return ZkStateReader.NODE_ROLES + "/" + role.roleName + "/" + mode;
}
}

7 comments on commit 2088d74

@risdenk
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chatman this commit does not pass ant precommit

common.compile-test:
    [mkdir] Created dir: /Users/risdenk/repos/apache/lucene-solr/solr/build/solr-solrj/classes/test
    [javac] Compiling 224 source files to /Users/risdenk/repos/apache/lucene-solr/solr/build/solr-solrj/classes/test
    [javac] /Users/risdenk/repos/apache/lucene-solr/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientBadInputTest.java:64: error: name clash: assertExceptionThrownWithMessageContaining(Class,List<String>,ThrowingRunnable) in CloudHttp2SolrClientBadInputTest and assertExceptionThrownWithMessageContaining(Class<? extends Throwable>,List<String>,ThrowingRunnable) in SolrTestCaseJ4 have the same erasure, yet neither overrides the other
    [javac]   private void assertExceptionThrownWithMessageContaining(@SuppressWarnings({"rawtypes"})Class expectedType,
    [javac]                ^
    [javac] /Users/risdenk/repos/apache/lucene-solr/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientBadInputTest.java:64: error: name clash: assertExceptionThrownWithMessageContaining(Class,List<String>,ThrowingRunnable) in CloudSolrClientBadInputTest and assertExceptionThrownWithMessageContaining(Class<? extends Throwable>,List<String>,ThrowingRunnable) in SolrTestCaseJ4 have the same erasure, yet neither overrides the other
    [javac]   private void assertExceptionThrownWithMessageContaining(@SuppressWarnings({"rawtypes"})Class expectedType,
    [javac]                ^
    [javac] /Users/risdenk/repos/apache/lucene-solr/solr/solrj/src/test/org/apache/solr/client/solrj/impl/ConcurrentUpdateHttp2SolrClientBadInputTest.java:89: error: name clash: assertExceptionThrownWithMessageContaining(Class,List<String>,ThrowingRunnable) in ConcurrentUpdateHttp2SolrClientBadInputTest and assertExceptionThrownWithMessageContaining(Class<? extends Throwable>,List<String>,ThrowingRunnable) in SolrTestCaseJ4 have the same erasure, yet neither overrides the other
    [javac]   private void assertExceptionThrownWithMessageContaining(@SuppressWarnings({"rawtypes"})Class expectedType,
    [javac]                ^
    [javac] /Users/risdenk/repos/apache/lucene-solr/solr/solrj/src/test/org/apache/solr/client/solrj/impl/ConcurrentUpdateSolrClientBadInputTest.java:82: error: name clash: assertExceptionThrownWithMessageContaining(Class,List<String>,ThrowingRunnable) in ConcurrentUpdateSolrClientBadInputTest and assertExceptionThrownWithMessageContaining(Class<? extends Throwable>,List<String>,ThrowingRunnable) in SolrTestCaseJ4 have the same erasure, yet neither overrides the other
    [javac]   private void assertExceptionThrownWithMessageContaining(@SuppressWarnings({"rawtypes"})Class expectedType,
    [javac]                ^
    [javac] /Users/risdenk/repos/apache/lucene-solr/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpSolrClientBadInputTest.java:48: error: name clash: assertExceptionThrownWithMessageContaining(Class,List<String>,ThrowingRunnable) in HttpSolrClientBadInputTest and assertExceptionThrownWithMessageContaining(Class<? extends Throwable>,List<String>,ThrowingRunnable) in SolrTestCaseJ4 have the same erasure, yet neither overrides the other
    [javac]   private void assertExceptionThrownWithMessageContaining(@SuppressWarnings({"rawtypes"})Class expectedType,
    [javac]                ^
    [javac] /Users/risdenk/repos/apache/lucene-solr/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttpSolrClientBadInputTest.java:80: error: name clash: assertExceptionThrownWithMessageContaining(Class,List<String>,ThrowingRunnable) in LBHttpSolrClientBadInputTest and assertExceptionThrownWithMessageContaining(Class<? extends Throwable>,List<String>,ThrowingRunnable) in SolrTestCaseJ4 have the same erasure, yet neither overrides the other
    [javac]   private void assertExceptionThrownWithMessageContaining(@SuppressWarnings({"rawtypes"})Class expectedType,
    [javac]                ^
    [javac] Note: Some input files use or override a deprecated API.
    [javac] Note: Recompile with -Xlint:deprecation for details.
    [javac] Note: /Users/risdenk/repos/apache/lucene-solr/solr/solrj/src/test/org/apache/solr/client/solrj/routing/NodePreferenceRulesComparatorTest.java uses unchecked or unsafe operations.
    [javac] Note: Recompile with -Xlint:unchecked for details.
    [javac] 6 errors

BUILD FAILED

the method assertExceptionThrownWithMessageContaining as added to SolrTestCaseJ4

@chatman
Copy link
Contributor

@chatman chatman commented on 2088d74 Oct 23, 2023 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chatman
Copy link
Contributor

@chatman chatman commented on 2088d74 Oct 23, 2023 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@risdenk
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[javadoc] Constructing Javadoc information...
[javadoc] warning: unknown enum constant Resolution.OPTIONAL
[javadoc] reason: class file for aQute.bnd.annotation.Resolution not
found

I haven't looked where that was introduced but probably a recent commit. I've had precommit passing on ant precommit for all my commits recently so it has to be something recent.

@chatman
Copy link
Contributor

@chatman chatman commented on 2088d74 Oct 23, 2023 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@risdenk
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did email and share I had already had a PR up with dependency upgrades - #2681

PRs do run ant precommit so no need to be just committing and guessing.

@chatman
Copy link
Contributor

@chatman chatman commented on 2088d74 Oct 24, 2023 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.