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.
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
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
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
@@ -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);
}
}
}
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
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
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
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
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
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
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
@@ -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.