Skip to content

Commit 6216b63

Browse files
committed
add yes-not-preferred allocation decision
1 parent a19a74e commit 6216b63

File tree

5 files changed

+183
-56
lines changed

5 files changed

+183
-56
lines changed

server/src/main/java/org/elasticsearch/cluster/routing/allocation/decider/AllocationDeciders.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,19 +205,26 @@ private Decision withDeciders(
205205
BiFunction<String, Decision, String> logMessageCreator
206206
) {
207207
if (debugMode == RoutingAllocation.DebugMode.OFF) {
208-
Decision result = Decision.YES;
208+
Decision finalDecision = Decision.YES;
209+
Decision.Preferred preferred = Decision.Preferred.YES;
209210
for (AllocationDecider decider : deciders) {
210211
var decision = deciderAction.apply(decider);
211212
if (decision.type() == Decision.Type.NO) {
212213
if (logger.isTraceEnabled()) {
213214
logger.trace(() -> logMessageCreator.apply(decider.getClass().getSimpleName(), decision));
214215
}
215216
return decision;
216-
} else if (result.type() == Decision.Type.YES && decision.type() == Decision.Type.THROTTLE) {
217-
result = decision;
217+
} else if (finalDecision.type() == Decision.Type.YES && decision.type() == Decision.Type.THROTTLE) {
218+
finalDecision = decision;
219+
} else if (finalDecision.preferred() == Decision.Preferred.NO) {
220+
preferred = Decision.Preferred.NO;
218221
}
219222
}
220-
return result;
223+
if (finalDecision.type() == Decision.Type.YES && preferred == Decision.Preferred.NO) {
224+
return Decision.YES_NOT_PREFERRED;
225+
} else {
226+
return finalDecision;
227+
}
221228
} else {
222229
var result = new Decision.Multi();
223230
for (AllocationDecider decider : deciders) {
@@ -227,6 +234,8 @@ private Decision withDeciders(
227234
}
228235
if (decision != Decision.ALWAYS && (debugMode == RoutingAllocation.DebugMode.ON || decision.type() != Decision.Type.YES)) {
229236
result.add(decision);
237+
} else if (decision.preferred() == Decision.Preferred.NO) {
238+
result.add(decision);
230239
}
231240
}
232241
return result;

server/src/main/java/org/elasticsearch/cluster/routing/allocation/decider/Decision.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public sealed interface Decision extends ToXContent, Writeable permits Decision.
3333

3434
Single ALWAYS = new Single(Type.YES);
3535
Single YES = new Single(Type.YES);
36+
Single YES_NOT_PREFERRED = new Single(Type.YES, Preferred.NO);
3637
Single NO = new Single(Type.NO);
3738
Single THROTTLE = new Single(Type.THROTTLE);
3839

@@ -48,6 +49,28 @@ static Decision single(Type type, @Nullable String label, @Nullable String expla
4849
return new Single(type, label, explanation, explanationParams);
4950
}
5051

52+
/**
53+
* Creates YES/PREFERRED decision
54+
* @param label label for the Decider that produced this decision
55+
* @param explanation explanation of the decision
56+
* @param explanationParams additional parameters for the decision
57+
* @return new {@link Decision} instance
58+
*/
59+
static Decision preferred(@Nullable String label, @Nullable String explanation, @Nullable Object... explanationParams) {
60+
return new Single(Type.YES, Preferred.YES, label, explanation, explanationParams);
61+
}
62+
63+
/**
64+
* Creates YES/NOT-PREFERRED decision
65+
* @param label label for the Decider that produced this decision
66+
* @param explanation explanation of the decision
67+
* @param explanationParams additional parameters for the decision
68+
* @return new {@link Decision} instance
69+
*/
70+
static Decision notPreferred(@Nullable String label, @Nullable String explanation, @Nullable Object... explanationParams) {
71+
return new Single(Type.YES, Preferred.NO, label, explanation, explanationParams);
72+
}
73+
5174
static Decision readFrom(StreamInput in) throws IOException {
5275
// Determine whether to read a Single or Multi Decision
5376
if (in.readBoolean()) {
@@ -85,6 +108,11 @@ private static Single readSingleFrom(StreamInput in) throws IOException {
85108
*/
86109
Type type();
87110

111+
/**
112+
* @return {@link Preferred}, a soft yes/no
113+
*/
114+
Preferred preferred();
115+
88116
/**
89117
* Get the description label for this decision.
90118
*/
@@ -149,10 +177,18 @@ public static Type min(Type a, Type b) {
149177

150178
}
151179

180+
/**
181+
* Preferred indicates if allocation is favorable. It's a soft YES/NO, means allocation is still possible when not-preferred.
182+
*/
183+
enum Preferred {
184+
NO,
185+
YES
186+
}
187+
152188
/**
153189
* Simple class representing a single decision
154190
*/
155-
record Single(Type type, String label, String explanationString) implements Decision, ToXContentObject {
191+
record Single(Type type, Preferred preferred, String label, String explanationString) implements Decision, ToXContentObject {
156192
/**
157193
* Creates a new {@link Single} decision of a given type
158194
* @param type {@link Type} of the decision
@@ -161,6 +197,10 @@ private Single(Type type) {
161197
this(type, null, null, (Object[]) null);
162198
}
163199

200+
private Single(Type type, Preferred preferred) {
201+
this(type, preferred, null, null, (Object[]) null);
202+
}
203+
164204
/**
165205
* Creates a new {@link Single} decision of a given type
166206
*
@@ -169,8 +209,27 @@ private Single(Type type) {
169209
* @param explanationParams A set of additional parameters
170210
*/
171211
public Single(Type type, @Nullable String label, @Nullable String explanation, @Nullable Object... explanationParams) {
212+
this(type, type == Type.YES ? Preferred.YES : Preferred.NO, label, explanation, explanationParams);
213+
}
214+
215+
/**
216+
* Creates a new {@link Single} decision of a given type
217+
*
218+
* @param type {@link Type} of the decision
219+
* @param preferred {@link Preferred} soft yes/no
220+
* @param explanation An explanation of this {@link Decision}
221+
* @param explanationParams A set of additional parameters
222+
*/
223+
public Single(
224+
Type type,
225+
Preferred preferred,
226+
@Nullable String label,
227+
@Nullable String explanation,
228+
@Nullable Object... explanationParams
229+
) {
172230
this(
173231
type,
232+
preferred,
174233
label,
175234
explanationParams != null && explanationParams.length > 0
176235
? String.format(Locale.ROOT, explanation, explanationParams)
@@ -255,6 +314,16 @@ public Type type() {
255314
return ret;
256315
}
257316

317+
@Override
318+
public Preferred preferred() {
319+
for (var decision : decisions) {
320+
if (decision.preferred() == Preferred.NO) {
321+
return Preferred.NO;
322+
}
323+
}
324+
return Preferred.YES;
325+
}
326+
258327
@Override
259328
@Nullable
260329
public String label() {

server/src/main/java/org/elasticsearch/cluster/routing/allocation/decider/WriteLoadConstraintDecider.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, Routing
7373
shardRouting.shardId()
7474
);
7575
logger.debug(explain);
76-
return Decision.single(Decision.Type.NO, NAME, explain);
76+
return Decision.notPreferred(NAME, explain);
7777
}
7878

7979
if (calculateShardMovementChange(nodeWriteThreadPoolStats, shardWriteLoad) >= nodeWriteThreadPoolLoadThreshold) {
@@ -92,7 +92,7 @@ public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, Routing
9292
nodeWriteThreadPoolStats.totalThreadPoolThreads()
9393
);
9494
logger.debug(explain);
95-
return Decision.single(Decision.Type.NO, NAME, explain);
95+
return Decision.notPreferred(NAME, explain);
9696
}
9797

9898
return Decision.YES;
@@ -101,12 +101,12 @@ public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, Routing
101101
@Override
102102
public Decision canRemain(IndexMetadata indexMetadata, ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) {
103103
if (writeLoadConstraintSettings.getWriteLoadConstraintEnabled().notFullyEnabled()) {
104-
return Decision.single(Decision.Type.YES, NAME, "canRemain() is not enabled");
104+
return Decision.preferred(NAME, "canRemain() is not enabled");
105105
}
106106

107107
// TODO: implement
108108

109-
return Decision.single(Decision.Type.YES, NAME, "canRemain() is not yet implemented");
109+
return Decision.preferred(NAME, "canRemain() is not yet implemented");
110110
}
111111

112112
/**

server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/AllocationDecidersTests.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
public class AllocationDecidersTests extends ESAllocationTestCase {
4545

4646
public void testCheckAllDecidersBeforeReturningYes() {
47-
var allDecisions = generateDecisions(() -> Decision.YES);
47+
var allDecisions = generateDecisions(() -> randomFrom(Decision.YES));
4848
var debugMode = randomFrom(RoutingAllocation.DebugMode.values());
4949
var expectedDecision = switch (debugMode) {
5050
case OFF -> Decision.YES;
@@ -55,8 +55,18 @@ public void testCheckAllDecidersBeforeReturningYes() {
5555
verifyDecidersCall(debugMode, allDecisions, allDecisions.size(), expectedDecision);
5656
}
5757

58+
public void testCheckAllDecidersBeforeReturningYesNotPreferred() {
59+
var debugMode = randomFrom(RoutingAllocation.DebugMode.values());
60+
var allDecisions = generateDecisions(Decision.YES_NOT_PREFERRED, () -> randomFrom(Decision.YES));
61+
var expectedDecision = switch (debugMode) {
62+
case OFF -> Decision.YES_NOT_PREFERRED;
63+
case EXCLUDE_YES_DECISIONS, ON -> collectToMultiDecision(List.of(Decision.YES_NOT_PREFERRED));
64+
};
65+
verifyDecidersCall(debugMode, allDecisions, allDecisions.size(), expectedDecision);
66+
}
67+
5868
public void testCheckAllDecidersBeforeReturningThrottle() {
59-
var allDecisions = generateDecisions(Decision.THROTTLE, () -> Decision.YES);
69+
var allDecisions = generateDecisions(Decision.THROTTLE, () -> randomFrom(Decision.YES, Decision.YES_NOT_PREFERRED));
6070
var debugMode = randomFrom(RoutingAllocation.DebugMode.values());
6171
var expectedDecision = switch (debugMode) {
6272
case OFF -> Decision.THROTTLE;
@@ -69,7 +79,10 @@ public void testCheckAllDecidersBeforeReturningThrottle() {
6979

7080
public void testExitsAfterFirstNoDecision() {
7181
var expectedDecision = randomFrom(Decision.NO, Decision.single(Decision.Type.NO, "no with label", "explanation"));
72-
var allDecisions = generateDecisions(expectedDecision, () -> randomFrom(Decision.YES, Decision.THROTTLE));
82+
var allDecisions = generateDecisions(
83+
expectedDecision,
84+
() -> randomFrom(Decision.YES, Decision.YES_NOT_PREFERRED, Decision.THROTTLE)
85+
);
7386
var expectedCalls = allDecisions.indexOf(expectedDecision) + 1;
7487

7588
verifyDecidersCall(RoutingAllocation.DebugMode.OFF, allDecisions, expectedCalls, expectedDecision);
@@ -79,6 +92,7 @@ public void testCollectsAllDecisionsForDebugModeOn() {
7992
var allDecisions = generateDecisions(
8093
() -> randomFrom(
8194
Decision.YES,
95+
Decision.YES_NOT_PREFERRED,
8296
Decision.THROTTLE,
8397
Decision.single(Decision.Type.THROTTLE, "throttle with label", "explanation"),
8498
Decision.NO,
@@ -94,13 +108,17 @@ public void testCollectsNoAndThrottleDecisionsForDebugModeExcludeYesDecisions()
94108
var allDecisions = generateDecisions(
95109
() -> randomFrom(
96110
Decision.YES,
111+
Decision.YES_NOT_PREFERRED,
97112
Decision.THROTTLE,
98113
Decision.single(Decision.Type.THROTTLE, "throttle with label", "explanation"),
99114
Decision.NO,
100115
Decision.single(Decision.Type.NO, "no with label", "explanation")
101116
)
102117
);
103-
var expectedDecision = collectToMultiDecision(allDecisions, decision -> decision.type() != Decision.Type.YES);
118+
var expectedDecision = collectToMultiDecision(
119+
allDecisions,
120+
decision -> decision.type() != Decision.Type.YES || decision.preferred() != Decision.Preferred.YES
121+
);
104122

105123
verifyDecidersCall(RoutingAllocation.DebugMode.EXCLUDE_YES_DECISIONS, allDecisions, allDecisions.size(), expectedDecision);
106124
}

0 commit comments

Comments
 (0)