All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.discovery.HandshakingTransportAddressConnector Maven / Gradle / Ivy

There is a newer version: 8.14.0
Show newest version
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

package org.elasticsearch.discovery;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRunnable;
import org.elasticsearch.action.NotifyOnceListener;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.Randomness;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.core.Releasable;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.transport.ConnectTransportException;
import org.elasticsearch.transport.ConnectionProfile;
import org.elasticsearch.transport.TransportRequestOptions.Type;
import org.elasticsearch.transport.TransportService;

import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;

public class HandshakingTransportAddressConnector implements TransportAddressConnector {

    private static final Logger logger = LogManager.getLogger(HandshakingTransportAddressConnector.class);

    // connection timeout for probes
    public static final Setting PROBE_CONNECT_TIMEOUT_SETTING = Setting.timeSetting(
        "discovery.probe.connect_timeout",
        TimeValue.timeValueSeconds(30),
        TimeValue.timeValueMillis(1),
        Setting.Property.NodeScope
    );
    // handshake timeout for probes
    public static final Setting PROBE_HANDSHAKE_TIMEOUT_SETTING = Setting.timeSetting(
        "discovery.probe.handshake_timeout",
        TimeValue.timeValueSeconds(30),
        TimeValue.timeValueMillis(1),
        Setting.Property.NodeScope
    );

    private final TransportService transportService;
    private final TimeValue probeConnectTimeout;
    private final TimeValue probeHandshakeTimeout;

    public HandshakingTransportAddressConnector(Settings settings, TransportService transportService) {
        this.transportService = transportService;
        probeConnectTimeout = PROBE_CONNECT_TIMEOUT_SETTING.get(settings);
        probeHandshakeTimeout = PROBE_HANDSHAKE_TIMEOUT_SETTING.get(settings);
    }

    @Override
    public void connectToRemoteMasterNode(TransportAddress transportAddress, ActionListener listener) {
        transportService.getThreadPool().generic().execute(new ActionRunnable(listener) {
            private final AbstractRunnable thisConnectionAttempt = this;

            @Override
            protected void doRun() {
                // We could skip this if the transportService were already connected to the given address, but the savings would be minimal
                // so we open a new connection anyway.

                final DiscoveryNode targetNode = new DiscoveryNode(
                    "",
                    transportAddress.toString(),
                    UUIDs.randomBase64UUID(Randomness.get()), // generated deterministically for reproducible tests
                    transportAddress.address().getHostString(),
                    transportAddress.getAddress(),
                    transportAddress,
                    emptyMap(),
                    emptySet(),
                    Version.CURRENT.minimumCompatibilityVersion()
                );

                logger.trace("[{}] opening probe connection", thisConnectionAttempt);
                transportService.openConnection(
                    targetNode,
                    ConnectionProfile.buildSingleChannelProfile(
                        Type.REG,
                        probeConnectTimeout,
                        probeHandshakeTimeout,
                        TimeValue.MINUS_ONE,
                        null,
                        null
                    ),
                    listener.delegateFailure((l, connection) -> {
                        logger.trace("[{}] opened probe connection", thisConnectionAttempt);

                        // use NotifyOnceListener to make sure the following line does not result in onFailure being called when
                        // the connection is closed in the onResponse handler
                        transportService.handshake(connection, probeHandshakeTimeout, new NotifyOnceListener() {

                            @Override
                            protected void innerOnResponse(DiscoveryNode remoteNode) {
                                try {
                                    // success means (amongst other things) that the cluster names match
                                    logger.trace("[{}] handshake successful: {}", thisConnectionAttempt, remoteNode);
                                    IOUtils.closeWhileHandlingException(connection);

                                    if (remoteNode.equals(transportService.getLocalNode())) {
                                        listener.onFailure(new ConnectTransportException(remoteNode, "local node found"));
                                    } else if (remoteNode.isMasterNode() == false) {
                                        listener.onFailure(new ConnectTransportException(remoteNode, "non-master-eligible node found"));
                                    } else {
                                        transportService.connectToNode(remoteNode, new ActionListener() {
                                            @Override
                                            public void onResponse(Releasable connectionReleasable) {
                                                logger.trace("[{}] completed full connection with [{}]", thisConnectionAttempt, remoteNode);
                                                listener.onResponse(new ProbeConnectionResult(remoteNode, connectionReleasable));
                                            }

                                            @Override
                                            public void onFailure(Exception e) {
                                                // we opened a connection and successfully performed a handshake, so we're definitely
                                                // talking to a master-eligible node with a matching cluster name and a good version, but
                                                // the attempt to open a full connection to its publish address failed; a common reason is
                                                // that the remote node is listening on 0.0.0.0 but has made an inappropriate choice for its
                                                // publish address.
                                                logger.warn(
                                                    new ParameterizedMessage(
                                                        "[{}] completed handshake with [{}] but followup connection failed",
                                                        thisConnectionAttempt,
                                                        remoteNode
                                                    ),
                                                    e
                                                );
                                                listener.onFailure(e);
                                            }
                                        });
                                    }
                                } catch (Exception e) {
                                    listener.onFailure(e);
                                }
                            }

                            @Override
                            protected void innerOnFailure(Exception e) {
                                // we opened a connection and successfully performed a low-level handshake, so we were definitely
                                // talking to an Elasticsearch node, but the high-level handshake failed indicating some kind of
                                // mismatched configurations (e.g. cluster name) that the user should address
                                logger.warn(new ParameterizedMessage("handshake failed for [{}]", thisConnectionAttempt), e);
                                IOUtils.closeWhileHandlingException(connection);
                                listener.onFailure(e);
                            }
                        });
                    })
                );
            }

            @Override
            public void onFailure(Exception e) {
                listener.onFailure(e);
            }

            @Override
            public String toString() {
                return "connectToRemoteMasterNode[" + transportAddress + "]";
            }
        });
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy