Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Voting config exclusions should work with absent nodes #50836

Merged
merged 33 commits into from
Apr 16, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c3d4615
Voting config exclusions should work with absent nodes
zacharymorn Jan 9, 2020
7881e7b
Merge branch 'master' into issue-47990
zacharymorn Jan 17, 2020
39eb2a1
Add new APIs to add voting config exclusion just based on node id or …
zacharymorn Jan 17, 2020
c3eb5a1
Address feedback comment
zacharymorn Feb 28, 2020
ad41573
Address feedback
zacharymorn Mar 4, 2020
b085c2c
Address comment
zacharymorn Mar 4, 2020
57cbc47
Update nodeId for VotingConfigExclusion when node with matching name …
zacharymorn Mar 5, 2020
e08a8a1
Add test cases to AddVotingConfigExclusionsRequestTests
zacharymorn Mar 6, 2020
d325123
Add assertion for voting config exclusion in cluster state
zacharymorn Mar 7, 2020
bede9a0
Add test cases to TransportAddVotingConfigExclusionsActionTests
zacharymorn Mar 7, 2020
fb337f2
Add test to NodeJoinTests
zacharymorn Mar 10, 2020
5e28294
Add test to CooridnatorTests
zacharymorn Mar 10, 2020
49f749c
Inline deprecation message
zacharymorn Mar 10, 2020
a174c7c
Merge branch 'master' into issue-47990
zacharymorn Mar 10, 2020
2d37e5c
Address some comments that can be fixed quickly
zacharymorn Mar 26, 2020
0fb29c3
Make Coordinator#validVotingConfigExclusionState package static for t…
zacharymorn Mar 26, 2020
aaa0f89
Refactoring for node resolution logic and NodeJoinTest
zacharymorn Mar 27, 2020
02a3533
Refactor out deprecated AddVotingConfigExclusionsRequest constructor
zacharymorn Mar 27, 2020
53f133c
Fix checkstyle and tests
zacharymorn Mar 27, 2020
5fe180e
Merge branch 'master' into issue-47990
zacharymorn Mar 27, 2020
0f6dd6f
Fix test failure due to misnomer
zacharymorn Mar 27, 2020
b0d0a97
Revert "Refactor out deprecated AddVotingConfigExclusionsRequest cons…
zacharymorn Apr 9, 2020
5c7a226
Migrate some tests that use nodeDescriptions to using nodeNames
zacharymorn Apr 9, 2020
c6fbce8
Fix style
zacharymorn Apr 9, 2020
fcd3912
Merge branch 'master' into issue-47990
zacharymorn Apr 9, 2020
a2e6247
Revert "Merge branch 'master' into issue-47990"
zacharymorn Apr 9, 2020
ccf6c73
Merge branch 'master' into issue-47990
zacharymorn Apr 9, 2020
2a01d70
Fix spacing
zacharymorn Apr 9, 2020
6d6353e
Update server/src/main/java/org/elasticsearch/cluster/coordination/Jo…
zacharymorn Apr 10, 2020
99a584d
Update server/src/main/java/org/elasticsearch/action/admin/cluster/co…
zacharymorn Apr 10, 2020
89ab437
Update server/src/test/java/org/elasticsearch/cluster/coordination/Co…
zacharymorn Apr 10, 2020
fd64f4b
Address feedback to use Map<String, DiscoveryNode> for existing nodes
zacharymorn Apr 10, 2020
51f74bf
Fix checkstyle
zacharymorn Apr 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.iterable.Iterables;
import org.elasticsearch.common.util.set.Sets;

