Skip to content

Commit b546d70

Browse files
authored
Add remote clusters xpack usage report (#94862)
This PR adds a new remote_clusters section to the xpack usage response to report stats of remote cluster connections including total number, mode and security model. It also adds a new remote_cluster_server sub-section under the existing security section. Relates: #94817
1 parent 8374d51 commit b546d70

File tree

14 files changed

+372
-22
lines changed

14 files changed

+372
-22
lines changed

docs/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,15 @@ tasks.named("yamlRestTest").configure {
116116
}
117117
}
118118

119+
// TODO: Remove the following when RCS feature is released
120+
// The get-builtin-privileges doc test does not include the new cluster privilege for RCS
121+
// So we disable the test if the build is a snapshot where unreleased feature is enabled by default
122+
tasks.named("yamlRestTest").configure {
123+
if (BuildParams.isSnapshotBuild()) {
124+
systemProperty 'tests.rest.blacklist', 'reference/rest-api/usage/*'
125+
}
126+
}
127+
119128
tasks.named("forbiddenPatterns").configure {
120129
exclude '**/*.mmdb'
121130
}

server/src/main/java/org/elasticsearch/transport/RemoteConnectionInfo.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ public boolean isSkipUnavailable() {
9393
return skipUnavailable;
9494
}
9595

