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.
This commit is contained in:
Jason Tedor 2020-04-02 08:55:56 -04:00
parent 161eac1942
commit 18b602280c
No known key found for this signature in database
GPG Key ID: FA89F05560F16BC5
4 changed files with 87 additions and 8 deletions

View File

@ -103,7 +103,7 @@ public class RestIndexAction extends BaseRestHandler {
@Override
public String getName() {
return "document_create_action";
return "document_create_action_auto_id";
}
@Override

View File

@ -42,21 +42,21 @@ import org.elasticsearch.action.admin.cluster.node.usage.NodeUsage;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.rest.BaseRestHandler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
/**
* A service to monitor usage of Elasticsearch features.
*/
public class UsageService {
private final List<BaseRestHandler> handlers;
private final Map<String, BaseRestHandler> handlers;
private final long sinceTime;
public UsageService() {
this.handlers = new ArrayList<>();
this.handlers = new HashMap<>();
this.sinceTime = System.currentTimeMillis();
}
@ -67,7 +67,26 @@ public class UsageService {
* the {@link BaseRestHandler} to add to the usage service.
*/
public void addRestHandler(BaseRestHandler handler) {
handlers.add(handler);
Objects.requireNonNull(handler);
if (handler.getName() == null) {
throw new IllegalArgumentException("handler of type [" + handler.getClass().getName() + "] does not have a name");
}
final BaseRestHandler maybeHandler = handlers.put(handler.getName(), handler);
/*
* Handlers will be registered multiple times, once for each route that the handler handles. This means that we will see handlers
* 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
* with the same name was registered before, and it is not the same instance as before.
*/
if (maybeHandler != null && maybeHandler != handler) {
final String message = String.format(
Locale.ROOT,
"handler of type [%s] conflicts with handler of type [%s] as they both have the same name [%s]",
handler.getClass().getName(),
maybeHandler.getClass().getName(),
handler.getName()
);
throw new IllegalArgumentException(message);
}
}
/**
@ -85,7 +104,7 @@ public class UsageService {
Map<String, Long> restUsageMap;
if (restActions) {
restUsageMap = new HashMap<>();
handlers.forEach(handler -> {
handlers.values().forEach(handler -> {
long usageCount = handler.getUsageCount();
if (usageCount > 0) {
restUsageMap.put(handler.getName(), usageCount);

View File

@ -132,7 +132,14 @@ public class ActionModuleTests extends ESTestCase {
public List<RestHandler> getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings,
IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter,
IndexNameExpressionResolver indexNameExpressionResolver, Supplier<DiscoveryNodes> nodesInCluster) {
return singletonList(new RestMainAction());
return singletonList(new RestMainAction() {
@Override
public String getName() {
return "duplicated_" + super.getName();
}
});
}
};
SettingsModule settings = new SettingsModule(Settings.EMPTY);

View File

@ -32,6 +32,7 @@ import org.elasticsearch.test.rest.FakeRestRequest;
import java.net.InetAddress;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
@ -41,6 +42,58 @@ import static org.hamcrest.Matchers.sameInstance;
public class UsageServiceTests extends ESTestCase {
/**
* Test that we can not add a null reference to a {@link org.elasticsearch.rest.RestHandler} to the {@link UsageService}.
*/
public void testHandlerCanNotBeNull() {
final UsageService service = new UsageService();
expectThrows(NullPointerException.class, () -> service.addRestHandler(null));
}
/**
* Test that we can not add an instance of a {@link org.elasticsearch.rest.RestHandler} with no name to the {@link UsageService}.
*/
public void testAHandlerWithNoName() {
final UsageService service = new UsageService();
final BaseRestHandler horse = new MockRestHandler(null);
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> service.addRestHandler(horse));
assertThat(
e.getMessage(),
equalTo("handler of type [org.elasticsearch.usage.UsageServiceTests$MockRestHandler] does not have a name"));
}
/**
* Test that we can add the same instance of a {@link org.elasticsearch.rest.RestHandler} to the {@link UsageService} multiple times.
*/
public void testHandlerWithConflictingNamesButSameInstance() {
final UsageService service = new UsageService();
final String name = randomAlphaOfLength(8);
final BaseRestHandler first = new MockRestHandler(name);
service.addRestHandler(first);
// nothing bad ever happens to me
service.addRestHandler(first);
}
/**
* Test that we can not add different instances of {@link org.elasticsearch.rest.RestHandler} with the same name to the
* {@link UsageService}.
*/
public void testHandlersWithConflictingNamesButDifferentInstances() {
final UsageService service = new UsageService();
final String name = randomAlphaOfLength(8);
final BaseRestHandler first = new MockRestHandler(name);
final BaseRestHandler second = new MockRestHandler(name);
service.addRestHandler(first);
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> service.addRestHandler(second));
final String expected = String.format(
Locale.ROOT,
"handler of type [%s] conflicts with handler of type [%1$s] as they both have the same name [%s]",
"org.elasticsearch.usage.UsageServiceTests$MockRestHandler",
name
);
assertThat(e.getMessage(), equalTo(expected));
}
public void testRestUsage() throws Exception {
DiscoveryNode discoveryNode = new DiscoveryNode("foo", new TransportAddress(InetAddress.getByName("localhost"), 12345),
Version.CURRENT);