import java.io.IOException;
import java.util.Arrays;
Expand Down Expand Up @@ -72,16 +74,22 @@ public AddVotingConfigExclusionsRequest(StreamInput in) throws IOException {

Set<VotingConfigExclusion> resolveVotingConfigExclusions(ClusterState currentState) {
final DiscoveryNodes allNodes = currentState.nodes();
final Set<VotingConfigExclusion> resolvedNodes = Arrays.stream(allNodes.resolveNodes(nodeDescriptions))
final DiscoveryNodes.NodeResolutionResults nodeResolutionResults = allNodes.resolveNodes(nodeDescriptions, true);

final Set<VotingConfigExclusion> resolvedNodes = Arrays.stream(nodeResolutionResults.getResolvedNodes())
.map(allNodes::get).filter(DiscoveryNode::isMasterNode).map(VotingConfigExclusion::new).collect(Collectors.toSet());
final Set<VotingConfigExclusion> unresolvedNodes = Arrays.stream(nodeResolutionResults.getUnresolvedNodes())
.map(VotingConfigExclusion::new).collect(Collectors.toSet());

Set<VotingConfigExclusion> allProcessedNodes = Sets.newHashSet(Iterables.concat(resolvedNodes, unresolvedNodes));

if (resolvedNodes.isEmpty()) {
if (allProcessedNodes.isEmpty()) {
throw new IllegalArgumentException("add voting config exclusions request for " + Arrays.asList(nodeDescriptions)
+ " matched no master-eligible nodes");
+ " matched no master-eligible nodes or absent nodes");
}

resolvedNodes.removeIf(n -> currentState.getVotingConfigExclusions().contains(n));
return resolvedNodes;
allProcessedNodes.removeIf(n -> currentState.getVotingConfigExclusions().contains(n));
return allProcessedNodes;
}

Set<VotingConfigExclusion> resolveVotingConfigExclusionsAndCheckMaximum(ClusterState currentState, int maxExclusionsCount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ public static class Builder {
public Builder() {

}

public Builder(CoordinationMetaData state) {
this.term = state.term;
this.lastCommittedConfiguration = state.lastCommittedConfiguration;
Expand Down Expand Up @@ -246,6 +246,11 @@ public VotingConfigExclusion(String nodeId, String nodeName) {
this.nodeName = nodeName;
}

public VotingConfigExclusion(String nodeIdAndName) {
this.nodeId = nodeIdAndName;
this.nodeName = nodeIdAndName;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(nodeId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,24 @@ public DiscoveryNode resolveNode(String node) {
return nodes.get(resolvedNodeIds[0]);
}

public static class NodeResolutionResults {
private String[] resolvedNodes;
private String[] unresolvedNodes;

public NodeResolutionResults(String[] resolvedNodes, String[] unresolvedNodes) {
this.resolvedNodes = resolvedNodes;
this.unresolvedNodes = unresolvedNodes;
}

public String[] getResolvedNodes() {
return resolvedNodes;
}

public String[] getUnresolvedNodes() {
return unresolvedNodes;
}
}

/**
* resolves a set of node "descriptions" to concrete and existing node ids. "descriptions" can be (resolved in this order):
* - "_local" or "_master" for the relevant nodes
Expand All @@ -326,60 +344,76 @@ public DiscoveryNode resolveNode(String node) {
* or a generic node attribute name in which case value will be treated as a wildcard and matched against the node attribute values.
*/
public String[] resolveNodes(String... nodes) {
return resolveNodes(nodes, false).getResolvedNodes();
}

public NodeResolutionResults resolveNodes(String[] nodes, boolean returnUnresolved) {
if (nodes == null || nodes.length == 0) {
return StreamSupport.stream(this.spliterator(), false).map(DiscoveryNode::getId).toArray(String[]::new);
return new NodeResolutionResults(StreamSupport.stream(this.spliterator(), false)
.map(DiscoveryNode::getId).toArray(String[]::new), new String[0]);
} else {
ObjectHashSet<String> resolvedNodesIds = new ObjectHashSet<>(nodes.length);
for (String nodeId : nodes) {
if (nodeId.equals("_local")) {
ObjectHashSet<String> unresolvedNodesIds = new ObjectHashSet<>();
boolean nodeIdResolved = false;

DaveCTurner marked this conversation as resolved.
Show resolved Hide resolved
for (String nodeIdToBeProcessed : nodes) {
if (nodeIdToBeProcessed.equals("_local")) {
String localNodeId = getLocalNodeId();
if (localNodeId != null) {
resolvedNodesIds.add(localNodeId);
nodeIdResolved = true;
}
} else if (nodeId.equals("_master")) {
} else if (nodeIdToBeProcessed.equals("_master")) {
String masterNodeId = getMasterNodeId();
if (masterNodeId != null) {
resolvedNodesIds.add(masterNodeId);
nodeIdResolved = true;
}
} else if (nodeExists(nodeId)) {
resolvedNodesIds.add(nodeId);
} else if (nodeExists(nodeIdToBeProcessed)) {
resolvedNodesIds.add(nodeIdToBeProcessed);
nodeIdResolved = true;
} else {
for (DiscoveryNode node : this) {
if ("_all".equals(nodeId)
|| Regex.simpleMatch(nodeId, node.getName())
|| Regex.simpleMatch(nodeId, node.getHostAddress())
|| Regex.simpleMatch(nodeId, node.getHostName())) {
if ("_all".equals(nodeIdToBeProcessed)
|| Regex.simpleMatch(nodeIdToBeProcessed, node.getName())
|| Regex.simpleMatch(nodeIdToBeProcessed, node.getHostAddress())
|| Regex.simpleMatch(nodeIdToBeProcessed, node.getHostName())) {
resolvedNodesIds.add(node.getId());
nodeIdResolved = true;
}
}
int index = nodeId.indexOf(':');
int index = nodeIdToBeProcessed.indexOf(':');
if (index != -1) {
String matchAttrName = nodeId.substring(0, index);
String matchAttrValue = nodeId.substring(index + 1);
String matchAttrName = nodeIdToBeProcessed.substring(0, index);
String matchAttrValue = nodeIdToBeProcessed.substring(index + 1);
if (DiscoveryNodeRole.DATA_ROLE.roleName().equals(matchAttrName)) {
if (Booleans.parseBoolean(matchAttrValue, true)) {
resolvedNodesIds.addAll(dataNodes.keys());
} else {
resolvedNodesIds.removeAll(dataNodes.keys());
}
nodeIdResolved = true;
} else if (DiscoveryNodeRole.MASTER_ROLE.roleName().equals(matchAttrName)) {
if (Booleans.parseBoolean(matchAttrValue, true)) {
resolvedNodesIds.addAll(masterNodes.keys());
} else {
resolvedNodesIds.removeAll(masterNodes.keys());
}
nodeIdResolved = true;
} else if (DiscoveryNodeRole.INGEST_ROLE.roleName().equals(matchAttrName)) {
if (Booleans.parseBoolean(matchAttrValue, true)) {
resolvedNodesIds.addAll(ingestNodes.keys());
} else {
resolvedNodesIds.removeAll(ingestNodes.keys());
}
nodeIdResolved = true;
} else if (DiscoveryNode.COORDINATING_ONLY.equals(matchAttrName)) {
if (Booleans.parseBoolean(matchAttrValue, true)) {
resolvedNodesIds.addAll(getCoordinatingOnlyNodes().keys());
} else {
resolvedNodesIds.removeAll(getCoordinatingOnlyNodes().keys());
}
nodeIdResolved = true;
} else {
for (DiscoveryNode node : this) {
for (DiscoveryNodeRole role : Sets.difference(node.getRoles(), DiscoveryNodeRole.BUILT_IN_ROLES)) {
Expand All @@ -389,6 +423,7 @@ public String[] resolveNodes(String... nodes) {
} else {
resolvedNodesIds.remove(node.getId());
}
nodeIdResolved = true;
}
}
}
Expand All @@ -398,14 +433,19 @@ public String[] resolveNodes(String... nodes) {
String attrValue = entry.getValue();
if (Regex.simpleMatch(matchAttrName, attrName) && Regex.simpleMatch(matchAttrValue, attrValue)) {
resolvedNodesIds.add(node.getId());
nodeIdResolved = true;
}
}
}
}
}
}

if(!nodeIdResolved) {
unresolvedNodesIds.add(nodeIdToBeProcessed);
}
}
return resolvedNodesIds.toArray(String.class);
return new NodeResolutionResults(resolvedNodesIds.toArray(String.class), unresolvedNodesIds.toArray(String.class));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,6 @@ public void testResolve() {
contains(localNodeExclusion));
assertThat(makeRequest("other*").resolveVotingConfigExclusions(clusterState),
containsInAnyOrder(otherNode1Exclusion, otherNode2Exclusion));

assertThat(expectThrows(IllegalArgumentException.class,
() -> makeRequest("not-a-node").resolveVotingConfigExclusions(clusterState)).getMessage(),
equalTo("add voting config exclusions request for [not-a-node] matched no master-eligible nodes"));
}

public void testResolveAndCheckMaximum() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,42 +243,19 @@ public void testReturnsImmediatelyIfVoteAlreadyWithdrawn() throws InterruptedExc
contains(otherNode1Exclusion));
}

public void testReturnsErrorIfNoMatchingNodes() throws InterruptedException {
public void testMatchesAbsentNodes() throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(1);
final SetOnce<TransportException> exceptionHolder = new SetOnce<>();

transportService.sendRequest(localNode, AddVotingConfigExclusionsAction.NAME,
new AddVotingConfigExclusionsRequest(new String[]{"not-a-node"}),
expectError(e -> {
exceptionHolder.set(e);
countDownLatch.countDown();
})
);

assertTrue(countDownLatch.await(30, TimeUnit.SECONDS));
final Throwable rootCause = exceptionHolder.get().getRootCause();
assertThat(rootCause, instanceOf(IllegalArgumentException.class));
assertThat(rootCause.getMessage(),
equalTo("add voting config exclusions request for [not-a-node] matched no master-eligible nodes"));
}

public void testOnlyMatchesMasterEligibleNodes() throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(1);
final SetOnce<TransportException> exceptionHolder = new SetOnce<>();

transportService.sendRequest(localNode, AddVotingConfigExclusionsAction.NAME,
new AddVotingConfigExclusionsRequest(new String[]{"_all", "master:false"}),
expectError(e -> {
exceptionHolder.set(e);
new AddVotingConfigExclusionsRequest(new String[]{"absent_node"}),
expectSuccess(e -> {
countDownLatch.countDown();
})
);

assertTrue(countDownLatch.await(30, TimeUnit.SECONDS));
final Throwable rootCause = exceptionHolder.get().getRootCause();
assertThat(rootCause, instanceOf(IllegalArgumentException.class));
assertThat(rootCause.getMessage(),
equalTo("add voting config exclusions request for [_all, master:false] matched no master-eligible nodes"));
assertEquals(Set.of(new VotingConfigExclusion("absent_node")),
clusterService.getClusterApplierService().state().getVotingConfigExclusions());
}

public void testSucceedsEvenIfAllExclusionsAlreadyAdded() throws InterruptedException {
Expand Down