Skip to content

Introduce node.roles setting #54998

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

Merged
merged 37 commits into from
Jun 24, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e1bb907
Introduce node.roles setting
jasontedor Apr 8, 2020
254c573
Checkstyle
jasontedor Apr 9, 2020
eaf2b34
Fix test
jasontedor Apr 9, 2020
7a78ed0
Remove imports
jasontedor Apr 9, 2020
bb82fb4
Use constant
jasontedor Apr 9, 2020
400ac0e
Merge remote-tracking branch 'elastic/master' into node-roles
jasontedor Apr 13, 2020
7fe22af
Fix test
jasontedor Apr 13, 2020
86663e9
Fix sorting order
jasontedor Apr 13, 2020
8fc5eb1
Fix stopping ML nodes
jasontedor Apr 13, 2020
a6e899e
Fix ml test
jasontedor Apr 13, 2020
7c75d9b
Fix one more ML test
jasontedor Apr 13, 2020
a431f37
Oh
jasontedor Apr 14, 2020
7fea055
Fix typo
jasontedor Apr 14, 2020
e926535
Add comments
jasontedor Apr 14, 2020
f7cef5c
Fix addRoles
jasontedor Apr 14, 2020
9000d8c
Fix imports
jasontedor Apr 14, 2020
c564977
Fix imports
jasontedor Apr 14, 2020
fed70b4
Fix tests
jasontedor Apr 14, 2020
2980590
Fix test bugs
jasontedor Apr 14, 2020
cf52fe8
Fix more tests
jasontedor Apr 14, 2020
6f26ea6
Fix tests, make node.X settings private
jasontedor Apr 15, 2020
3e86674
Remove unneeded role setting
jasontedor Apr 15, 2020
e903d0f
Merge branch 'master' into node-roles
elasticmachine Apr 15, 2020
c038cbb
Merge remote-tracking branch 'elastic/master' into node-roles
jasontedor Apr 15, 2020
c260abd
Docs
jasontedor Apr 16, 2020
f7ed0ea
Handle un-set roles and legacy setting
jasontedor Apr 16, 2020
64aaa65
Merge remote-tracking branch 'elastic' into node-roles
jasontedor Jun 18, 2020
cf2bae9
Fix leftover merge conflicts
jasontedor Jun 18, 2020
1478a20
Fix use of legacy settings
jasontedor Jun 18, 2020
8e226d8
Fix compilation
jasontedor Jun 18, 2020
e2c6f00
Feedback
jasontedor Jun 18, 2020
cfb6513
Merge remote-tracking branch 'elastic/master' into node-roles
jasontedor Jun 18, 2020
7d593f0
Fix imports
jasontedor Jun 18, 2020
41a3998
Merge remote-tracking branch 'elastic' into node-roles
jasontedor Jun 24, 2020
827b525
Fix merge conflict
jasontedor Jun 24, 2020
b852f72
Reword
jasontedor Jun 24, 2020
6d9ef4f
Simplify
jasontedor Jun 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions docs/reference/commands/node-tool.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,16 @@ after repurposing it.
The intended use is:

* Stop the node
* Update `elasticsearch.yml` by setting `node.master` and `node.data` as
desired.
* Update `elasticsearch.yml` by setting `node.roles` as desired.
* Run `elasticsearch-node repurpose` on the node
* Start the node

If you run `elasticsearch-node repurpose` on a node with `node.data: false` and
`node.master: true` then it will delete any remaining shard data on that node,
but it will leave the index and cluster metadata alone. If you run
`elasticsearch-node repurpose` on a node with `node.data: false` and
`node.master: false` then it will delete any remaining shard data and index
metadata, but it will leave the cluster metadata alone.
If you run `elasticsearch-node repurpose` on a node without the `data` role and
with the `master` role then it will delete any remaining shard data on that
node, but it will leave the index and cluster metadata alone. If you run
`elasticsearch-node repurpose` on a node without the `data` and `master` roles
then it will delete any remaining shard data and index metadata, but it will
leave the cluster metadata alone.

[WARNING]
Running this command can lead to data loss for the indices mentioned if the
Expand Down Expand Up @@ -351,12 +350,12 @@ from the on-disk cluster state.
=== Examples

[float]
==== Repurposing a node as a dedicated master node (master: true, data: false)
==== Repurposing a node as a dedicated master node

In this example, a former data node is repurposed as a dedicated master node.
First update the node's settings to `node.master: true` and `node.data: false`
in its `elasticsearch.yml` config file. Then run the `elasticsearch-node
repurpose` command to find and remove excess shard data:
First update the node's settings to `node.roles: [ "master" ]` in its
`elasticsearch.yml` config file. Then run the `elasticsearch-node repurpose`
command to find and remove excess shard data:

[source,txt]
----
Expand All @@ -373,13 +372,13 @@ Node successfully repurposed to master and no-data.
----

[float]
==== Repurposing a node as a coordinating-only node (master: false, data: false)
==== Repurposing a node as a coordinating-only node

In this example, a node that previously held data is repurposed as a
coordinating-only node. First update the node's settings to `node.master:
false` and `node.data: false` in its `elasticsearch.yml` config file. Then run
the `elasticsearch-node repurpose` command to find and remove excess shard data
and index metadata:
coordinating-only node. First update the node's settings to `node.roles: []` in
its `elasticsearch.yml` config file. Then run the `elasticsearch-node repurpose`
command to find and remove excess shard data and index metadata:


[source,txt]
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.NodeRoles;
import org.elasticsearch.test.StreamsUtils;

import java.io.ByteArrayInputStream;
Expand All @@ -45,6 +46,7 @@
import java.util.Collections;
import java.util.List;

import static org.elasticsearch.test.NodeRoles.nonIngestNode;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.Matchers.equalTo;

Expand Down Expand Up @@ -82,7 +84,7 @@ protected Settings nodeSettings(final int nodeOrdinal) {
}
return Settings.builder()
.put("ingest.geoip.database_path", databasePath)
.put("node.ingest", false)
.put(nonIngestNode())
.put(super.nodeSettings(nodeOrdinal))
.build();
}
Expand Down Expand Up @@ -145,7 +147,7 @@ public void testLazyLoading() throws IOException {
Arrays.stream(internalCluster().getNodeNames()).forEach(node -> assertDatabaseLoadStatus(node, false));

// start an ingest node
final String ingestNode = internalCluster().startNode(Settings.builder().put("node.ingest", true).build());
final String ingestNode = internalCluster().startNode(NodeRoles.ingestNode());
internalCluster().getInstance(IngestService.class, ingestNode);
// the geo-IP database should not be loaded yet as we have no indexed any documents using a pipeline that has a geo-IP processor
assertDatabaseLoadStatus(ingestNode, false);
Expand Down
3 changes: 1 addition & 2 deletions qa/multi-cluster-search/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ task 'remote-cluster'(type: RestIntegTestTask) {

testClusters.'remote-cluster' {
numberOfNodes = 2
setting 'node.remote_cluster_client', 'false'
setting 'node.roles', '[data,ingest,master]'
}

task mixedClusterTest(type: RestIntegTestTask) {
Expand All @@ -50,7 +50,6 @@ testClusters.mixedClusterTest {
setting 'cluster.remote.my_remote_cluster.seeds',
{ "\"${testClusters.'remote-cluster'.getAllTransportPortURI().get(0)}\"" }
setting 'cluster.remote.connections_per_cluster', '1'
setting 'node.remote_cluster_client', 'true'
}


Expand Down
2 changes: 1 addition & 1 deletion qa/smoke-test-ingest-disabled/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ dependencies {
}

testClusters.integTest {
setting 'node.ingest', 'false'
setting 'node.roles', '[data,master,remote_cluster_client]'
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.util.set.Sets;
Expand All @@ -36,12 +38,16 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.elasticsearch.node.NodeRoleSettings.NODE_ROLES_SETTING;


/**
Expand All @@ -51,20 +57,26 @@ public class DiscoveryNode implements Writeable, ToXContentFragment {

static final String COORDINATING_ONLY = "coordinating_only";

public static boolean isMasterNode(Settings settings) {
return Node.NODE_MASTER_SETTING.get(settings);
public static boolean hasRole(final Settings settings, final DiscoveryNodeRole role) {
final Setting<List<DiscoveryNodeRole>> nodeRolesSetting =
Setting.listSetting("node.roles", List.of(role.roleName()), DiscoveryNode::getRoleFromRoleName, Property.NodeScope);
return getRolesFromSettings(settings, nodeRolesSetting, rolesToMap(DiscoveryNodeRole.BUILT_IN_ROLES.stream())).contains(role);
}

public static boolean isMasterNode(final Settings settings) {
return hasRole(settings, DiscoveryNodeRole.MASTER_ROLE);
}

public static boolean isDataNode(Settings settings) {
return Node.NODE_DATA_SETTING.get(settings);
public static boolean isDataNode(final Settings settings) {
return hasRole(settings, DiscoveryNodeRole.DATA_ROLE);
}

public static boolean isIngestNode(Settings settings) {
return Node.NODE_INGEST_SETTING.get(settings);
public static boolean isIngestNode(final Settings settings) {
return hasRole(settings, DiscoveryNodeRole.INGEST_ROLE);
}

public static boolean isRemoteClusterClient(final Settings settings) {
return Node.NODE_REMOTE_CLUSTER_CLIENT.get(settings);
return hasRole(settings, DiscoveryNodeRole.REMOTE_CLUSTER_CLIENT_ROLE);
}

private final String nodeName;
Expand Down Expand Up @@ -175,7 +187,7 @@ public DiscoveryNode(String nodeName, String nodeId, String ephemeralId, String
//verify that no node roles are being provided as attributes
Predicate<Map<String, String>> predicate = (attrs) -> {
boolean success = true;
for (final DiscoveryNodeRole role : DiscoveryNode.roleNameToPossibleRoles.values()) {
for (final DiscoveryNodeRole role : DiscoveryNode.roleMap.values()) {
success &= attrs.containsKey(role.roleName()) == false;
assert success : role.roleName();
}
Expand All @@ -194,7 +206,37 @@ public static DiscoveryNode createLocal(Settings settings, TransportAddress publ

/** extract node roles from the given settings */
public static Set<DiscoveryNodeRole> getRolesFromSettings(final Settings settings) {
return roleNameToPossibleRoles.values().stream().filter(s -> s.roleSetting().get(settings)).collect(Collectors.toUnmodifiableSet());
return getRolesFromSettings(settings, NODE_ROLES_SETTING, DiscoveryNode.roleMap);
}

private static Set<DiscoveryNodeRole> getRolesFromSettings(
final Settings settings,
final Setting<List<DiscoveryNodeRole>> nodeRolesSetting,
final Map<String, DiscoveryNodeRole> roleMap
) {
if (nodeRolesSetting.exists(settings)) {
validateLegacySettings(settings, roleMap);
return Set.copyOf(nodeRolesSetting.get(settings));
} else {
return roleMap.values()
.stream()
.filter(s -> s.legacySetting().get(settings))
.collect(Collectors.toUnmodifiableSet());
}
}

private static void validateLegacySettings(final Settings settings, final Map<String, DiscoveryNodeRole> roleMap) {
for (final DiscoveryNodeRole role : roleMap.values()) {
if (role.legacySetting().exists(settings)) {
final String message = String.format(
Locale.ROOT,
"can not explicitly configure node roles and use legacy role setting [%s]=[%s]",
role.legacySetting().getKey(),
role.legacySetting().get(settings)
);
throw new IllegalArgumentException(message);
}
}
}

/**
Expand All @@ -220,7 +262,7 @@ public DiscoveryNode(StreamInput in) throws IOException {
for (int i = 0; i < rolesSize; i++) {
final String roleName = in.readString();
final String roleNameAbbreviation = in.readString();
final DiscoveryNodeRole role = roleNameToPossibleRoles.get(roleName);
final DiscoveryNodeRole role = roleMap.get(roleName);
if (role == null) {
roles.add(new DiscoveryNodeRole.UnknownRole(roleName, roleNameAbbreviation));
} else {
Expand Down Expand Up @@ -442,22 +484,38 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
return builder;
}

private static Map<String, DiscoveryNodeRole> roleNameToPossibleRoles;
private static Map<String, DiscoveryNodeRole> rolesToMap(final Stream<DiscoveryNodeRole> roles) {
return roles.collect(Collectors.toUnmodifiableMap(DiscoveryNodeRole::roleName, Function.identity()));
}

private static Map<String, DiscoveryNodeRole> roleMap = rolesToMap(DiscoveryNodeRole.BUILT_IN_ROLES.stream());

public static DiscoveryNodeRole getRoleFromRoleName(final String roleName) {
if (roleMap.containsKey(roleName) == false) {
throw new IllegalArgumentException("unknown role [" + roleName + "]");
}
return roleMap.get(roleName);
}

public static Set<DiscoveryNodeRole> getPossibleRoles() {
return Set.copyOf(roleMap.values());
}

public static void setPossibleRoles(final Set<DiscoveryNodeRole> possibleRoles) {
public static void setAdditionalRoles(final Set<DiscoveryNodeRole> additionalRoles) {
assert additionalRoles.stream().allMatch(r -> r.legacySetting().isDeprecated()) : additionalRoles;
final Map<String, DiscoveryNodeRole> roleNameToPossibleRoles =
possibleRoles.stream().collect(Collectors.toUnmodifiableMap(DiscoveryNodeRole::roleName, Function.identity()));
rolesToMap(Stream.concat(DiscoveryNodeRole.BUILT_IN_ROLES.stream(), additionalRoles.stream()));
// collect the abbreviation names into a map to ensure that there are not any duplicate abbreviations
final Map<String, DiscoveryNodeRole> roleNameAbbreviationToPossibleRoles = roleNameToPossibleRoles.values()
.stream()
.collect(Collectors.toUnmodifiableMap(DiscoveryNodeRole::roleNameAbbreviation, Function.identity()));
assert roleNameToPossibleRoles.size() == roleNameAbbreviationToPossibleRoles.size() :
"roles by name [" + roleNameToPossibleRoles + "], roles by name abbreviation [" + roleNameAbbreviationToPossibleRoles + "]";
DiscoveryNode.roleNameToPossibleRoles = roleNameToPossibleRoles;
DiscoveryNode.roleMap = roleNameToPossibleRoles;
}

public static Set<String> getPossibleRoleNames() {
return roleNameToPossibleRoles.keySet();
return roleMap.keySet();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package org.elasticsearch.cluster.node;

import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.node.Node;

Expand Down Expand Up @@ -64,6 +65,10 @@ public final boolean isKnownRole() {
return isKnownRole;
}

public boolean isEnabledByDefault(final Settings settings) {
return legacySetting().get(settings);
}

protected DiscoveryNodeRole(final String roleName, final String roleNameAbbreviation) {
this(true, roleName, roleNameAbbreviation);
}
Expand All @@ -74,7 +79,7 @@ private DiscoveryNodeRole(final boolean isKnownRole, final String roleName, fina
this.roleNameAbbreviation = Objects.requireNonNull(roleNameAbbreviation);
}

protected abstract Setting<Boolean> roleSetting();
public abstract Setting<Boolean> legacySetting();

@Override
public final boolean equals(Object o) {
Expand Down Expand Up @@ -111,7 +116,7 @@ public final String toString() {
public static final DiscoveryNodeRole DATA_ROLE = new DiscoveryNodeRole("data", "d") {

@Override
protected Setting<Boolean> roleSetting() {
public Setting<Boolean> legacySetting() {
return Node.NODE_DATA_SETTING;
}

Expand All @@ -123,7 +128,7 @@ protected Setting<Boolean> roleSetting() {
public static final DiscoveryNodeRole INGEST_ROLE = new DiscoveryNodeRole("ingest", "i") {

@Override
protected Setting<Boolean> roleSetting() {
public Setting<Boolean> legacySetting() {
return Node.NODE_INGEST_SETTING;
}

Expand All @@ -135,7 +140,7 @@ protected Setting<Boolean> roleSetting() {
public static final DiscoveryNodeRole MASTER_ROLE = new DiscoveryNodeRole("master", "m") {

@Override
protected Setting<Boolean> roleSetting() {
public Setting<Boolean> legacySetting() {
return Node.NODE_MASTER_SETTING;
}

Expand All @@ -144,7 +149,7 @@ protected Setting<Boolean> roleSetting() {
public static final DiscoveryNodeRole REMOTE_CLUSTER_CLIENT_ROLE = new DiscoveryNodeRole("remote_cluster_client", "r") {

@Override
protected Setting<Boolean> roleSetting() {
public Setting<Boolean> legacySetting() {
return Node.NODE_REMOTE_CLUSTER_CLIENT;
}

Expand Down Expand Up @@ -176,7 +181,7 @@ static class UnknownRole extends DiscoveryNodeRole {
}

@Override
protected Setting<Boolean> roleSetting() {
public Setting<Boolean> legacySetting() {
// since this setting is not registered, it will always return false when testing if the local node has the role
assert false;
return Setting.boolSetting("node. " + roleName(), false, Setting.Property.NodeScope);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
import org.elasticsearch.monitor.os.OsService;
import org.elasticsearch.monitor.process.ProcessService;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeRoleSettings;
import org.elasticsearch.persistent.PersistentTasksClusterService;
import org.elasticsearch.persistent.decider.EnableAssignmentDecider;
import org.elasticsearch.plugins.PluginsService;
Expand Down Expand Up @@ -403,6 +404,7 @@ public void apply(Settings value, Settings current, Settings previous) {
Node.NODE_INGEST_SETTING,
Node.NODE_REMOTE_CLUSTER_CLIENT,
Node.NODE_ATTRIBUTES,
NodeRoleSettings.NODE_ROLES_SETTING,
AutoCreateIndex.AUTO_CREATE_INDEX_SETTING,
BaseRestHandler.MULTI_ALLOW_EXPLICIT_INDEX,
ClusterName.CLUSTER_NAME_SETTING,
Expand Down
Loading