Skip to content

Commit ffeff40

Browse files
committed
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 e065d6c commit ffeff40

File tree

7 files changed

+90
-7
lines changed

7 files changed

+90
-7
lines changed

docs/reference/indices/aliases.asciidoc

Lines changed: 4 additions & 0 deletions
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

Lines changed: 23 additions & 1 deletion
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;
@@ -259,7 +262,11 @@ public AliasActions(StreamInput in) throws IOException {
259262
if (in.getVersion().onOrAfter(Version.V_7_0_0)) {
260263
originalAliases = in.readStringArray();
261264
}
262-
265+
if (in.getVersion().onOrAfter(Version.V_7_9_0)) {
266+
mustExist = in.readOptionalBoolean();
267+
} else {
268+
mustExist = null;
269+
}
263270
}
264271

265272
@Override
@@ -280,6 +287,9 @@ public void writeTo(StreamOutput out) throws IOException {
280287
if (out.getVersion().onOrAfter(Version.V_7_0_0)) {
281288
out.writeStringArray(originalAliases);
282289
}
290+
if (out.getVersion().onOrAfter(Version.V_7_9_0)) {
291+
out.writeOptionalBoolean(mustExist);
292+
}
283293
}
284294

285295
/**
@@ -464,6 +474,18 @@ public Boolean isHidden() {
464474
return isHidden;
465475
}
466476

477+
public AliasActions mustExist(Boolean mustExist) {
478+
if (type != Type.REMOVE) {
479+
throw new IllegalArgumentException("[" + MUST_EXIST.getPreferredName() + "] is unsupported for [" + type + "]");
480+
}
481+
this.mustExist = mustExist;
482+
return this;
483+
}
484+
485+
public Boolean mustExist() {
486+
return mustExist;
487+
}
488+
467489
@Override
468490
public String[] aliases() {
469491
return aliases;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ protected void masterOperation(final IndicesAliasesRequest request, final Cluste
130130
break;
131131
case REMOVE:
132132
for (String alias : concreteAliases(action, state.metadata(), index.getName())) {
133-
finalActions.add(new AliasAction.Remove(index.getName(), alias));
133+
finalActions.add(new AliasAction.Remove(index.getName(), alias, action.mustExist()));
134134
}
135135
break;
136136
case REMOVE_INDEX:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ static List<AliasAction> rolloverAliasToNewIndex(String oldIndex, String newInde
229229
} else {
230230
return Collections.unmodifiableList(Arrays.asList(
231231
new AliasAction.Add(newIndex, alias, null, null, null, null, isHidden),
232-
new AliasAction.Remove(oldIndex, alias)));
232+
new AliasAction.Remove(oldIndex, alias, null)));
233233
}
234234
}
235235

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

Lines changed: 7 additions & 1 deletion
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

Lines changed: 11 additions & 0 deletions
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

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void testAddAndRemove() {
8585
// Remove the alias from it while adding another one
8686
before = after;
8787
after = service.applyAliasActions(before, Arrays.asList(
88-
new AliasAction.Remove(index, "test"),
88+
new AliasAction.Remove(index, "test", null),
8989
new AliasAction.Add(index, "test_2", null, null, null, null, null)));
9090
assertNull(after.metadata().getIndicesLookup().get("test"));
9191
alias = after.metadata().getIndicesLookup().get("test_2");
@@ -96,12 +96,52 @@ public void testAddAndRemove() {
9696

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

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

0 commit comments

Comments
 (0)