Skip to content

Commit 44c7c4b

Browse files
authored
[CCR] Add auto follow stats api (#33801)
GET /_ccr/auto_follow/stats Returns: ``` { "number_of_successful_follow_indices": ... "number_of_failed_follow_indices": ... "number_of_failed_remote_cluster_state_requests": ... "recent_auto_follow_errors": [ ... ] } ``` Relates to #33007
1 parent 1f1ebb4 commit 44c7c4b

File tree

11 files changed

+311
-5
lines changed

11 files changed

+311
-5
lines changed

x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/FollowIndexIT.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ public void testAutoFollowPatterns() throws Exception {
118118
}
119119

120120
assertBusy(() -> {
121+
Request statsRequest = new Request("GET", "/_ccr/auto_follow/stats");
122+
Map<String, ?> response = toMap(client().performRequest(statsRequest));
123+
assertThat(response.get("number_of_successful_follow_indices"), equalTo(1));
124+
121125
ensureYellow("logs-20190101");
122126
verifyDocuments("logs-20190101", 5);
123127
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"Test autofollow stats":
3+
- do:
4+
ccr.auto_follow_stats: {}
5+
6+
- match: { number_of_successful_follow_indices: 0 }
7+
- match: { number_of_failed_follow_indices: 0 }
8+
- match: { number_of_failed_remote_cluster_state_requests: 0 }
9+
- length: { recent_auto_follow_errors: 0 }
10+

x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/Ccr.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
import org.elasticsearch.threadpool.ThreadPool;
4141
import org.elasticsearch.watcher.ResourceWatcherService;
4242
import org.elasticsearch.xpack.ccr.action.AutoFollowCoordinator;
43+
import org.elasticsearch.xpack.ccr.action.TransportAutoFollowStatsAction;
44+
import org.elasticsearch.xpack.ccr.rest.RestAutoFollowStatsAction;
45+
import org.elasticsearch.xpack.core.ccr.action.AutoFollowStatsAction;
4346
import org.elasticsearch.xpack.core.ccr.action.DeleteAutoFollowPatternAction;
4447
import org.elasticsearch.xpack.core.ccr.action.PutAutoFollowPatternAction;
4548
import org.elasticsearch.xpack.ccr.action.ShardChangesAction;
@@ -153,6 +156,7 @@ public List<PersistentTasksExecutor<?>> getPersistentTasksExecutor(ClusterServic
153156
new ActionHandler<>(ShardChangesAction.INSTANCE, ShardChangesAction.TransportAction.class),
154157
// stats action
155158
new ActionHandler<>(CcrStatsAction.INSTANCE, TransportCcrStatsAction.class),
159+
new ActionHandler<>(AutoFollowStatsAction.INSTANCE, TransportAutoFollowStatsAction.class),
156160
// follow actions
157161
new ActionHandler<>(CreateAndFollowIndexAction.INSTANCE, TransportCreateAndFollowIndexAction.class),
158162
new ActionHandler<>(FollowIndexAction.INSTANCE, TransportFollowIndexAction.class),
@@ -173,6 +177,7 @@ public List<RestHandler> getRestHandlers(Settings settings, RestController restC
173177
return Arrays.asList(
174178
// stats API
175179
new RestCcrStatsAction(settings, restController),
180+
new RestAutoFollowStatsAction(settings, restController),
176181
// follow APIs
177182
new RestCreateAndFollowIndexAction(settings, restController),
178183
new RestFollowIndexAction(settings, restController),

x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/action/AutoFollowCoordinator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.apache.logging.log4j.Logger;
1010
import org.apache.logging.log4j.message.ParameterizedMessage;
1111
import org.elasticsearch.ElasticsearchException;
12+
import org.elasticsearch.ExceptionsHelper;
1213
import org.elasticsearch.action.ActionListener;
1314
import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest;
1415
import org.elasticsearch.client.Client;
@@ -114,7 +115,7 @@ synchronized void updateStats(List<AutoFollowResult> results) {
114115
if (entry.getValue() != null) {
115116
numberOfFailedIndicesAutoFollowed++;
116117
recentAutoFollowErrors.put(result.clusterAlias + ":" + entry.getKey().getName(),
117-
new ElasticsearchException(entry.getValue()));
118+
ExceptionsHelper.convertToElastic(entry.getValue()));
118119
LOGGER.warn(new ParameterizedMessage("failure occurred while auto following index [{}] in leader cluster [{}]",
119120
entry.getKey(), result.clusterAlias), entry.getValue());
120121
} else {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.ccr.action;
8+
9+
import org.elasticsearch.action.ActionListener;
10+
import org.elasticsearch.action.support.ActionFilters;
11+
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
12+
import org.elasticsearch.cluster.ClusterState;
13+
import org.elasticsearch.cluster.block.ClusterBlockException;
14+
import org.elasticsearch.cluster.block.ClusterBlockLevel;
15+
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
16+
import org.elasticsearch.cluster.service.ClusterService;
17+
import org.elasticsearch.common.inject.Inject;
18+
import org.elasticsearch.common.settings.Settings;
19+
import org.elasticsearch.license.LicenseUtils;
20+
import org.elasticsearch.tasks.Task;
21+
import org.elasticsearch.threadpool.ThreadPool;
22+
import org.elasticsearch.transport.TransportService;
23+
import org.elasticsearch.xpack.ccr.Ccr;
24+
import org.elasticsearch.xpack.ccr.CcrLicenseChecker;
25+
import org.elasticsearch.xpack.core.ccr.AutoFollowStats;
26+
import org.elasticsearch.xpack.core.ccr.action.AutoFollowStatsAction;
27+
28+
import java.util.Objects;
29+
30+
public class TransportAutoFollowStatsAction
31+
extends TransportMasterNodeAction<AutoFollowStatsAction.Request, AutoFollowStatsAction.Response> {
32+
33+
private final CcrLicenseChecker ccrLicenseChecker;
34+
private final AutoFollowCoordinator autoFollowCoordinator;
35+
36+
@Inject
37+
public TransportAutoFollowStatsAction(
38+
Settings settings,
39+
TransportService transportService,
40+
ClusterService clusterService,
41+
ThreadPool threadPool,
42+
ActionFilters actionFilters,
43+
IndexNameExpressionResolver indexNameExpressionResolver,
44+
AutoFollowCoordinator autoFollowCoordinator,
45+
CcrLicenseChecker ccrLicenseChecker
46+
) {
47+
super(
48+
settings,
49+
AutoFollowStatsAction.NAME,
50+
transportService,
51+
clusterService,
52+
threadPool,
53+
actionFilters,
54+
AutoFollowStatsAction.Request::new,
55+
indexNameExpressionResolver
56+
);
57+
this.ccrLicenseChecker = Objects.requireNonNull(ccrLicenseChecker);
58+
this.autoFollowCoordinator = Objects.requireNonNull(autoFollowCoordinator);
59+
}
60+
61+
@Override
62+
protected String executor() {
63+
return Ccr.CCR_THREAD_POOL_NAME;
64+
}
65+
66+
@Override
67+
protected AutoFollowStatsAction.Response newResponse() {
68+
return new AutoFollowStatsAction.Response();
69+
}
70+
71+
@Override
72+
protected void doExecute(Task task, AutoFollowStatsAction.Request request, ActionListener<AutoFollowStatsAction.Response> listener) {
73+
if (ccrLicenseChecker.isCcrAllowed() == false) {
74+
listener.onFailure(LicenseUtils.newComplianceException("ccr"));
75+
return;
76+
}
77+
super.doExecute(task, request, listener);
78+
}
79+
80+
@Override
81+
protected void masterOperation(
82+
AutoFollowStatsAction.Request request,
83+
ClusterState state,
84+
ActionListener<AutoFollowStatsAction.Response> listener
85+
) throws Exception {
86+
AutoFollowStats stats = autoFollowCoordinator.getStats();
87+
listener.onResponse(new AutoFollowStatsAction.Response(stats));
88+
}
89+
90+
@Override
91+
protected ClusterBlockException checkBlock(AutoFollowStatsAction.Request request, ClusterState state) {
92+
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ);
93+
}
94+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.ccr.rest;
8+
9+
import org.elasticsearch.client.node.NodeClient;
10+
import org.elasticsearch.common.settings.Settings;
11+
import org.elasticsearch.rest.BaseRestHandler;
12+
import org.elasticsearch.rest.RestController;
13+
import org.elasticsearch.rest.RestRequest;
14+
import org.elasticsearch.rest.action.RestToXContentListener;
15+
import org.elasticsearch.xpack.core.ccr.action.AutoFollowStatsAction;
16+
17+
import java.io.IOException;
18+
19+
public class RestAutoFollowStatsAction extends BaseRestHandler {
20+
21+
public RestAutoFollowStatsAction(final Settings settings, final RestController controller) {
22+
super(settings);
23+
controller.registerHandler(RestRequest.Method.GET, "/_ccr/auto_follow/stats", this);
24+
}
25+
26+
@Override
27+
public String getName() {
28+
return "ccr_auto_follow_stats";
29+
}
30+
31+
@Override
32+
protected RestChannelConsumer prepareRequest(final RestRequest restRequest, final NodeClient client) throws IOException {
33+
final AutoFollowStatsAction.Request request = new AutoFollowStatsAction.Request();
34+
return channel -> client.execute(AutoFollowStatsAction.INSTANCE, request, new RestToXContentListener<>(channel));
35+
}
36+
37+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.ccr.action;
7+
8+
import org.elasticsearch.test.AbstractStreamableTestCase;
9+
import org.elasticsearch.xpack.core.ccr.AutoFollowStats;
10+
import org.elasticsearch.xpack.core.ccr.action.AutoFollowStatsAction;
11+
12+
import static org.elasticsearch.xpack.ccr.action.AutoFollowStatsTests.randomReadExceptions;
13+
14+
public class AutoFollowStatsResponseTests extends AbstractStreamableTestCase<AutoFollowStatsAction.Response> {
15+
16+
@Override
17+
protected AutoFollowStatsAction.Response createBlankInstance() {
18+
return new AutoFollowStatsAction.Response();
19+
}
20+
21+
@Override
22+
protected AutoFollowStatsAction.Response createTestInstance() {
23+
AutoFollowStats autoFollowStats = new AutoFollowStats(
24+
randomNonNegativeLong(),
25+
randomNonNegativeLong(),
26+
randomNonNegativeLong(),
27+
randomReadExceptions()
28+
);
29+
return new AutoFollowStatsAction.Response(autoFollowStats);
30+
}
31+
}

x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/action/AutoFollowStatsTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ protected AutoFollowStats createTestInstance() {
3838
);
3939
}
4040

41-
private static NavigableMap<String, ElasticsearchException> randomReadExceptions() {
41+
static NavigableMap<String, ElasticsearchException> randomReadExceptions() {
4242
final int count = randomIntBetween(0, 16);
4343
final NavigableMap<String, ElasticsearchException> readExceptions = new TreeMap<>();
4444
for (int i = 0; i < count; i++) {

x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/action/AutoFollowTests.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import org.elasticsearch.plugins.Plugin;
1616
import org.elasticsearch.test.ESSingleNodeTestCase;
1717
import org.elasticsearch.xpack.ccr.LocalStateCcr;
18+
import org.elasticsearch.xpack.core.ccr.AutoFollowStats;
19+
import org.elasticsearch.xpack.core.ccr.action.AutoFollowStatsAction;
1820
import org.elasticsearch.xpack.core.ccr.action.DeleteAutoFollowPatternAction;
1921
import org.elasticsearch.xpack.core.ccr.action.PutAutoFollowPatternAction;
2022

@@ -58,6 +60,9 @@ public void testAutoFollow() throws Exception {
5860
});
5961
createIndex("transactions-201901", leaderIndexSettings, "_doc");
6062
assertBusy(() -> {
63+
AutoFollowStats autoFollowStats = getAutoFollowStats();
64+
assertThat(autoFollowStats.getNumberOfSuccessfulFollowIndices(), equalTo(2L));
65+
6166
IndicesExistsRequest request = new IndicesExistsRequest("copy-transactions-201901");
6267
assertTrue(client().admin().indices().exists(request).actionGet().isExists());
6368
});
@@ -82,9 +87,8 @@ public void testAutoFollowManyIndices() throws Exception {
8287
}
8388
int expectedVal1 = numIndices;
8489
assertBusy(() -> {
85-
MetaData metaData = client().admin().cluster().prepareState().get().getState().metaData();
86-
int count = (int) Arrays.stream(metaData.getConcreteAllIndices()).filter(s -> s.startsWith("copy-")).count();
87-
assertThat(count, equalTo(expectedVal1));
90+
AutoFollowStats autoFollowStats = getAutoFollowStats();
91+
assertThat(autoFollowStats.getNumberOfSuccessfulFollowIndices(), equalTo((long) expectedVal1));
8892
});
8993

9094
deleteAutoFollowPatternSetting();
@@ -188,4 +192,9 @@ private void deleteAutoFollowPatternSetting() {
188192
assertTrue(client().execute(DeleteAutoFollowPatternAction.INSTANCE, request).actionGet().isAcknowledged());
189193
}
190194

195+
private AutoFollowStats getAutoFollowStats() {
196+
AutoFollowStatsAction.Request request = new AutoFollowStatsAction.Request();
197+
return client().execute(AutoFollowStatsAction.INSTANCE, request).actionGet().getStats();
198+
}
199+
191200
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.core.ccr.action;
8+
9+
import org.elasticsearch.action.ActionRequestValidationException;
10+
import org.elasticsearch.action.ActionResponse;
11+
import org.elasticsearch.action.support.master.MasterNodeRequest;
12+
import org.elasticsearch.action.Action;
13+
import org.elasticsearch.common.io.stream.StreamInput;
14+
import org.elasticsearch.common.io.stream.StreamOutput;
15+
import org.elasticsearch.common.xcontent.ToXContentObject;
16+
import org.elasticsearch.common.xcontent.XContentBuilder;
17+
import org.elasticsearch.xpack.core.ccr.AutoFollowStats;
18+
19+
import java.io.IOException;
20+
import java.util.Objects;
21+
22+
public class AutoFollowStatsAction extends Action<AutoFollowStatsAction.Response> {
23+
24+
public static final String NAME = "cluster:monitor/ccr/auto_follow_stats";
25+
public static final AutoFollowStatsAction INSTANCE = new AutoFollowStatsAction();
26+
27+
private AutoFollowStatsAction() {
28+
super(NAME);
29+
}
30+
31+
@Override
32+
public Response newResponse() {
33+
return new Response();
34+
}
35+
36+
public static class Request extends MasterNodeRequest<Request> {
37+
38+
public Request(StreamInput in) throws IOException {
39+
super(in);
40+
}
41+
42+
public Request() {
43+
}
44+
45+
@Override
46+
public ActionRequestValidationException validate() {
47+
return null;
48+
}
49+
50+
@Override
51+
public void writeTo(StreamOutput out) throws IOException {
52+
super.writeTo(out);
53+
}
54+
}
55+
56+
public static class Response extends ActionResponse implements ToXContentObject {
57+
58+
private AutoFollowStats stats;
59+
60+
public Response(AutoFollowStats stats) {
61+
this.stats = stats;
62+
}
63+
64+
public Response() {
65+
}
66+
67+
public AutoFollowStats getStats() {
68+
return stats;
69+
}
70+
71+
@Override
72+
public void readFrom(StreamInput in) throws IOException {
73+
super.readFrom(in);
74+
stats = new AutoFollowStats(in);
75+
}
76+
77+
@Override
78+
public void writeTo(StreamOutput out) throws IOException {
79+
super.writeTo(out);
80+
stats.writeTo(out);
81+
}
82+
83+
@Override
84+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
85+
stats.toXContent(builder, params);
86+
return builder;
87+
}
88+
89+
@Override
90+
public boolean equals(Object o) {
91+
if (this == o) return true;
92+
if (o == null || getClass() != o.getClass()) return false;
93+
Response response = (Response) o;
94+
return Objects.equals(stats, response.stats);
95+
}
96+
97+
@Override
98+
public int hashCode() {
99+
return Objects.hash(stats);
100+
}
101+
}
102+
103+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"ccr.auto_follow_stats": {
3+
"documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/current",
4+
"methods": [ "GET" ],
5+
"url": {
6+
"path": "/_ccr/auto_follow/stats",
7+
"paths": [ "/_ccr/auto_follow/stats" ],
8+
"parts": {},
9+
"body": null
10+
}
11+
}
12+
}

0 commit comments

Comments
 (0)