Skip to content

Commit b305454

Browse files
authored
Add new flag to check whether alias exists on remove (#58100)
This allows doing true CAS operations on aliases, making sure that an alias is actually properly moved from a given source index onto a given target index. This is useful to ensure that an alias is actually moved from a given index to another one, and not just added to another index.
1 parent 1f6c953 commit b305454

File tree

7 files changed

+90
-6
lines changed

7 files changed

+90
-6
lines changed

docs/reference/indices/aliases.asciidoc

+4
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ unless overriden in the request using the `expand_wildcards` parameter,
113113
similar to <<index-hidden,hidden indices>>. This property must be set to the
114114
same value on all indices that share an alias. Defaults to `false`.
115115

116+
`must_exist`::
117+
(Optional, boolean)
118+
If `true`, the alias to remove must exist. Defaults to `false`.
119+
116120
`is_write_index`::
117121
(Optional, boolean)
118122
If `true`, assigns the index as an alias's write index.

server/src/main/java/org/elasticsearch/action/admin/indices/alias/IndicesAliasesRequest.java

+23
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public static class AliasActions implements AliasesRequest, Writeable, ToXConten
9898
private static final ParseField SEARCH_ROUTING = new ParseField("search_routing", "searchRouting", "search-routing");
9999
private static final ParseField IS_WRITE_INDEX = new ParseField("is_write_index");
100100
private static final ParseField IS_HIDDEN = new ParseField("is_hidden");
101+
private static final ParseField MUST_EXIST = new ParseField("must_exist");
101102

102103
private static final ParseField ADD = new ParseField("add");
103104
private static final ParseField REMOVE = new ParseField("remove");
@@ -195,6 +196,7 @@ private static ObjectParser<AliasActions, Void> parser(String name, Supplier<Ali
195196
ADD_PARSER.declareField(AliasActions::searchRouting, XContentParser::text, SEARCH_ROUTING, ValueType.INT);
196197
ADD_PARSER.declareField(AliasActions::writeIndex, XContentParser::booleanValue, IS_WRITE_INDEX, ValueType.BOOLEAN);
197198
ADD_PARSER.declareField(AliasActions::isHidden, XContentParser::booleanValue, IS_HIDDEN, ValueType.BOOLEAN);
199+
ADD_PARSER.declareField(AliasActions::mustExist, XContentParser::booleanValue, MUST_EXIST, ValueType.BOOLEAN);
198200
}
199201
private static final ObjectParser<AliasActions, Void> REMOVE_PARSER = parser(REMOVE.getPreferredName(), AliasActions::remove);
200202
private static final ObjectParser<AliasActions, Void> REMOVE_INDEX_PARSER = parser(REMOVE_INDEX.getPreferredName(),
@@ -234,6 +236,7 @@ private static ObjectParser<AliasActions, Void> parser(String name, Supplier<Ali
234236
private String searchRouting;
235237
private Boolean writeIndex;
236238
private Boolean isHidden;
239+
private Boolean mustExist;
237240

238241
public AliasActions(AliasActions.Type type) {
239242
this.type = type;
@@ -255,6 +258,11 @@ public AliasActions(StreamInput in) throws IOException {
255258
isHidden = in.readOptionalBoolean();
256259
}
257260
originalAliases = in.readStringArray();
261+
if (in.getVersion().onOrAfter(Version.V_8_0_0)) {
262+
mustExist = in.readOptionalBoolean();
263+
} else {
264+
mustExist = null;
265+
}
258266
}
259267

260268
@Override
@@ -271,6 +279,9 @@ public void writeTo(StreamOutput out) throws IOException {
271279
out.writeOptionalBoolean(isHidden);
272280
}
273281
out.writeStringArray(originalAliases);
282+
if (out.getVersion().onOrAfter(Version.V_8_0_0)) {
283+
out.writeOptionalBoolean(mustExist);
284+
}
274285
}
275286

276287
/**
@@ -455,6 +466,18 @@ public Boolean isHidden() {
455466
return isHidden;
456467
}
457468

469+
public AliasActions mustExist(Boolean mustExist) {
470+
if (type != Type.REMOVE) {
471+
throw new IllegalArgumentException("[" + MUST_EXIST.getPreferredName() + "] is unsupported for [" + type + "]");
472+
}
473+
this.mustExist = mustExist;
474+
return this;
475+
}
476+
477+
public Boolean mustExist() {
478+
return mustExist;
479+
}
480+
458481
@Override
459482
public String[] aliases() {
460483
return aliases;

server/src/main/java/org/elasticsearch/action/admin/indices/alias/TransportIndicesAliasesAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ protected void masterOperation(Task task, final IndicesAliasesRequest request, f
131131
break;
132132
case REMOVE:
133133
for (String alias : concreteAliases(action, state.metadata(), index.getName())) {
134-
finalActions.add(new AliasAction.Remove(index.getName(), alias));
134+
finalActions.add(new AliasAction.Remove(index.getName(), alias, action.mustExist()));
135135
}
136136
break;
137137
case REMOVE_INDEX:

server/src/main/java/org/elasticsearch/action/admin/indices/rollover/MetadataRolloverService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ static List<AliasAction> rolloverAliasToNewIndex(String oldIndex, String newInde
227227
} else {
228228
return List.of(
229229
new AliasAction.Add(newIndex, alias, null, null, null, null, isHidden),
230-
new AliasAction.Remove(oldIndex, alias));
230+
new AliasAction.Remove(oldIndex, alias, null));
231231
}
232232
}
233233

server/src/main/java/org/elasticsearch/cluster/metadata/AliasAction.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -149,16 +149,19 @@ boolean apply(NewAliasValidator aliasValidator, Metadata.Builder metadata, Index
149149
*/
150150
public static class Remove extends AliasAction {
151151
private final String alias;
152+
@Nullable
153+
private final Boolean mustExist;
152154

153155
/**
154156
* Build the operation.
155157
*/
156-
public Remove(String index, String alias) {
158+
public Remove(String index, String alias, @Nullable Boolean mustExist) {
157159
super(index);
158160
if (false == Strings.hasText(alias)) {
159161
throw new IllegalArgumentException("[alias] is required");
160162
}
161163
this.alias = alias;
164+
this.mustExist = mustExist;
162165
}
163166

164167
/**
@@ -176,6 +179,9 @@ boolean removeIndex() {
176179
@Override
177180
boolean apply(NewAliasValidator aliasValidator, Metadata.Builder metadata, IndexMetadata index) {
178181
if (false == index.getAliases().containsKey(alias)) {
182+
if (mustExist != null && mustExist.booleanValue()) {
183+
throw new IllegalArgumentException("required alias [" + alias + "] does not exist");
184+
}
179185
return false;
180186
}
181187
metadata.put(IndexMetadata.builder(index).removeAlias(alias));

server/src/test/java/org/elasticsearch/action/admin/indices/alias/AliasActionsTests.java

+11
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,17 @@ public void testBadOptionsInNonIndex() {
108108
assertEquals("[filter] is unsupported for [" + action.actionType() + "]", e.getMessage());
109109
}
110110

111+
public void testMustExistOption() {
112+
final boolean mustExist = randomBoolean();
113+
AliasActions removeAliasAction = AliasActions.remove();
114+
assertNull(removeAliasAction.mustExist());
115+
removeAliasAction.mustExist(mustExist);
116+
assertEquals(mustExist, removeAliasAction.mustExist());
117+
AliasActions action = randomBoolean() ? AliasActions.add() : AliasActions.removeIndex();
118+
Exception e = expectThrows(IllegalArgumentException.class, () -> action.mustExist(mustExist));
119+
assertEquals("[must_exist] is unsupported for [" + action.actionType() + "]", e.getMessage());
120+
}
121+
111122
public void testParseAdd() throws IOException {
112123
String[] indices = generateRandomStringArray(10, 5, false, false);
113124
String[] aliases = generateRandomStringArray(10, 5, false, false);

server/src/test/java/org/elasticsearch/cluster/metadata/MetadataIndexAliasesServiceTests.java

+43-3
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public void testAddAndRemove() {
8484
// Remove the alias from it while adding another one
8585
before = after;
8686
after = service.applyAliasActions(before, Arrays.asList(
87-
new AliasAction.Remove(index, "test"),
87+
new AliasAction.Remove(index, "test", null),
8888
new AliasAction.Add(index, "test_2", null, null, null, null, null)));
8989
assertNull(after.metadata().getIndicesLookup().get("test"));
9090
alias = after.metadata().getIndicesLookup().get("test_2");
@@ -95,12 +95,52 @@ public void testAddAndRemove() {
9595

9696
// Now just remove on its own
9797
before = after;
98-
after = service.applyAliasActions(before, singletonList(new AliasAction.Remove(index, "test_2")));
98+
after = service.applyAliasActions(before, singletonList(new AliasAction.Remove(index, "test_2", randomBoolean())));
9999
assertNull(after.metadata().getIndicesLookup().get("test"));
100100
assertNull(after.metadata().getIndicesLookup().get("test_2"));
101101
assertAliasesVersionIncreased(index, before, after);
102102
}
103103

104+
public void testMustExist() {
105+
// Create a state with a single index
106+
String index = randomAlphaOfLength(5);
107+
ClusterState before = createIndex(ClusterState.builder(ClusterName.DEFAULT).build(), index);
108+
109+
// Add an alias to it
110+
ClusterState after = service.applyAliasActions(before, singletonList(new AliasAction.Add(index, "test", null, null, null, null,
111+
null)));
112+
IndexAbstraction alias = after.metadata().getIndicesLookup().get("test");
113+
assertNotNull(alias);
114+
assertThat(alias.getType(), equalTo(IndexAbstraction.Type.ALIAS));
115+
assertThat(alias.getIndices(), contains(after.metadata().index(index)));
116+
assertAliasesVersionIncreased(index, before, after);
117+
118+
// Remove the alias from it with mustExist == true while adding another one
119+
before = after;
120+
after = service.applyAliasActions(before, Arrays.asList(
121+
new AliasAction.Remove(index, "test", true),
122+
new AliasAction.Add(index, "test_2", null, null, null, null, null)));
123+
assertNull(after.metadata().getIndicesLookup().get("test"));
124+
alias = after.metadata().getIndicesLookup().get("test_2");
125+
assertNotNull(alias);
126+
assertThat(alias.getType(), equalTo(IndexAbstraction.Type.ALIAS));
127+
assertThat(alias.getIndices(), contains(after.metadata().index(index)));
128+
assertAliasesVersionIncreased(index, before, after);
129+
130+
// Now just remove on its own
131+
before = after;
132+
after = service.applyAliasActions(before, singletonList(new AliasAction.Remove(index, "test_2", randomBoolean())));
133+
assertNull(after.metadata().getIndicesLookup().get("test"));
134+
assertNull(after.metadata().getIndicesLookup().get("test_2"));
135+
assertAliasesVersionIncreased(index, before, after);
136+
137+
// Show that removing non-existing alias with mustExist == true fails
138+
final ClusterState finalCS = after;
139+
final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
140+
() -> service.applyAliasActions(finalCS, singletonList(new AliasAction.Remove(index, "test_2", true))));
141+
assertThat(iae.getMessage(), containsString("required alias [test_2] does not exist"));
142+
}
143+
104144
public void testMultipleIndices() {
105145
final var length = randomIntBetween(2, 8);
106146
final var indices = new HashSet<String>(length);
@@ -183,7 +223,7 @@ public void testAliasesVersionUnchangedWhenActionsAreIdempotent() {
183223
// now perform a remove and add for each alias which is idempotent, the resulting aliases are unchanged
184224
final var removeAndAddActions = new ArrayList<AliasAction>(2 * length);
185225
for (final var aliasName : aliasNames) {
186-
removeAndAddActions.add(new AliasAction.Remove(index, aliasName));
226+
removeAndAddActions.add(new AliasAction.Remove(index, aliasName, null));
187227
removeAndAddActions.add(new AliasAction.Add(index, aliasName, null, null, null, null, null));
188228
}
189229
final ClusterState afterRemoveAndAddAlias = service.applyAliasActions(afterAddingAlias, removeAndAddActions);

0 commit comments

Comments
 (0)