Execute low level handshake in #openConnection (#22440)

Today we execute the low level handshake on the TCP layer in #connectToNode.
If #openConnection is used directly, which is truly expert, no handshake is executed
which allows connecting to nodes that are not necessarily compatible. This change
moves the handshake to #openConnection to prevent bypassing this logic.
This commit is contained in:
Simon Willnauer 2017-01-05 07:32:53 +01:00 committed by GitHub
parent bf51522788
commit a5daa5d3a2
3 changed files with 63 additions and 11 deletions

View File

@ -458,13 +458,6 @@ public abstract class TcpTransport<Channel> extends AbstractLifecycleComponent i
"failed to connect to [{}], cleaning dangling connections", node), e); "failed to connect to [{}], cleaning dangling connections", node), e);
throw e; throw e;
} }
Channel channel = nodeChannels.channel(TransportRequestOptions.Type.PING);
final TimeValue connectTimeout = connectionProfile.getConnectTimeout() == null ?
defaultConnectionProfile.getConnectTimeout() :
connectionProfile.getConnectTimeout();
final TimeValue handshakeTimeout = connectionProfile.getHandshakeTimeout() == null ?
connectTimeout : connectionProfile.getHandshakeTimeout();
Version version = executeHandshake(node, channel, handshakeTimeout);
// we acquire a connection lock, so no way there is an existing connection // we acquire a connection lock, so no way there is an existing connection
connectedNodes.put(node, nodeChannels); connectedNodes.put(node, nodeChannels);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@ -483,11 +476,18 @@ public abstract class TcpTransport<Channel> extends AbstractLifecycleComponent i
} }
@Override @Override
public final NodeChannels openConnection(DiscoveryNode node, ConnectionProfile profile) throws IOException { public final NodeChannels openConnection(DiscoveryNode node, ConnectionProfile connectionProfile) throws IOException {
try { try {
NodeChannels nodeChannels = connectToChannels(node, profile); NodeChannels nodeChannels = connectToChannels(node, connectionProfile);
final Channel channel = nodeChannels.getChannels().get(0); // one channel is guaranteed by the connection profile
final TimeValue connectTimeout = connectionProfile.getConnectTimeout() == null ?
defaultConnectionProfile.getConnectTimeout() :
connectionProfile.getConnectTimeout();
final TimeValue handshakeTimeout = connectionProfile.getHandshakeTimeout() == null ?
connectTimeout : connectionProfile.getHandshakeTimeout();
final Version version = executeHandshake(node, channel, handshakeTimeout);
transportServiceAdapter.onConnectionOpened(node); transportServiceAdapter.onConnectionOpened(node);
return nodeChannels; return new NodeChannels(nodeChannels, version); // clone the channels - we now have the correct version
} catch (ConnectTransportException e) { } catch (ConnectTransportException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {

View File

@ -684,6 +684,11 @@ public final class MockTransportService extends TransportService {
return connection.getNode(); return connection.getNode();
} }
@Override
public Version getVersion() {
return connection.getVersion();
}
@Override @Override
public void sendRequest(long requestId, String action, TransportRequest request, TransportRequestOptions options) public void sendRequest(long requestId, String action, TransportRequest request, TransportRequestOptions options)
throws IOException, TransportException { throws IOException, TransportException {

View File

@ -43,6 +43,7 @@ import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
import org.elasticsearch.mocksocket.MockServerSocket; import org.elasticsearch.mocksocket.MockServerSocket;
import org.elasticsearch.node.Node; import org.elasticsearch.node.Node;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.VersionUtils;
import org.elasticsearch.test.transport.MockTransportService; import org.elasticsearch.test.transport.MockTransportService;
import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
@ -1818,6 +1819,52 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase {
} }
} }
public void testHandshakeWithIncompatVersion() {
assumeTrue("only tcp transport has a handshake method", serviceA.getOriginalTransport() instanceof TcpTransport);
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(Collections.emptyList());
try (MockTcpTransport transport = new MockTcpTransport(Settings.EMPTY, threadPool, BigArrays.NON_RECYCLING_INSTANCE,
new NoneCircuitBreakerService(), namedWriteableRegistry, new NetworkService(Settings.EMPTY, Collections.emptyList()),
Version.fromString("2.0.0"))) {
transport.transportServiceAdapter(serviceA.new Adapter());
transport.start();
DiscoveryNode node =
new DiscoveryNode("TS_TPC", "TS_TPC", transport.boundAddress().publishAddress(), emptyMap(), emptySet(), version0);
ConnectionProfile.Builder builder = new ConnectionProfile.Builder();
builder.addConnections(1,
TransportRequestOptions.Type.BULK,
TransportRequestOptions.Type.PING,
TransportRequestOptions.Type.RECOVERY,
TransportRequestOptions.Type.REG,
TransportRequestOptions.Type.STATE);
expectThrows(ConnectTransportException.class, () -> serviceA.openConnection(node, builder.build()));
}
}
public void testHandshakeUpdatesVersion() throws IOException {
assumeTrue("only tcp transport has a handshake method", serviceA.getOriginalTransport() instanceof TcpTransport);
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(Collections.emptyList());
Version version = VersionUtils.randomVersionBetween(random(), Version.CURRENT.minimumCompatibilityVersion(), Version.CURRENT);
try (MockTcpTransport transport = new MockTcpTransport(Settings.EMPTY, threadPool, BigArrays.NON_RECYCLING_INSTANCE,
new NoneCircuitBreakerService(), namedWriteableRegistry, new NetworkService(Settings.EMPTY, Collections.emptyList()),version)) {
transport.transportServiceAdapter(serviceA.new Adapter());
transport.start();
DiscoveryNode node =
new DiscoveryNode("TS_TPC", "TS_TPC", transport.boundAddress().publishAddress(), emptyMap(), emptySet(),
Version.fromString("2.0.0"));
ConnectionProfile.Builder builder = new ConnectionProfile.Builder();
builder.addConnections(1,
TransportRequestOptions.Type.BULK,
TransportRequestOptions.Type.PING,
TransportRequestOptions.Type.RECOVERY,
TransportRequestOptions.Type.REG,
TransportRequestOptions.Type.STATE);
try (Transport.Connection connection = serviceA.openConnection(node, builder.build())) {
assertEquals(connection.getVersion(), version);
}
}
}
public void testTcpHandshake() throws IOException, InterruptedException { public void testTcpHandshake() throws IOException, InterruptedException {
assumeTrue("only tcp transport has a handshake method", serviceA.getOriginalTransport() instanceof TcpTransport); assumeTrue("only tcp transport has a handshake method", serviceA.getOriginalTransport() instanceof TcpTransport);
TcpTransport originalTransport = (TcpTransport) serviceA.getOriginalTransport(); TcpTransport originalTransport = (TcpTransport) serviceA.getOriginalTransport();
@ -1830,7 +1877,7 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase {
int messageLengthBytes, Version version, InetSocketAddress remoteAddress, byte status) int messageLengthBytes, Version version, InetSocketAddress remoteAddress, byte status)
throws IOException { throws IOException {
return super.handleRequest(mockChannel, profileName, stream, requestId, messageLengthBytes, version, remoteAddress, return super.handleRequest(mockChannel, profileName, stream, requestId, messageLengthBytes, version, remoteAddress,
(byte)(status & ~(1<<3))); // we flip the isHanshake bit back and ackt like the handler is not found (byte)(status & ~(1<<3))); // we flip the isHandshake bit back and act like the handler is not found
} }
}) { }) {
transport.transportServiceAdapter(serviceA.new Adapter()); transport.transportServiceAdapter(serviceA.new Adapter());