/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.action.support.nodes;
import org.elasticsearch.Version;
import org.elasticsearch.action.FailedNodeException;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.action.support.broadcast.node.TransportBroadcastByNodeActionTests;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.transport.CapturingTransport;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.function.Supplier;
import static org.elasticsearch.test.ClusterServiceUtils.createClusterService;
import static org.elasticsearch.test.ClusterServiceUtils.setState;
import static org.mockito.Mockito.mock;
public class TransportNodesActionTests extends ESTestCase {
private static ThreadPool THREAD_POOL;
private ClusterService clusterService;
private CapturingTransport transport;
private TransportService transportService;
public void testRequestIsSentToEachNode() throws Exception {
TransportNodesAction action = getTestTransportNodesAction();
TestNodesRequest request = new TestNodesRequest();
PlainActionFuture<TestNodesResponse> listener = new PlainActionFuture<>();
action.new AsyncAction(null, request, listener).start();
Map<String, List<CapturingTransport.CapturedRequest>> capturedRequests = transport.getCapturedRequestsByTargetNodeAndClear();
int numNodes = clusterService.state().getNodes().getSize();
// check a request was sent to the right number of nodes
assertEquals(numNodes, capturedRequests.size());
}
public void testNodesSelectors() {
TransportNodesAction action = getTestTransportNodesAction();
int numSelectors = randomIntBetween(1, 5);
Set<String> nodeSelectors = new HashSet<>();
for (int i = 0; i < numSelectors; i++) {
nodeSelectors.add(randomFrom(NodeSelector.values()).selector);
}
int numNodeIds = randomIntBetween(0, 3);
String[] nodeIds = clusterService.state().nodes().getNodes().keys().toArray(String.class);
for (int i = 0; i < numNodeIds; i++) {
String nodeId = randomFrom(nodeIds);
nodeSelectors.add(nodeId);
}
String[] finalNodesIds = nodeSelectors.toArray(new String[nodeSelectors.size()]);
TestNodesRequest request = new TestNodesRequest(finalNodesIds);
action.new AsyncAction(null, request, new PlainActionFuture<>()).start();
Map<String, List<CapturingTransport.CapturedRequest>> capturedRequests = transport.getCapturedRequestsByTargetNodeAndClear();
assertEquals(clusterService.state().nodes().resolveNodes(finalNodesIds).length, capturedRequests.size());
}
public void testNewResponseNullArray() {
TransportNodesAction action = getTestTransportNodesAction();
expectThrows(NullPointerException.class, () -> action.newResponse(new TestNodesRequest(), null));
}
public void testNewResponse() {
TestTransportNodesAction action = getTestTransportNodesAction();
TestNodesRequest request = new TestNodesRequest();
List<TestNodeResponse> expectedNodeResponses = mockList(TestNodeResponse::new, randomIntBetween(0, 2));
expectedNodeResponses.add(new TestNodeResponse());
List<BaseNodeResponse> nodeResponses = new ArrayList<>(expectedNodeResponses);
// This should be ignored:
nodeResponses.add(new OtherNodeResponse());
List<FailedNodeException> failures = mockList(
() -> new FailedNodeException(
randomAlphaOfLength(8),
randomAlphaOfLength(8),
new IllegalStateException(randomAlphaOfLength(8))),
randomIntBetween(0, 2));
List<Object> allResponses = new ArrayList<>(expectedNodeResponses);
allResponses.addAll(failures);
Collections.shuffle(allResponses, random());
AtomicReferenceArray<?> atomicArray = new AtomicReferenceArray<>(allResponses.toArray());
TestNodesResponse response = action.newResponse(request, atomicArray);
assertSame(request, response.request);
// note: I shuffled the overall list, so it's not possible to guarantee that it's in the right order
assertTrue(expectedNodeResponses.containsAll(response.getNodes()));
assertTrue(failures.containsAll(response.failures()));
}
public void testCustomResolving() throws Exception {
TransportNodesAction action = getDataNodesOnlyTransportNodesAction(transportService);
TestNodesRequest request = new TestNodesRequest(randomBoolean() ? null : generateRandomStringArray(10, 5, false, true));
PlainActionFuture<TestNodesResponse> listener = new PlainActionFuture<>();
action.new AsyncAction(null, request, listener).start();
Map<String, List<CapturingTransport.CapturedRequest>> capturedRequests = transport.getCapturedRequestsByTargetNodeAndClear();
// check requests were only sent to data nodes
for (String nodeTarget : capturedRequests.keySet()) {
assertTrue(clusterService.state().nodes().get(nodeTarget).isDataNode());
}
assertEquals(clusterService.state().nodes().getDataNodes().size(), capturedRequests.size());
}
private <T> List<T> mockList(Supplier<T> supplier, int size) {
List<T> failures = new ArrayList<>(size);
for (int i = 0; i < size; ++i) {
failures.add(supplier.get());
}
return failures;
}
private enum NodeSelector {
LOCAL("_local"), ELECTED_MASTER("_master"), MASTER_ELIGIBLE("master:true"), DATA("data:true"), CUSTOM_ATTRIBUTE("attr:value");
private final String selector;
NodeSelector(String selector) {
this.selector = selector;
}
}
@BeforeClass
public static void startThreadPool() {
THREAD_POOL = new TestThreadPool(TransportBroadcastByNodeActionTests.class.getSimpleName());
}
@AfterClass
public static void destroyThreadPool() {
ThreadPool.terminate(THREAD_POOL, 30, TimeUnit.SECONDS);
// since static must set to null to be eligible for collection
THREAD_POOL = null;
}
@Before
public void setUp() throws Exception {
super.setUp();
transport = new CapturingTransport();
clusterService = createClusterService(THREAD_POOL);
transportService = new TransportService(clusterService.getSettings(), transport, THREAD_POOL,
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> clusterService.localNode(), null);
transportService.start();
transportService.acceptIncomingRequests();
int numNodes = randomIntBetween(3, 10);
DiscoveryNodes.Builder discoBuilder = DiscoveryNodes.builder();
List<DiscoveryNode> discoveryNodes = new ArrayList<>();
for (int i = 0; i < numNodes; i++) {
Map<String, String> attributes = new HashMap<>();
Set<DiscoveryNode.Role> roles = new HashSet<>(randomSubsetOf(Arrays.asList(DiscoveryNode.Role.values())));
if (frequently()) {
attributes.put("custom", randomBoolean() ? "match" : randomAlphaOfLengthBetween(3, 5));
}
final DiscoveryNode node = newNode(i, attributes, roles);
discoBuilder = discoBuilder.add(node);
discoveryNodes.add(node);
}
discoBuilder.localNodeId(randomFrom(discoveryNodes).getId());
discoBuilder.masterNodeId(randomFrom(discoveryNodes).getId());
ClusterState.Builder stateBuilder = ClusterState.builder(clusterService.getClusterName());
stateBuilder.nodes(discoBuilder);
ClusterState clusterState = stateBuilder.build();
setState(clusterService, clusterState);
}
@After
public void tearDown() throws Exception {
super.tearDown();
clusterService.close();
transport.close();
}
public TestTransportNodesAction getTestTransportNodesAction() {
return new TestTransportNodesAction(
Settings.EMPTY,
THREAD_POOL,
clusterService,
transportService,
new ActionFilters(Collections.emptySet()),
TestNodesRequest::new,
TestNodeRequest::new,
ThreadPool.Names.SAME
);
}
public DataNodesOnlyTransportNodesAction getDataNodesOnlyTransportNodesAction(TransportService transportService) {
return new DataNodesOnlyTransportNodesAction(
Settings.EMPTY,
THREAD_POOL,
clusterService,
transportService,
new ActionFilters(Collections.emptySet()),
TestNodesRequest::new,
TestNodeRequest::new,
ThreadPool.Names.SAME
);
}
private static DiscoveryNode newNode(int nodeId, Map<String, String> attributes, Set<DiscoveryNode.Role> roles) {
String node = "node_" + nodeId;
return new DiscoveryNode(node, node, buildNewFakeTransportAddress(), attributes, roles, Version.CURRENT);
}
private static class TestTransportNodesAction
extends TransportNodesAction<TestNodesRequest, TestNodesResponse, TestNodeRequest, TestNodeResponse> {
TestTransportNodesAction(Settings settings, ThreadPool threadPool, ClusterService clusterService, TransportService
transportService, ActionFilters actionFilters, Supplier<TestNodesRequest> request,
Supplier<TestNodeRequest> nodeRequest, String nodeExecutor) {
super(settings, "indices:admin/test", threadPool, clusterService, transportService, actionFilters,
null, request, nodeRequest, nodeExecutor, TestNodeResponse.class);
}
@Override
protected TestNodesResponse newResponse(TestNodesRequest request,
List<TestNodeResponse> responses, List<FailedNodeException> failures) {
return new TestNodesResponse(clusterService.getClusterName(), request, responses, failures);
}
@Override
protected TestNodeRequest newNodeRequest(String nodeId, TestNodesRequest request) {
return new TestNodeRequest();
}
@Override
protected TestNodeResponse newNodeResponse() {
return new TestNodeResponse();
}
@Override
protected TestNodeResponse nodeOperation(TestNodeRequest request) {
return new TestNodeResponse();
}
@Override
protected boolean accumulateExceptions() {
return false;
}
}
private static class DataNodesOnlyTransportNodesAction
extends TestTransportNodesAction {
DataNodesOnlyTransportNodesAction(Settings settings, ThreadPool threadPool, ClusterService clusterService, TransportService
transportService, ActionFilters actionFilters, Supplier<TestNodesRequest> request,
Supplier<TestNodeRequest> nodeRequest, String nodeExecutor) {
super(settings, threadPool, clusterService, transportService, actionFilters, request, nodeRequest, nodeExecutor);
}
@Override
protected void resolveRequest(TestNodesRequest request, ClusterState clusterState) {
request.setConcreteNodes(clusterState.nodes().getDataNodes().values().toArray(DiscoveryNode.class));
}
}
private static class TestNodesRequest extends BaseNodesRequest<TestNodesRequest> {
TestNodesRequest(String... nodesIds) {
super(nodesIds);
}
}
private static class TestNodesResponse extends BaseNodesResponse<TestNodeResponse> {
private final TestNodesRequest request;
TestNodesResponse(ClusterName clusterName, TestNodesRequest request, List<TestNodeResponse> nodeResponses,
List<FailedNodeException> failures) {
super(clusterName, nodeResponses, failures);
this.request = request;
}
@Override
protected List<TestNodeResponse> readNodesFrom(StreamInput in) throws IOException {
return in.readStreamableList(TestNodeResponse::new);
}
@Override
protected void writeNodesTo(StreamOutput out, List<TestNodeResponse> nodes) throws IOException {
out.writeStreamableList(nodes);
}
}
private static class TestNodeRequest extends BaseNodeRequest { }
private static class TestNodeResponse extends BaseNodeResponse { }
private static class OtherNodeResponse extends BaseNodeResponse { }
}