96+
public boolean hasClusterCredentials() {
97+
return hasClusterCredentials;
98+
}
99+
96100
@Override
97101
public void writeTo(StreamOutput out) throws IOException {
98102
if (out.getTransportVersion().onOrAfter(TransportVersion.V_7_6_0)) {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.core;
9+
10+
import org.elasticsearch.TransportVersion;
11+
import org.elasticsearch.common.io.stream.StreamInput;
12+
import org.elasticsearch.common.io.stream.StreamOutput;
13+
import org.elasticsearch.transport.RemoteClusterPortSettings;
14+
import org.elasticsearch.transport.RemoteConnectionInfo;
15+
import org.elasticsearch.xcontent.XContentBuilder;
16+
17+
import java.io.IOException;
18+
import java.util.List;
19+
20+
public class RemoteClusterFeatureSetUsage extends XPackFeatureSet.Usage {
21+
22+
private final List<RemoteConnectionInfo> remoteConnectionInfos;
23+
24+
public RemoteClusterFeatureSetUsage(StreamInput in) throws IOException {
25+
super(in);
26+
this.remoteConnectionInfos = in.readImmutableList(RemoteConnectionInfo::new);
27+
}
28+
29+
public RemoteClusterFeatureSetUsage(List<RemoteConnectionInfo> remoteConnectionInfos) {
30+
super(XPackField.REMOTE_CLUSTERS, true, true);
31+
this.remoteConnectionInfos = remoteConnectionInfos;
32+
}
33+
34+
@Override
35+
public TransportVersion getMinimalSupportedVersion() {
36+
return RemoteClusterPortSettings.TRANSPORT_VERSION_REMOTE_CLUSTER_SECURITY;
37+
}
38+
39+
@Override
40+
public void writeTo(StreamOutput out) throws IOException {
41+
super.writeTo(out);
42+
out.writeList(remoteConnectionInfos);
43+
}
44+
45+
@Override
46+
protected void innerXContent(XContentBuilder builder, Params params) throws IOException {
47+
final int size = remoteConnectionInfos.size();
48+
builder.field("size", size);
49+
50+
int numberOfSniffModes = 0;
51+
int numberOfConfigurableModels = 0;
52+
for (var info : remoteConnectionInfos) {
53+
if ("sniff".equals(info.getModeInfo().modeName())) {
54+
numberOfSniffModes += 1;
55+
} else {
56+
assert "proxy".equals(info.getModeInfo().modeName());
57+
}
58+
if (info.hasClusterCredentials()) {
59+
numberOfConfigurableModels += 1;
60+
}
61+
}
62+
63+
builder.startObject("mode");
64+
builder.field("proxy", size - numberOfSniffModes);
65+
builder.field("sniff", numberOfSniffModes);
66+
builder.endObject();
67+
68+
builder.startObject("security");
69+
builder.field("basic", size - numberOfConfigurableModels);
70+
builder.field("configurable", numberOfConfigurableModels);
71+
builder.endObject();
72+
}
73+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.core;
9+
10+
import org.elasticsearch.action.ActionListener;
11+
import org.elasticsearch.action.support.ActionFilters;
12+
import org.elasticsearch.cluster.ClusterState;
13+
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
14+
import org.elasticsearch.cluster.service.ClusterService;
15+
import org.elasticsearch.common.inject.Inject;
16+
import org.elasticsearch.protocol.xpack.XPackUsageRequest;
17+
import org.elasticsearch.tasks.Task;
18+
import org.elasticsearch.threadpool.ThreadPool;
19+
import org.elasticsearch.transport.RemoteClusterService;
20+
import org.elasticsearch.transport.TransportService;
21+
import org.elasticsearch.xpack.core.action.XPackUsageFeatureAction;
22+
import org.elasticsearch.xpack.core.action.XPackUsageFeatureResponse;
23+
import org.elasticsearch.xpack.core.action.XPackUsageFeatureTransportAction;
24+
25+
public class RemoteClusterUsageTransportAction extends XPackUsageFeatureTransportAction {
26+
27+
@Inject
28+
public RemoteClusterUsageTransportAction(
29+
TransportService transportService,
30+
ClusterService clusterService,
31+
ThreadPool threadPool,
32+
ActionFilters actionFilters,
33+
IndexNameExpressionResolver indexNameExpressionResolver
34+
) {
35+
super(
36+
XPackUsageFeatureAction.REMOTE_CLUSTERS.name(),
37+
transportService,
38+
clusterService,
39+
threadPool,
40+
actionFilters,
41+
indexNameExpressionResolver
42+
);
43+
}
44+
45+
@Override
46+
protected void masterOperation(
47+
Task task,
48+
XPackUsageRequest request,
49+
ClusterState state,
50+
ActionListener<XPackUsageFeatureResponse> listener
51+
) throws Exception {
52+
final RemoteClusterService remoteClusterService = transportService.getRemoteClusterService();
53+
54+
listener.onResponse(
55+
new XPackUsageFeatureResponse(new RemoteClusterFeatureSetUsage(remoteClusterService.getRemoteConnectionInfos().toList()))
56+
);
57+
}
58+
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,9 @@ public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
555555
// TSDB Downsampling
556556
new NamedWriteableRegistry.Entry(LifecycleAction.class, DownsampleAction.NAME, DownsampleAction::new),
557557
// Health API usage
558-
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.HEALTH_API, HealthApiFeatureSetUsage::new)
558+
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.HEALTH_API, HealthApiFeatureSetUsage::new),
559+
// Remote cluster usage
560+
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.REMOTE_CLUSTERS, RemoteClusterFeatureSetUsage::new)
559561
);
560562
}
561563

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackField.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public final class XPackField {
7575
public static final String ARCHIVE = "archive";
7676
/** Name constant for the health api feature. */
7777
public static final String HEALTH_API = "health_api";
78+
public static final String REMOTE_CLUSTERS = "remote_clusters";
7879

7980
private XPackField() {}
8081

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackPlugin.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ public Collection<Object> createComponents(
342342
actions.add(new ActionHandler<>(XPackUsageFeatureAction.DATA_STREAMS, DataStreamUsageTransportAction.class));
343343
actions.add(new ActionHandler<>(XPackInfoFeatureAction.DATA_STREAMS, DataStreamInfoTransportAction.class));
344344
actions.add(new ActionHandler<>(XPackUsageFeatureAction.HEALTH, HealthApiUsageTransportAction.class));
345+
actions.add(new ActionHandler<>(XPackUsageFeatureAction.REMOTE_CLUSTERS, RemoteClusterUsageTransportAction.class));
345346
return actions;
346347
}
347348

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackUsageFeatureAction.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
package org.elasticsearch.xpack.core.action;
88

99
import org.elasticsearch.action.ActionType;
10+
import org.elasticsearch.transport.TcpTransport;
1011
import org.elasticsearch.xpack.core.XPackField;
1112

1213
import java.util.List;
14+
import java.util.Objects;
15+
import java.util.stream.Stream;
1316

1417
/**
1518
* A base action for usage of a feature plugin.
@@ -46,8 +49,9 @@ public class XPackUsageFeatureAction extends ActionType<XPackUsageFeatureRespons
4649
public static final XPackUsageFeatureAction AGGREGATE_METRIC = new XPackUsageFeatureAction(XPackField.AGGREGATE_METRIC);
4750
public static final XPackUsageFeatureAction ARCHIVE = new XPackUsageFeatureAction(XPackField.ARCHIVE);
4851
public static final XPackUsageFeatureAction HEALTH = new XPackUsageFeatureAction(XPackField.HEALTH_API);
52+
public static final XPackUsageFeatureAction REMOTE_CLUSTERS = new XPackUsageFeatureAction(XPackField.REMOTE_CLUSTERS);
4953

50-
static final List<XPackUsageFeatureAction> ALL = List.of(
54+
static final List<XPackUsageFeatureAction> ALL = Stream.of(
5155
AGGREGATE_METRIC,
5256
ANALYTICS,
5357
CCR,
@@ -70,8 +74,9 @@ public class XPackUsageFeatureAction extends ActionType<XPackUsageFeatureRespons
7074
VOTING_ONLY,
7175
WATCHER,
7276
ARCHIVE,
73-
HEALTH
74-
);
77+
HEALTH,
78+
TcpTransport.isUntrustedRemoteClusterEnabled() ? REMOTE_CLUSTERS : null
79+
).filter(Objects::nonNull).toList();
7580

7681
// public for testing
7782
public XPackUsageFeatureAction(String name) {

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityFeatureSetUsage.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.elasticsearch.TransportVersion;
1010
import org.elasticsearch.common.io.stream.StreamInput;
1111
import org.elasticsearch.common.io.stream.StreamOutput;
12+
import org.elasticsearch.transport.RemoteClusterPortSettings;
1213
import org.elasticsearch.xcontent.XContentBuilder;
1314
import org.elasticsearch.xpack.core.XPackFeatureSet;
1415
import org.elasticsearch.xpack.core.XPackField;
@@ -32,6 +33,7 @@ public class SecurityFeatureSetUsage extends XPackFeatureSet.Usage {
3233
private static final String OPERATOR_PRIVILEGES_XFIELD = XPackField.OPERATOR_PRIVILEGES;
3334
private static final String DOMAINS_XFIELD = "domains";
3435
private static final String USER_PROFILE_XFIELD = "user_profile";
36+
private static final String REMOTE_CLUSTER_SERVER_XFIELD = "remote_cluster_server";
3537

3638
private Map<String, Object> realmsUsage;
3739
private Map<String, Object> rolesStoreUsage;
@@ -46,6 +48,7 @@ public class SecurityFeatureSetUsage extends XPackFeatureSet.Usage {
4648
private Map<String, Object> operatorPrivilegesUsage;
4749
private Map<String, Object> domainsUsage;
4850
private Map<String, Object> userProfileUsage;
51+
private Map<String, Object> remoteClusterServerUsage;
4952

5053
public SecurityFeatureSetUsage(StreamInput in) throws IOException {
5154
super(in);
@@ -72,6 +75,9 @@ public SecurityFeatureSetUsage(StreamInput in) throws IOException {
7275
if (in.getTransportVersion().onOrAfter(TransportVersion.V_8_5_0)) {
7376
userProfileUsage = in.readMap();
7477
}
78+
if (in.getTransportVersion().onOrAfter(RemoteClusterPortSettings.TRANSPORT_VERSION_REMOTE_CLUSTER_SECURITY)) {
79+
remoteClusterServerUsage = in.readMap();
80+
}
7581
}
7682

7783
public SecurityFeatureSetUsage(
@@ -88,7 +94,8 @@ public SecurityFeatureSetUsage(
8894
Map<String, Object> fips140Usage,
8995
Map<String, Object> operatorPrivilegesUsage,
9096
Map<String, Object> domainsUsage,
91-
Map<String, Object> userProfileUsage
97+
Map<String, Object> userProfileUsage,
98+
Map<String, Object> remoteClusterServerUsage
9299
) {
93100
super(XPackField.SECURITY, true, enabled);
94101
this.realmsUsage = realmsUsage;
@@ -104,6 +111,7 @@ public SecurityFeatureSetUsage(
104111
this.operatorPrivilegesUsage = operatorPrivilegesUsage;
105112
this.domainsUsage = domainsUsage;
106113
this.userProfileUsage = userProfileUsage;
114+
this.remoteClusterServerUsage = remoteClusterServerUsage;
107115
}
108116

109117
@Override
@@ -137,6 +145,9 @@ public void writeTo(StreamOutput out) throws IOException {
137145
if (out.getTransportVersion().onOrAfter(TransportVersion.V_8_5_0)) {
138146
out.writeGenericMap(userProfileUsage);
139147
}
148+
if (out.getTransportVersion().onOrAfter(RemoteClusterPortSettings.TRANSPORT_VERSION_REMOTE_CLUSTER_SECURITY)) {
149+
out.writeGenericMap(remoteClusterServerUsage);
150+
}
140151
}
141152

142153
@Override
@@ -160,6 +171,9 @@ protected void innerXContent(XContentBuilder builder, Params params) throws IOEx
160171
if (userProfileUsage != null && false == userProfileUsage.isEmpty()) {
161172
builder.field(USER_PROFILE_XFIELD, userProfileUsage);
162173
}
174+
if (remoteClusterServerUsage != null && false == remoteClusterServerUsage.isEmpty()) {
175+
builder.field(REMOTE_CLUSTER_SERVER_XFIELD, remoteClusterServerUsage);
176+
}
163177
} else if (sslUsage.isEmpty() == false) {
164178
// A trial (or basic) license can have SSL without security.
165179
// This is because security defaults to disabled on that license, but that dynamic-default does not disable SSL.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.core;
9+
10+
import org.elasticsearch.common.Strings;
11+
import org.elasticsearch.common.xcontent.XContentHelper;
12+
import org.elasticsearch.core.TimeValue;
13+
import org.elasticsearch.test.ESTestCase;
14+
import org.elasticsearch.transport.ProxyConnectionStrategy;
15+
import org.elasticsearch.transport.RemoteConnectionInfo;
16+
import org.elasticsearch.transport.SniffConnectionStrategy;
17+
import org.elasticsearch.xcontent.ToXContent;
18+
import org.elasticsearch.xcontent.XContentBuilder;
19+
import org.elasticsearch.xcontent.json.JsonXContent;
20+
21+
import java.io.IOException;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
25+
import static org.hamcrest.Matchers.equalTo;
26+
27+
public class RemoteClusterFeatureSetUsageTests extends ESTestCase {
28+
29+
public void testToXContent() throws IOException {
30+
final int numberOfRemoteClusters = randomIntBetween(0, 10);
31+
int numberOfSniffModes = 0;
32+
int numberOfConfigurableModels = 0;
33+
final List<RemoteConnectionInfo> infos = new ArrayList<>();
34+
for (int i = 0; i < numberOfRemoteClusters; i++) {
35+
final boolean hasCredentials = randomBoolean();
36+
if (hasCredentials) {
37+
numberOfConfigurableModels += 1;
38+
}
39+
final RemoteConnectionInfo.ModeInfo modeInfo;
40+
if (randomBoolean()) {
41+
modeInfo = new SniffConnectionStrategy.SniffModeInfo(List.of(), randomIntBetween(1, 8), randomIntBetween(1, 8));
42+
numberOfSniffModes += 1;
43+
} else {
44+
modeInfo = new ProxyConnectionStrategy.ProxyModeInfo(
45+
randomAlphaOfLengthBetween(3, 8),
46+
randomAlphaOfLengthBetween(3, 8),
47+
randomIntBetween(1, 8),
48+
randomIntBetween(1, 8)
49+
);
50+
}
51+
infos.add(
52+
new RemoteConnectionInfo(randomAlphaOfLengthBetween(3, 8), modeInfo, TimeValue.ZERO, randomBoolean(), hasCredentials)
53+
);
54+
}
55+
56+
try (XContentBuilder builder = JsonXContent.contentBuilder()) {
57+
new RemoteClusterFeatureSetUsage(infos).toXContent(builder, ToXContent.EMPTY_PARAMS);
58+
assertThat(
59+
Strings.toString(builder),
60+
equalTo(
61+
XContentHelper.stripWhitespace(
62+
Strings.format(
63+
"""
64+
{
65+
"size": %s,
66+
"mode": {
67+
"proxy": %s,
68+
"sniff": %s
69+
},
70+
"security": {
71+
"basic": %s,
72+
"configurable": %s
73+
}
74+
}""",
75+
numberOfRemoteClusters,
76+
numberOfRemoteClusters - numberOfSniffModes,
77+
numberOfSniffModes,
78+
numberOfRemoteClusters - numberOfConfigurableModels,
79+
numberOfConfigurableModels
80+
)
81+
)
82+
)
83+
);
84+
}
85+
86+
}
87+
}

0 commit comments

Comments
 (0)