org.elasticsearch.discovery.zen.ping.multicast.MulticastZenPing Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search 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.discovery.zen.ping.multicast;
import org.elasticsearch.ElasticSearchException;
import org.elasticsearch.ElasticSearchIllegalStateException;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.discovery.DiscoveryException;
import org.elasticsearch.discovery.zen.DiscoveryNodesProvider;
import org.elasticsearch.discovery.zen.ping.ZenPing;
import org.elasticsearch.discovery.zen.ping.ZenPingException;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.*;
import org.elasticsearch.util.TimeValue;
import org.elasticsearch.util.component.AbstractLifecycleComponent;
import org.elasticsearch.util.io.stream.*;
import org.elasticsearch.util.network.NetworkService;
import org.elasticsearch.util.settings.Settings;
import java.io.IOException;
import java.net.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static org.elasticsearch.cluster.node.DiscoveryNode.*;
import static org.elasticsearch.util.concurrent.ConcurrentCollections.*;
import static org.elasticsearch.util.concurrent.DynamicExecutors.*;
import static org.elasticsearch.util.settings.ImmutableSettings.Builder.*;
/**
* @author kimchy (shay.banon)
*/
public class MulticastZenPing extends AbstractLifecycleComponent implements ZenPing {
private final String address;
private final int port;
private final String group;
private final int bufferSize;
private final int ttl;
private final ThreadPool threadPool;
private final TransportService transportService;
private final ClusterName clusterName;
private final NetworkService networkService;
private volatile DiscoveryNodesProvider nodesProvider;
private volatile Receiver receiver;
private volatile Thread receiverThread;
private MulticastSocket multicastSocket;
private DatagramPacket datagramPacketSend;
private DatagramPacket datagramPacketReceive;
private final AtomicInteger pingIdGenerator = new AtomicInteger();
private final Map> receivedResponses = newConcurrentMap();
private final Object sendMutex = new Object();
private final Object receiveMutex = new Object();
public MulticastZenPing(ThreadPool threadPool, TransportService transportService, ClusterName clusterName) {
this(EMPTY_SETTINGS, threadPool, transportService, clusterName, new NetworkService(EMPTY_SETTINGS));
}
public MulticastZenPing(Settings settings, ThreadPool threadPool, TransportService transportService, ClusterName clusterName, NetworkService networkService) {
super(settings);
this.threadPool = threadPool;
this.transportService = transportService;
this.clusterName = clusterName;
this.networkService = networkService;
this.address = componentSettings.get("address");
this.port = componentSettings.getAsInt("port", 54328);
this.group = componentSettings.get("group", "224.2.2.4");
this.bufferSize = componentSettings.getAsInt("buffer_size", 2048);
this.ttl = componentSettings.getAsInt("ttl", 3);
logger.debug("Using group [{}], with port [{}], ttl [{}], and address [{}]", group, port, ttl, address);
this.transportService.registerHandler(MulticastPingResponseRequestHandler.ACTION, new MulticastPingResponseRequestHandler());
}
@Override public void setNodesProvider(DiscoveryNodesProvider nodesProvider) {
if (lifecycle.started()) {
throw new ElasticSearchIllegalStateException("Can't set nodes provider when started");
}
this.nodesProvider = nodesProvider;
}
@Override protected void doStart() throws ElasticSearchException {
try {
this.datagramPacketReceive = new DatagramPacket(new byte[bufferSize], bufferSize);
this.datagramPacketSend = new DatagramPacket(new byte[bufferSize], bufferSize, InetAddress.getByName(group), port);
} catch (Exception e) {
throw new DiscoveryException("Failed to set datagram packets", e);
}
try {
MulticastSocket multicastSocket = new MulticastSocket(null);
multicastSocket.setReuseAddress(true);
// bind to receive interface
multicastSocket.bind(new InetSocketAddress(port));
multicastSocket.setTimeToLive(ttl);
// set the send interface
InetAddress multicastInterface = networkService.resolvePublishHostAddress(address);
multicastSocket.setInterface(multicastInterface);
multicastSocket.setReceiveBufferSize(bufferSize);
multicastSocket.setSendBufferSize(bufferSize);
multicastSocket.joinGroup(InetAddress.getByName(group));
multicastSocket.setSoTimeout(60000);
this.multicastSocket = multicastSocket;
} catch (Exception e) {
throw new DiscoveryException("Failed to setup multicast socket", e);
}
this.receiver = new Receiver();
this.receiverThread = daemonThreadFactory(settings, "discovery#multicast#received").newThread(receiver);
this.receiverThread.start();
}
@Override protected void doStop() throws ElasticSearchException {
receiver.stop();
receiverThread.interrupt();
multicastSocket.close();
}
@Override protected void doClose() throws ElasticSearchException {
}
public PingResponse[] pingAndWait(TimeValue timeout) {
final AtomicReference response = new AtomicReference();
final CountDownLatch latch = new CountDownLatch(1);
ping(new PingListener() {
@Override public void onPing(PingResponse[] pings) {
response.set(pings);
latch.countDown();
}
}, timeout);
try {
latch.await();
return response.get();
} catch (InterruptedException e) {
return null;
}
}
@Override public void ping(final PingListener listener, final TimeValue timeout) {
final int id = pingIdGenerator.incrementAndGet();
receivedResponses.put(id, new ConcurrentHashMap());
sendPingRequest(id);
// try and send another ping request halfway through (just in case someone woke up during it...)
// this can be a good trade-off to nailing the initial lookup or un-delivered messages
threadPool.schedule(new Runnable() {
@Override public void run() {
try {
sendPingRequest(id);
} catch (Exception e) {
logger.debug("[{}] Failed to send second ping request", e, id);
}
}
}, timeout.millis() / 2, TimeUnit.MILLISECONDS);
threadPool.schedule(new Runnable() {
@Override public void run() {
ConcurrentMap responses = receivedResponses.remove(id);
listener.onPing(responses.values().toArray(new PingResponse[responses.size()]));
}
}, timeout);
}
private void sendPingRequest(int id) {
synchronized (sendMutex) {
try {
HandlesStreamOutput out = BytesStreamOutput.Cached.cachedHandles();
out.writeInt(id);
clusterName.writeTo(out);
nodesProvider.nodes().localNode().writeTo(out);
datagramPacketSend.setData(((BytesStreamOutput) out.wrappedOut()).copiedByteArray());
} catch (IOException e) {
receivedResponses.remove(id);
throw new ZenPingException("Failed to serialize ping request", e);
}
try {
multicastSocket.send(datagramPacketSend);
if (logger.isTraceEnabled()) {
logger.trace("[{}] Sending ping request", id);
}
} catch (IOException e) {
receivedResponses.remove(id);
throw new ZenPingException("Failed to send ping request over multicast", e);
}
}
}
class MulticastPingResponseRequestHandler extends BaseTransportRequestHandler {
static final String ACTION = "discovery/zen/multicast";
@Override public MulticastPingResponse newInstance() {
return new MulticastPingResponse();
}
@Override public void messageReceived(MulticastPingResponse request, TransportChannel channel) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("[{}] Received {}", request.id, request.pingResponse);
}
ConcurrentMap responses = receivedResponses.get(request.id);
if (responses == null) {
logger.warn("Received ping response with no matching id [{}]", request.id);
} else {
responses.put(request.pingResponse.target(), request.pingResponse);
}
channel.sendResponse(VoidStreamable.INSTANCE);
}
}
static class MulticastPingResponse implements Streamable {
int id;
PingResponse pingResponse;
MulticastPingResponse() {
}
@Override public void readFrom(StreamInput in) throws IOException {
id = in.readInt();
pingResponse = PingResponse.readPingResponse(in);
}
@Override public void writeTo(StreamOutput out) throws IOException {
out.writeInt(id);
pingResponse.writeTo(out);
}
}
private class Receiver implements Runnable {
private volatile boolean running = true;
public void stop() {
running = false;
}
@Override public void run() {
while (running) {
try {
int id;
DiscoveryNode requestingNodeX;
ClusterName clusterName;
synchronized (receiveMutex) {
try {
multicastSocket.receive(datagramPacketReceive);
} catch (SocketTimeoutException ignore) {
continue;
} catch (Exception e) {
if (running) {
logger.warn("Failed to receive packet", e);
}
continue;
}
try {
StreamInput input = HandlesStreamInput.Cached.cached(new BytesStreamInput(datagramPacketReceive.getData(), datagramPacketReceive.getOffset(), datagramPacketReceive.getLength()));
id = input.readInt();
clusterName = ClusterName.readClusterName(input);
requestingNodeX = readNode(input);
} catch (Exception e) {
logger.warn("Failed to read requesting node from {}", e, datagramPacketReceive.getSocketAddress());
continue;
}
}
DiscoveryNodes discoveryNodes = nodesProvider.nodes();
final DiscoveryNode requestingNode = requestingNodeX;
if (requestingNode.id().equals(discoveryNodes.localNodeId())) {
// that's me, ignore
continue;
}
if (!clusterName.equals(MulticastZenPing.this.clusterName)) {
// not our cluster, ignore it...
continue;
}
final MulticastPingResponse multicastPingResponse = new MulticastPingResponse();
multicastPingResponse.id = id;
multicastPingResponse.pingResponse = new PingResponse(discoveryNodes.localNode(), discoveryNodes.masterNode(), clusterName);
if (logger.isTraceEnabled()) {
logger.trace("[{}] Received ping_request from [{}], sending {}", id, requestingNode, multicastPingResponse.pingResponse);
}
if (!transportService.nodeConnected(requestingNode)) {
// do the connect and send on a thread pool
threadPool.execute(new Runnable() {
@Override public void run() {
// connect to the node if possible
try {
transportService.connectToNode(requestingNode);
} catch (Exception e) {
logger.warn("Failed to connect to requesting node {}", e, requestingNode);
}
transportService.sendRequest(requestingNode, MulticastPingResponseRequestHandler.ACTION, multicastPingResponse, new VoidTransportResponseHandler(false) {
@Override public void handleException(RemoteTransportException exp) {
logger.warn("Failed to receive confirmation on sent ping response to [{}]", exp, requestingNode);
}
});
}
});
} else {
transportService.sendRequest(requestingNode, MulticastPingResponseRequestHandler.ACTION, multicastPingResponse, new VoidTransportResponseHandler(false) {
@Override public void handleException(RemoteTransportException exp) {
logger.warn("Failed to receive confirmation on sent ping response to [{}]", exp, requestingNode);
}
});
}
} catch (Exception e) {
logger.warn("Unexpected exception in multicast receiver", e);
}
}
}
}
}