Skip to content

Commit 284dce6

Browse files
authored
Centralize the concept of processors configuration (#89662)
This commit centralize the processor count concept into the Processors class. With this change now all the places using a processor count rely on this new class, such as desired nodes, `node.processors` setting and autoscaling deciders. - Processor counts are rounded to up to 5 decimal places - Processors can be represented as doubles Desired nodes processors were stored as floats, this poses some challenges during upgrades as once the value is casted to a double, the precision increases and therefore the number is not the same. In order to allow idempotent desired nodes updates after upgrades, this commit introduces `DesiredNode#equalsWithProcessorsCloseTo(DesiredNode that)` which allows comparing two desired nodes that differ up to a max delta in their processor specification as floats.
1 parent 33ff7b2 commit 284dce6

File tree

39 files changed

+796
-241
lines changed

39 files changed

+796
-241
lines changed

docs/changelog/89662.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 89662
2+
summary: Centralize the concept of processors configuration
3+
area: Autoscaling
4+
type: enhancement
5+
issues: []

qa/evil-tests/src/test/java/org/elasticsearch/monitor/os/EvilOsProbeTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
package org.elasticsearch.monitor.os;
1010

1111
import org.apache.lucene.util.Constants;
12+
import org.elasticsearch.common.unit.Processors;
1213
import org.elasticsearch.core.PathUtils;
1314
import org.elasticsearch.test.ESTestCase;
1415

@@ -24,7 +25,7 @@
2425
public class EvilOsProbeTests extends ESTestCase {
2526

2627
public void testOsPrettyName() throws IOException {
27-
final OsInfo osInfo = OsProbe.getInstance().osInfo(randomLongBetween(1, 100), randomIntBetween(1, 8));
28+
final OsInfo osInfo = OsProbe.getInstance().osInfo(randomLongBetween(1, 100), Processors.of((double) randomIntBetween(1, 8)));
2829
if (Constants.LINUX) {
2930
final List<String> lines;
3031
if (Files.exists(PathUtils.get("/etc/os-release"))) {

qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/DesiredNodesUpgradeIT.java

Lines changed: 109 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.elasticsearch.common.Strings;
1818
import org.elasticsearch.common.settings.Settings;
1919
import org.elasticsearch.common.unit.ByteSizeValue;
20+
import org.elasticsearch.common.unit.Processors;
2021
import org.elasticsearch.common.xcontent.support.XContentMapValues;
2122
import org.elasticsearch.xcontent.json.JsonXContent;
2223

@@ -31,37 +32,109 @@
3132
import static org.hamcrest.Matchers.is;
3233

3334
public class DesiredNodesUpgradeIT extends AbstractRollingTestCase {
35+
private enum ProcessorsPrecision {
36+
DOUBLE,
37+
FLOAT
38+
}
39+
3440
public void testUpgradeDesiredNodes() throws Exception {
3541
// Desired nodes was introduced in 8.1
3642
if (UPGRADE_FROM_VERSION.before(Version.V_8_1_0)) {
3743
return;
3844
}
3945

46+
if (UPGRADE_FROM_VERSION.onOrAfter(Processors.DOUBLE_PROCESSORS_SUPPORT_VERSION)) {
47+
assertUpgradedNodesCanReadDesiredNodes();
48+
} else if (UPGRADE_FROM_VERSION.onOrAfter(DesiredNode.RANGE_FLOAT_PROCESSORS_SUPPORT_VERSION)) {
49+
assertDesiredNodesUpdatedWithRoundedUpFloatsAreIdempotent();
50+
} else {
51+
assertDesiredNodesWithFloatProcessorsAreRejectedInOlderVersions();
52+
}
53+
}
54+
55+
private void assertUpgradedNodesCanReadDesiredNodes() throws Exception {
56+
final int desiredNodesVersion = switch (CLUSTER_TYPE) {
57+
case OLD -> 1;
58+
case MIXED -> FIRST_MIXED_ROUND ? 2 : 3;
59+
case UPGRADED -> 4;
60+
};
61+
62+
if (CLUSTER_TYPE != ClusterType.OLD) {
63+
final Map<String, Object> desiredNodes = getLatestDesiredNodes();
64+
final String historyId = extractValue(desiredNodes, "history_id");
65+
final int version = extractValue(desiredNodes, "version");
66+
assertThat(historyId, is(equalTo("upgrade_test")));
67+
assertThat(version, is(equalTo(desiredNodesVersion - 1)));
68+
}
69+
70+
addClusterNodesToDesiredNodesWithProcessorsOrProcessorRanges(desiredNodesVersion, ProcessorsPrecision.DOUBLE);
71+
assertAllDesiredNodesAreActualized();
72+
}
73+
74+
private void assertDesiredNodesUpdatedWithRoundedUpFloatsAreIdempotent() throws Exception {
75+
// We define the same set of desired nodes to ensure that they are equal across all
76+
// the test runs, otherwise we cannot guarantee an idempotent update in this test
77+
final var desiredNodes = getNodeNames().stream()
78+
.map(
79+
nodeName -> new DesiredNode(
80+
Settings.builder().put(NODE_NAME_SETTING.getKey(), nodeName).build(),
81+
1238.49922909,
82+
ByteSizeValue.ofGb(32),
83+
ByteSizeValue.ofGb(128),
84+
Version.CURRENT
85+
)
86+
)
87+
.toList();
88+
89+
final int desiredNodesVersion = switch (CLUSTER_TYPE) {
90+
case OLD -> 1;
91+
case MIXED -> FIRST_MIXED_ROUND ? 2 : 3;
92+
case UPGRADED -> 4;
93+
};
94+
95+
if (CLUSTER_TYPE != ClusterType.OLD) {
96+
updateDesiredNodes(desiredNodes, desiredNodesVersion - 1);
97+
}
98+
for (int i = 0; i < 2; i++) {
99+
updateDesiredNodes(desiredNodes, desiredNodesVersion);
100+
}
101+
102+
final Map<String, Object> latestDesiredNodes = getLatestDesiredNodes();
103+
final int latestDesiredNodesVersion = extractValue(latestDesiredNodes, "version");
104+
assertThat(latestDesiredNodesVersion, is(equalTo(desiredNodesVersion)));
105+
106+
if (CLUSTER_TYPE == ClusterType.UPGRADED) {
107+
assertAllDesiredNodesAreActualized();
108+
}
109+
}
110+
111+
private void assertDesiredNodesWithFloatProcessorsAreRejectedInOlderVersions() throws Exception {
40112
switch (CLUSTER_TYPE) {
41113
case OLD -> addClusterNodesToDesiredNodesWithIntegerProcessors(1);
42114
case MIXED -> {
43115
int version = FIRST_MIXED_ROUND ? 2 : 3;
44-
if (UPGRADE_FROM_VERSION.onOrAfter(DesiredNode.RANGE_FLOAT_PROCESSORS_SUPPORT_VERSION)) {
45-
addClusterNodesToDesiredNodesWithFloatProcessorsOrProcessorRanges(version);
46-
} else {
47-
// Processor ranges or float processors are forbidden during upgrades: 8.2 -> 8.3 clusters
48-
final var responseException = expectThrows(
49-
ResponseException.class,
50-
() -> addClusterNodesToDesiredNodesWithFloatProcessorsOrProcessorRanges(version)
51-
);
52-
final var statusCode = responseException.getResponse().getStatusLine().getStatusCode();
53-
assertThat(statusCode, is(equalTo(400)));
54-
}
116+
// Processor ranges or float processors are forbidden during upgrades: 8.2 -> 8.3 clusters
117+
final var responseException = expectThrows(
118+
ResponseException.class,
119+
() -> addClusterNodesToDesiredNodesWithProcessorsOrProcessorRanges(version, ProcessorsPrecision.FLOAT)
120+
);
121+
final var statusCode = responseException.getResponse().getStatusLine().getStatusCode();
122+
assertThat(statusCode, is(equalTo(400)));
55123
}
56124
case UPGRADED -> {
57125
assertAllDesiredNodesAreActualized();
58-
addClusterNodesToDesiredNodesWithFloatProcessorsOrProcessorRanges(4);
126+
addClusterNodesToDesiredNodesWithProcessorsOrProcessorRanges(4, ProcessorsPrecision.FLOAT);
59127
}
60128
}
61129

130+
getLatestDesiredNodes();
131+
}
132+
133+
private Map<String, Object> getLatestDesiredNodes() throws IOException {
62134
final var getDesiredNodesRequest = new Request("GET", "/_internal/desired_nodes/_latest");
63135
final var response = client().performRequest(getDesiredNodesRequest);
64136
assertThat(response.getStatusLine().getStatusCode(), is(equalTo(200)));
137+
return responseAsMap(response);
65138
}
66139

67140
private void assertAllDesiredNodesAreActualized() throws Exception {
@@ -77,32 +150,34 @@ private void assertAllDesiredNodesAreActualized() throws Exception {
77150
}
78151
}
79152

80-
private void addClusterNodesToDesiredNodesWithFloatProcessorsOrProcessorRanges(int version) throws Exception {
153+
private void addClusterNodesToDesiredNodesWithProcessorsOrProcessorRanges(int version, ProcessorsPrecision processorsPrecision)
154+
throws Exception {
81155
final List<DesiredNode> nodes;
82156
if (randomBoolean()) {
83157
nodes = getNodeNames().stream()
84158
.map(
85159
nodeName -> new DesiredNode(
86160
Settings.builder().put(NODE_NAME_SETTING.getKey(), nodeName).build(),
87-
0.5f,
161+
processorsPrecision == ProcessorsPrecision.DOUBLE ? randomDoubleProcessorCount() : randomFloatProcessorCount(),
88162
ByteSizeValue.ofGb(randomIntBetween(10, 24)),
89163
ByteSizeValue.ofGb(randomIntBetween(128, 256)),
90164
Version.CURRENT
91165
)
92166
)
93167
.toList();
94168
} else {
95-
nodes = getNodeNames().stream()
96-
.map(
97-
nodeName -> new DesiredNode(
98-
Settings.builder().put(NODE_NAME_SETTING.getKey(), nodeName).build(),
99-
new DesiredNode.ProcessorsRange(randomIntBetween(1, 10), (float) randomIntBetween(20, 30)),
100-
ByteSizeValue.ofGb(randomIntBetween(10, 24)),
101-
ByteSizeValue.ofGb(randomIntBetween(128, 256)),
102-
Version.CURRENT
103-
)
104-
)
105-
.toList();
169+
nodes = getNodeNames().stream().map(nodeName -> {
170+
double minProcessors = processorsPrecision == ProcessorsPrecision.DOUBLE
171+
? randomDoubleProcessorCount()
172+
: randomFloatProcessorCount();
173+
return new DesiredNode(
174+
Settings.builder().put(NODE_NAME_SETTING.getKey(), nodeName).build(),
175+
new DesiredNode.ProcessorsRange(minProcessors, minProcessors + randomIntBetween(10, 20)),
176+
ByteSizeValue.ofGb(randomIntBetween(10, 24)),
177+
ByteSizeValue.ofGb(randomIntBetween(128, 256)),
178+
Version.CURRENT
179+
);
180+
}).toList();
106181
}
107182
updateDesiredNodes(nodes, version);
108183
}
@@ -123,7 +198,7 @@ private void addClusterNodesToDesiredNodesWithIntegerProcessors(int version) thr
123198
}
124199

125200
private void updateDesiredNodes(List<DesiredNode> nodes, int version) throws IOException {
126-
final var request = new Request("PUT", "/_internal/desired_nodes/history/" + version);
201+
final var request = new Request("PUT", "/_internal/desired_nodes/upgrade_test/" + version);
127202
try (var builder = JsonXContent.contentBuilder()) {
128203
builder.startObject();
129204
builder.xContentList(UpdateDesiredNodesRequest.NODES_FIELD.getPreferredName(), nodes);
@@ -149,6 +224,14 @@ private List<String> getNodeNames() throws Exception {
149224
return nodeNames;
150225
}
151226

227+
private double randomDoubleProcessorCount() {
228+
return randomDoubleBetween(0.5, 512.1234, true);
229+
}
230+
231+
private float randomFloatProcessorCount() {
232+
return randomIntBetween(1, 512) + randomFloat();
233+
}
234+
152235
@SuppressWarnings("unchecked")
153236
private static <T> T extractValue(Map<String, Object> map, String path) {
154237
return (T) XContentMapValues.extractValue(path, map);

server/src/internalClusterTest/java/org/elasticsearch/nodesinfo/SimpleNodesInfoIT.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ public void testNodesInfosTotalIndexingBuffer() throws Exception {
104104

105105
public void testAllocatedProcessors() throws Exception {
106106
List<String> nodesIds = internalCluster().startNodes(
107-
Settings.builder().put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), 3).build(),
108-
Settings.builder().put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), 6).build()
107+
Settings.builder().put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), 2.9).build(),
108+
Settings.builder().put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), 5.9).build()
109109
);
110110

111111
final String node_1 = nodesIds.get(0);
@@ -134,6 +134,8 @@ public void testAllocatedProcessors() throws Exception {
134134
);
135135

136136
assertThat(response.getNodesMap().get(server1NodeId).getInfo(OsInfo.class).getAllocatedProcessors(), equalTo(3));
137+
assertThat(response.getNodesMap().get(server1NodeId).getInfo(OsInfo.class).getFractionalAllocatedProcessors(), equalTo(2.9));
137138
assertThat(response.getNodesMap().get(server2NodeId).getInfo(OsInfo.class).getAllocatedProcessors(), equalTo(6));
139+
assertThat(response.getNodesMap().get(server2NodeId).getInfo(OsInfo.class).getFractionalAllocatedProcessors(), equalTo(5.9));
138140
}
139141
}

server/src/main/java/org/elasticsearch/action/admin/cluster/desirednodes/TransportUpdateDesiredNodesAction.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ protected void doExecute(Task task, UpdateDesiredNodesRequest request, ActionLis
100100
if (request.isCompatibleWithVersion(minNodeVersion) == false) {
101101
listener.onFailure(
102102
new IllegalArgumentException(
103-
"Unable to use processor ranges or floating-point processors in mixed-clusters with nodes in version: " + minNodeVersion
103+
"Unable to use processor ranges, floating-point (with greater precision) processors "
104+
+ "in mixed-clusters with nodes in version: "
105+
+ minNodeVersion
104106
)
105107
);
106108
return;
@@ -124,7 +126,7 @@ static DesiredNodes updateDesiredNodes(DesiredNodes latestDesiredNodes, UpdateDe
124126
);
125127

126128
if (latestDesiredNodes != null) {
127-
if (latestDesiredNodes.equals(proposedDesiredNodes)) {
129+
if (latestDesiredNodes.equalsWithProcessorsCloseTo(proposedDesiredNodes)) {
128130
return latestDesiredNodes;
129131
}
130132

server/src/main/java/org/elasticsearch/action/admin/cluster/desirednodes/UpdateDesiredNodesRequest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ public UpdateDesiredNodesRequest(StreamInput in) throws IOException {
5858
this.historyID = in.readString();
5959
this.version = in.readLong();
6060
this.nodes = in.readList(DesiredNode::readFrom);
61-
dryRun = in.getVersion().onOrAfter(DRY_RUN_VERSION) ? in.readBoolean() : false;
61+
if (in.getVersion().onOrAfter(DRY_RUN_VERSION)) {
62+
this.dryRun = in.readBoolean();
63+
} else {
64+
this.dryRun = false;
65+
}
6266
}
6367

6468
@Override
@@ -98,6 +102,7 @@ public boolean isCompatibleWithVersion(Version version) {
98102
if (version.onOrAfter(DesiredNode.RANGE_FLOAT_PROCESSORS_SUPPORT_VERSION)) {
99103
return true;
100104
}
105+
101106
return nodes.stream().allMatch(desiredNode -> desiredNode.isCompatibleWithVersion(version));
102107
}
103108

server/src/main/java/org/elasticsearch/cluster/desirednodes/DesiredNodesSettingsValidator.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,13 @@ private void validate(DesiredNode node) {
9292
int minProcessors = node.roundedDownMinProcessors();
9393
Integer roundedUpMaxProcessors = node.roundedUpMaxProcessors();
9494
int maxProcessors = roundedUpMaxProcessors == null ? minProcessors : roundedUpMaxProcessors;
95-
Setting.intSetting(NODE_PROCESSORS_SETTING.getKey(), minProcessors, 1, maxProcessors, Setting.Property.NodeScope).get(settings);
95+
Setting.doubleSetting(
96+
NODE_PROCESSORS_SETTING.getKey(),
97+
minProcessors,
98+
Double.MIN_VALUE,
99+
maxProcessors,
100+
Setting.Property.NodeScope
101+
).get(settings);
96102
final Settings.Builder updatedSettings = Settings.builder().put(settings);
97103
updatedSettings.remove(NODE_PROCESSORS_SETTING.getKey());
98104
settings = updatedSettings.build();

0 commit comments

Comments
 (0)