Skip to content

Commit cdd07c5

Browse files
authored
Add validation to the usage service (#54617)
Today the usage service can let in some issues, such as handlers that do not have a name, where the errors do not manifest until later (calling the usage API), or conflicting handlers with the same name. This commit addresses this by adding some validation to the usage service.
1 parent de93853 commit cdd07c5

File tree

4 files changed

+87
-8
lines changed

4 files changed

+87
-8
lines changed

server/src/main/java/org/elasticsearch/rest/action/document/RestIndexAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public AutoIdHandler(ClusterService clusterService) {
8989

9090
@Override
9191
public String getName() {
92-
return "document_create_action";
92+
return "document_create_action_auto_id";
9393
}
9494

9595
@Override

server/src/main/java/org/elasticsearch/usage/UsageService.java

+25-6
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,21 @@
4242
import org.elasticsearch.cluster.node.DiscoveryNode;
4343
import org.elasticsearch.rest.BaseRestHandler;
4444

45-
import java.util.ArrayList;
4645
import java.util.HashMap;
47-
import java.util.List;
46+
import java.util.Locale;
4847
import java.util.Map;
48+
import java.util.Objects;
4949

5050
/**
5151
* A service to monitor usage of Elasticsearch features.
5252
*/
5353
public class UsageService {
5454

55-
private final List<BaseRestHandler> handlers;
55+
private final Map<String, BaseRestHandler> handlers;
5656
private final long sinceTime;
5757

5858
public UsageService() {
59-
this.handlers = new ArrayList<>();
59+
this.handlers = new HashMap<>();
6060
this.sinceTime = System.currentTimeMillis();
6161
}
6262

@@ -67,7 +67,26 @@ public UsageService() {
6767
* the {@link BaseRestHandler} to add to the usage service.
6868
*/
6969
public void addRestHandler(BaseRestHandler handler) {
70-
handlers.add(handler);
70+
Objects.requireNonNull(handler);
71+
if (handler.getName() == null) {
72+
throw new IllegalArgumentException("handler of type [" + handler.getClass().getName() + "] does not have a name");
73+
}
74+
final BaseRestHandler maybeHandler = handlers.put(handler.getName(), handler);
75+
/*
76+
* Handlers will be registered multiple times, once for each route that the handler handles. This means that we will see handlers
77+
* multiple times, so we do not have a conflict if we are seeing the same instance multiple times. So, we only reject if a handler
78+
* with the same name was registered before, and it is not the same instance as before.
79+
*/
80+
if (maybeHandler != null && maybeHandler != handler) {
81+
final String message = String.format(
82+
Locale.ROOT,
83+
"handler of type [%s] conflicts with handler of type [%s] as they both have the same name [%s]",
84+
handler.getClass().getName(),
85+
maybeHandler.getClass().getName(),
86+
handler.getName()
87+
);
88+
throw new IllegalArgumentException(message);
89+
}
7190
}
7291

7392
/**
@@ -85,7 +104,7 @@ public NodeUsage getUsageStats(DiscoveryNode localNode, boolean restActions) {
85104
Map<String, Long> restUsageMap;
86105
if (restActions) {
87106
restUsageMap = new HashMap<>();
88-
handlers.forEach(handler -> {
107+
handlers.values().forEach(handler -> {
89108
long usageCount = handler.getUsageCount();
90109
if (usageCount > 0) {
91110
restUsageMap.put(handler.getName(), usageCount);

server/src/test/java/org/elasticsearch/action/ActionModuleTests.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,14 @@ public void testPluginCantOverwriteBuiltinRestHandler() throws IOException {
132132
public List<RestHandler> getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings,
133133
IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter,
134134
IndexNameExpressionResolver indexNameExpressionResolver, Supplier<DiscoveryNodes> nodesInCluster) {
135-
return singletonList(new RestMainAction());
135+
return singletonList(new RestMainAction() {
136+
137+
@Override
138+
public String getName() {
139+
return "duplicated_" + super.getName();
140+
}
141+
142+
});
136143
}
137144
};
138145
SettingsModule settings = new SettingsModule(Settings.EMPTY);

server/src/test/java/org/elasticsearch/usage/UsageServiceTests.java

+53
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.net.InetAddress;
3333
import java.util.Collections;
3434
import java.util.List;
35+
import java.util.Locale;
3536
import java.util.Map;
3637

3738
import static org.hamcrest.Matchers.equalTo;
@@ -41,6 +42,58 @@
4142

4243
public class UsageServiceTests extends ESTestCase {
4344

45+
/**
46+
* Test that we can not add a null reference to a {@link org.elasticsearch.rest.RestHandler} to the {@link UsageService}.
47+
*/
48+
public void testHandlerCanNotBeNull() {
49+
final UsageService service = new UsageService();
50+
expectThrows(NullPointerException.class, () -> service.addRestHandler(null));
51+
}
52+
53+
/**
54+
* Test that we can not add an instance of a {@link org.elasticsearch.rest.RestHandler} with no name to the {@link UsageService}.
55+
*/
56+
public void testAHandlerWithNoName() {
57+
final UsageService service = new UsageService();
58+
final BaseRestHandler horse = new MockRestHandler(null);
59+
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> service.addRestHandler(horse));
60+
assertThat(
61+
e.getMessage(),
62+
equalTo("handler of type [org.elasticsearch.usage.UsageServiceTests$MockRestHandler] does not have a name"));
63+
}
64+
65+
/**
66+
* Test that we can add the same instance of a {@link org.elasticsearch.rest.RestHandler} to the {@link UsageService} multiple times.
67+
*/
68+
public void testHandlerWithConflictingNamesButSameInstance() {
69+
final UsageService service = new UsageService();
70+
final String name = randomAlphaOfLength(8);
71+
final BaseRestHandler first = new MockRestHandler(name);
72+
service.addRestHandler(first);
73+
// nothing bad ever happens to me
74+
service.addRestHandler(first);
75+
}
76+
77+
/**
78+
* Test that we can not add different instances of {@link org.elasticsearch.rest.RestHandler} with the same name to the
79+
* {@link UsageService}.
80+
*/
81+
public void testHandlersWithConflictingNamesButDifferentInstances() {
82+
final UsageService service = new UsageService();
83+
final String name = randomAlphaOfLength(8);
84+
final BaseRestHandler first = new MockRestHandler(name);
85+
final BaseRestHandler second = new MockRestHandler(name);
86+
service.addRestHandler(first);
87+
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> service.addRestHandler(second));
88+
final String expected = String.format(
89+
Locale.ROOT,
90+
"handler of type [%s] conflicts with handler of type [%1$s] as they both have the same name [%s]",
91+
"org.elasticsearch.usage.UsageServiceTests$MockRestHandler",
92+
name
93+
);
94+
assertThat(e.getMessage(), equalTo(expected));
95+
}
96+
4497
public void testRestUsage() throws Exception {
4598
DiscoveryNode discoveryNode = new DiscoveryNode("foo", new TransportAddress(InetAddress.getByName("localhost"), 12345),
4699
Version.CURRENT);

0 commit comments

Comments
 (0)