Skip to content

Commit c650764

Browse files
authored
Use alias name from rollover request to query indices stats (#40774) (#41285)
In `TransportRolloverAction` before doing rollover we resolve source index name (write index) from the alias in the rollover request. Before evaluating the conditions and executing rollover action, we retrieve stats, but to do so we used the source index name resolved from the alias instead of alias from the index. This fails when the user is assigned a role with index privilege on the alias instead of the concrete index. This commit fixes this by using the alias from the request. After this change, verified that when we retrieve all the stats (including write + read indexes) we are considering only source index. Closes #40771
1 parent a8f18a4 commit c650764

File tree

3 files changed

+281
-8
lines changed

3 files changed

+281
-8
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ protected void masterOperation(final RolloverRequest rolloverRequest, final Clus
120120
final String rolloverIndexName = indexNameExpressionResolver.resolveDateMathExpression(unresolvedName);
121121
MetaDataCreateIndexService.validateIndexName(rolloverIndexName, state); // will fail if the index already exists
122122
checkNoDuplicatedAliasInIndexTemplate(metaData, rolloverIndexName, rolloverRequest.getAlias());
123-
client.admin().indices().prepareStats(sourceIndexName).clear().setDocs(true).execute(
123+
client.admin().indices().prepareStats(rolloverRequest.getAlias()).clear().setDocs(true).execute(
124124
new ActionListener<IndicesStatsResponse>() {
125125
@Override
126126
public void onResponse(IndicesStatsResponse statsResponse) {
@@ -249,7 +249,7 @@ static Map<String, Boolean> evaluateConditions(final Collection<Condition<?>> co
249249

250250
static Map<String, Boolean> evaluateConditions(final Collection<Condition<?>> conditions, final IndexMetaData metaData,
251251
final IndicesStatsResponse statsResponse) {
252-
return evaluateConditions(conditions, statsResponse.getPrimaries().getDocs(), metaData);
252+
return evaluateConditions(conditions, statsResponse.getIndex(metaData.getIndex().getName()).getPrimaries().getDocs(), metaData);
253253
}
254254

255255
static void validate(MetaData metaData, RolloverRequest request) {

server/src/test/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverActionTests.java

+140-6
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,30 @@
2020
package org.elasticsearch.action.admin.indices.rollover;
2121

2222
import org.elasticsearch.Version;
23+
import org.elasticsearch.action.ActionListener;
2324
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesClusterStateUpdateRequest;
2425
import org.elasticsearch.action.admin.indices.create.CreateIndexClusterStateUpdateRequest;
2526
import org.elasticsearch.action.admin.indices.stats.CommonStats;
27+
import org.elasticsearch.action.admin.indices.stats.IndexStats;
28+
import org.elasticsearch.action.admin.indices.stats.IndicesStatsRequestBuilder;
2629
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
30+
import org.elasticsearch.action.support.ActionFilters;
2731
import org.elasticsearch.action.support.ActiveShardCount;
32+
import org.elasticsearch.action.support.PlainActionFuture;
33+
import org.elasticsearch.client.AdminClient;
34+
import org.elasticsearch.client.Client;
35+
import org.elasticsearch.client.IndicesAdminClient;
36+
import org.elasticsearch.cluster.ClusterName;
37+
import org.elasticsearch.cluster.ClusterState;
2838
import org.elasticsearch.cluster.metadata.AliasAction;
2939
import org.elasticsearch.cluster.metadata.AliasMetaData;
3040
import org.elasticsearch.cluster.metadata.IndexMetaData;
3141
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
3242
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
3343
import org.elasticsearch.cluster.metadata.MetaData;
44+
import org.elasticsearch.cluster.metadata.MetaDataCreateIndexService;
45+
import org.elasticsearch.cluster.metadata.MetaDataIndexAliasesService;
46+
import org.elasticsearch.cluster.service.ClusterService;
3447
import org.elasticsearch.common.UUIDs;
3548
import org.elasticsearch.common.settings.Settings;
3649
import org.elasticsearch.common.unit.ByteSizeUnit;
@@ -39,9 +52,12 @@
3952
import org.elasticsearch.common.util.set.Sets;
4053
import org.elasticsearch.index.shard.DocsStats;
4154
import org.elasticsearch.test.ESTestCase;
55+
import org.elasticsearch.threadpool.ThreadPool;
56+
import org.elasticsearch.transport.TransportService;
4257
import org.mockito.ArgumentCaptor;
4358

4459
import java.util.Arrays;
60+
import java.util.HashMap;
4561
import java.util.List;
4662
import java.util.Locale;
4763
import java.util.Map;
@@ -51,7 +67,9 @@
5167
import static org.hamcrest.Matchers.containsString;
5268
import static org.hamcrest.Matchers.equalTo;
5369
import static org.hamcrest.Matchers.hasSize;
70+
import static org.hamcrest.Matchers.is;
5471
import static org.mockito.Matchers.any;
72+
import static org.mockito.Mockito.doAnswer;
5573
import static org.mockito.Mockito.mock;
5674
import static org.mockito.Mockito.verify;
5775
import static org.mockito.Mockito.when;
@@ -64,7 +82,9 @@ public void testDocStatsSelectionFromPrimariesOnly() {
6482
long docsInShards = 200;
6583

6684
final Condition<?> condition = createTestCondition();
67-
evaluateConditions(Sets.newHashSet(condition), createMetaData(), createIndicesStatResponse(docsInShards, docsInPrimaryShards));
85+
String indexName = randomAlphaOfLengthBetween(5, 7);
86+
evaluateConditions(Sets.newHashSet(condition), createMetaData(indexName),
87+
createIndicesStatResponse(indexName, docsInShards, docsInPrimaryShards));
6888
final ArgumentCaptor<Condition.Stats> argument = ArgumentCaptor.forClass(Condition.Stats.class);
6989
verify(condition).evaluate(argument.capture());
7090

@@ -286,36 +306,150 @@ public void testRejectDuplicateAlias() {
286306
.patterns(Arrays.asList("foo-*", "bar-*"))
287307
.putAlias(AliasMetaData.builder("foo-write")).putAlias(AliasMetaData.builder("bar-write").writeIndex(randomBoolean()))
288308
.build();
289-
final MetaData metaData = MetaData.builder().put(createMetaData(), false).put(template).build();
309+
final MetaData metaData = MetaData.builder().put(createMetaData(randomAlphaOfLengthBetween(5, 7)), false).put(template).build();
290310
String indexName = randomFrom("foo-123", "bar-xyz");
291311
String aliasName = randomFrom("foo-write", "bar-write");
292312
final IllegalArgumentException ex = expectThrows(IllegalArgumentException.class,
293313
() -> TransportRolloverAction.checkNoDuplicatedAliasInIndexTemplate(metaData, indexName, aliasName));
294314
assertThat(ex.getMessage(), containsString("index template [test-template]"));
295315
}
296316

297-
private IndicesStatsResponse createIndicesStatResponse(long totalDocs, long primaryDocs) {
317+
public void testConditionEvaluationWhenAliasToWriteAndReadIndicesConsidersOnlyPrimariesFromWriteIndex() {
318+
final TransportService mockTransportService = mock(TransportService.class);
319+
final ClusterService mockClusterService = mock(ClusterService.class);
320+
final ThreadPool mockThreadPool = mock(ThreadPool.class);
321+
final MetaDataCreateIndexService mockCreateIndexService = mock(MetaDataCreateIndexService.class);
322+
final IndexNameExpressionResolver mockIndexNameExpressionResolver = mock(IndexNameExpressionResolver.class);
323+
when(mockIndexNameExpressionResolver.resolveDateMathExpression(any())).thenReturn("logs-index-000003");
324+
final ActionFilters mockActionFilters = mock(ActionFilters.class);
325+
final MetaDataIndexAliasesService mdIndexAliasesService = mock(MetaDataIndexAliasesService.class);
326+
327+
final Client mockClient = mock(Client.class);
328+
final AdminClient mockAdminClient = mock(AdminClient.class);
329+
final IndicesAdminClient mockIndicesAdminClient = mock(IndicesAdminClient.class);
330+
when(mockClient.admin()).thenReturn(mockAdminClient);
331+
when(mockAdminClient.indices()).thenReturn(mockIndicesAdminClient);
332+
333+
final IndicesStatsRequestBuilder mockIndicesStatsBuilder = mock(IndicesStatsRequestBuilder.class);
334+
when(mockIndicesAdminClient.prepareStats(any())).thenReturn(mockIndicesStatsBuilder);
335+
final Map<String, IndexStats> indexStats = new HashMap<>();
336+
int total = randomIntBetween(500, 1000);
337+
indexStats.put("logs-index-000001", createIndexStats(200L, total));
338+
indexStats.put("logs-index-000002", createIndexStats(300L, total));
339+
final IndicesStatsResponse statsResponse = createAliasToMultipleIndicesStatsResponse(indexStats);
340+
when(mockIndicesStatsBuilder.clear()).thenReturn(mockIndicesStatsBuilder);
341+
when(mockIndicesStatsBuilder.setDocs(true)).thenReturn(mockIndicesStatsBuilder);
342+
343+
assert statsResponse.getPrimaries().getDocs().getCount() == 500L;
344+
assert statsResponse.getTotal().getDocs().getCount() == (total + total);
345+
346+
doAnswer(invocation -> {
347+
Object[] args = invocation.getArguments();
348+
assert args.length == 1;
349+
ActionListener<IndicesStatsResponse> listener = (ActionListener<IndicesStatsResponse>) args[0];
350+
listener.onResponse(statsResponse);
351+
return null;
352+
}).when(mockIndicesStatsBuilder).execute(any(ActionListener.class));
353+
354+
final IndexMetaData.Builder indexMetaData = IndexMetaData.builder("logs-index-000001")
355+
.putAlias(AliasMetaData.builder("logs-alias").writeIndex(false).build()).settings(settings(Version.CURRENT))
356+
.numberOfShards(1).numberOfReplicas(1);
357+
final IndexMetaData.Builder indexMetaData2 = IndexMetaData.builder("logs-index-000002")
358+
.putAlias(AliasMetaData.builder("logs-alias").writeIndex(true).build()).settings(settings(Version.CURRENT))
359+
.numberOfShards(1).numberOfReplicas(1);
360+
final ClusterState stateBefore = ClusterState.builder(ClusterName.DEFAULT)
361+
.metaData(MetaData.builder().put(indexMetaData).put(indexMetaData2)).build();
362+
363+
final TransportRolloverAction transportRolloverAction = new TransportRolloverAction(mockTransportService, mockClusterService,
364+
mockThreadPool, mockCreateIndexService, mockActionFilters, mockIndexNameExpressionResolver, mdIndexAliasesService,
365+
mockClient);
366+
367+
// For given alias, verify that condition evaluation fails when the condition doc count is greater than the primaries doc count
368+
// (primaries from only write index is considered)
369+
PlainActionFuture<RolloverResponse> future = new PlainActionFuture<>();
370+
RolloverRequest rolloverRequest = new RolloverRequest("logs-alias", "logs-index-000003");
371+
rolloverRequest.addMaxIndexDocsCondition(500L);
372+
rolloverRequest.dryRun(true);
373+
transportRolloverAction.masterOperation(rolloverRequest, stateBefore, future);
374+
375+
RolloverResponse response = future.actionGet();
376+
assertThat(response.getOldIndex(), equalTo("logs-index-000002"));
377+
assertThat(response.getNewIndex(), equalTo("logs-index-000003"));
378+
assertThat(response.isDryRun(), equalTo(true));
379+
assertThat(response.isRolledOver(), equalTo(false));
380+
assertThat(response.getConditionStatus().size(), equalTo(1));
381+
assertThat(response.getConditionStatus().get("[max_docs: 500]"), is(false));
382+
383+
// For given alias, verify that the condition evaluation is successful when condition doc count is less than the primaries doc count
384+
// (primaries from only write index is considered)
385+
future = new PlainActionFuture<>();
386+
rolloverRequest = new RolloverRequest("logs-alias", "logs-index-000003");
387+
rolloverRequest.addMaxIndexDocsCondition(300L);
388+
rolloverRequest.dryRun(true);
389+
transportRolloverAction.masterOperation(rolloverRequest, stateBefore, future);
390+
391+
response = future.actionGet();
392+
assertThat(response.getOldIndex(), equalTo("logs-index-000002"));
393+
assertThat(response.getNewIndex(), equalTo("logs-index-000003"));
394+
assertThat(response.isDryRun(), equalTo(true));
395+
assertThat(response.isRolledOver(), equalTo(false));
396+
assertThat(response.getConditionStatus().size(), equalTo(1));
397+
assertThat(response.getConditionStatus().get("[max_docs: 300]"), is(true));
398+
}
399+
400+
private IndicesStatsResponse createIndicesStatResponse(String indexName, long totalDocs, long primariesDocs) {
298401
final CommonStats primaryStats = mock(CommonStats.class);
299-
when(primaryStats.getDocs()).thenReturn(new DocsStats(primaryDocs, 0, between(1, 10000)));
402+
when(primaryStats.getDocs()).thenReturn(new DocsStats(primariesDocs, 0, between(1, 10000)));
300403

301404
final CommonStats totalStats = mock(CommonStats.class);
302405
when(totalStats.getDocs()).thenReturn(new DocsStats(totalDocs, 0, between(1, 10000)));
303406

304407
final IndicesStatsResponse response = mock(IndicesStatsResponse.class);
305408
when(response.getPrimaries()).thenReturn(primaryStats);
306409
when(response.getTotal()).thenReturn(totalStats);
410+
final IndexStats indexStats = mock(IndexStats.class);
411+
when(response.getIndex(indexName)).thenReturn(indexStats);
412+
when(indexStats.getPrimaries()).thenReturn(primaryStats);
413+
when(indexStats.getTotal()).thenReturn(totalStats);
414+
return response;
415+
}
416+
417+
private IndicesStatsResponse createAliasToMultipleIndicesStatsResponse(Map<String, IndexStats> indexStats) {
418+
final IndicesStatsResponse response = mock(IndicesStatsResponse.class);
419+
final CommonStats primariesStats = new CommonStats();
420+
final CommonStats totalStats = new CommonStats();
421+
for (String indexName : indexStats.keySet()) {
422+
when(response.getIndex(indexName)).thenReturn(indexStats.get(indexName));
423+
primariesStats.add(indexStats.get(indexName).getPrimaries());
424+
totalStats.add(indexStats.get(indexName).getTotal());
425+
}
307426

427+
when(response.getPrimaries()).thenReturn(primariesStats);
428+
when(response.getTotal()).thenReturn(totalStats);
308429
return response;
309430
}
310431

311-
private static IndexMetaData createMetaData() {
432+
private IndexStats createIndexStats(long primaries, long total) {
433+
final CommonStats primariesCommonStats = mock(CommonStats.class);
434+
when(primariesCommonStats.getDocs()).thenReturn(new DocsStats(primaries, 0, between(1, 10000)));
435+
436+
final CommonStats totalCommonStats = mock(CommonStats.class);
437+
when(totalCommonStats.getDocs()).thenReturn(new DocsStats(total, 0, between(1, 10000)));
438+
439+
IndexStats indexStats = mock(IndexStats.class);
440+
when(indexStats.getPrimaries()).thenReturn(primariesCommonStats);
441+
when(indexStats.getTotal()).thenReturn(totalCommonStats);
442+
return indexStats;
443+
}
444+
445+
private static IndexMetaData createMetaData(String indexName) {
312446
final Settings settings = Settings.builder()
313447
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
314448
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID())
315449
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
316450
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
317451
.build();
318-
return IndexMetaData.builder(randomAlphaOfLength(10))
452+
return IndexMetaData.builder(indexName)
319453
.creationDate(System.currentTimeMillis() - TimeValue.timeValueHours(3).getMillis())
320454
.settings(settings)
321455
.build();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
---
2+
3+
setup:
4+
- skip:
5+
features: headers
6+
7+
- do:
8+
cluster.health:
9+
wait_for_status: yellow
10+
11+
- do:
12+
security.put_role:
13+
name: "alias_write_manage_role"
14+
body: >
15+
{
16+
"indices": [
17+
{ "names": ["write_manage_alias"], "privileges": ["write", "manage"] }
18+
]
19+
}
20+
21+
- do:
22+
security.put_user:
23+
username: "test_user"
24+
body: >
25+
{
26+
"password" : "x-pack-test-password",
27+
"roles" : [ "alias_write_manage_role" ],
28+
"full_name" : "user with privileges to write, manage via alias"
29+
}
30+
31+
- do:
32+
indices.create:
33+
index: logs-000001
34+
body:
35+
settings:
36+
index:
37+
number_of_shards: 1
38+
number_of_replicas: 0
39+
40+
- do:
41+
indices.put_alias:
42+
index: logs-000001
43+
name: write_manage_alias
44+
45+
---
46+
teardown:
47+
- do:
48+
security.delete_user:
49+
username: "test_user"
50+
ignore: 404
51+
52+
- do:
53+
security.delete_role:
54+
name: "alias_write_role"
55+
ignore: 404
56+
57+
- do:
58+
indices.delete_alias:
59+
index: "logs-000001"
60+
name: [ "write_manage_alias" ]
61+
ignore: 404
62+
63+
- do:
64+
indices.delete:
65+
index: [ "logs-000001" ]
66+
ignore: 404
67+
68+
---
69+
"Test rollover, index via write alias of index":
70+
71+
# index using alias
72+
- do:
73+
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
74+
create:
75+
id: 1
76+
index: write_manage_alias
77+
body: >
78+
{
79+
"name" : "doc1"
80+
}
81+
82+
- do:
83+
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
84+
create:
85+
id: 2
86+
index: write_manage_alias
87+
body: >
88+
{
89+
"name" : "doc2"
90+
}
91+
92+
- do:
93+
indices.refresh: {}
94+
95+
# rollover using alias
96+
- do:
97+
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
98+
indices.rollover:
99+
alias: "write_manage_alias"
100+
wait_for_active_shards: 1
101+
body:
102+
conditions:
103+
max_docs: 1
104+
105+
- match: { old_index: logs-000001 }
106+
- match: { new_index: logs-000002 }
107+
- match: { rolled_over: true }
108+
- match: { dry_run: false }
109+
- match: { conditions: { "[max_docs: 1]": true } }
110+
111+
# ensure new index is created
112+
- do:
113+
indices.exists:
114+
index: logs-000002
115+
116+
- is_true: ''
117+
118+
# index using alias
119+
- do:
120+
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
121+
create:
122+
id: 3
123+
index: write_manage_alias
124+
body: >
125+
{
126+
"name" : "doc3"
127+
}
128+
129+
- do:
130+
indices.refresh: {}
131+
132+
# check alias points to the new index and the doc was indexed
133+
- do:
134+
search:
135+
rest_total_hits_as_int: true
136+
index: write_manage_alias
137+
138+
- match: { hits.total: 1 }
139+
- match: { hits.hits.0._index: "logs-000002"}

0 commit comments

Comments
 (0)