/*
* Copyright (C) 2008 Yohan Liyanage.
*
* Licensed 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.nebulaframework.discovery.multicast;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nebulaframework.grid.Grid;
import org.nebulaframework.grid.cluster.manager.ClusterManager;
import org.nebulaframework.util.net.NetUtils;
/**
* Implementation of Multicast Discovery. This class provides necessary
* mechanisms to discover clusters using multicast messages. All
* ClusterManager's will start a multicast discovery service which listens
* to multicast requests for a specific IP Address. When such a message
* is received, it will respond on a designated response channel (another
* multicast IP Address).
*
* @author Yohan Liyanage
* @version 1.0
*/
public class MulticastDiscovery {
private static Log log = LogFactory.getLog(MulticastDiscovery.class);
/** Multicast Service Port */
public static final int SERVICE_PORT = 8787;
/** Request Channel IP Address */
public static final InetAddress SERVICE_REQUEST_IP;
/** Response Channel IP Address*/
public static final InetAddress SERVICE_RESPONSE_IP;
/** Default Greeting Message */
public static final String GREET_MSG = "ALOHA";
/** Multicast Discovery Timout */
public static final long TIMEOUT = 10000L;
private String cluster = null;
/**
* Static initilization to resolve Multicast
* service IP Address.
*/
static {
try {
SERVICE_REQUEST_IP = InetAddress.getByName("230.0.0.1");
SERVICE_RESPONSE_IP = InetAddress.getByName("230.0.0.2");
} catch (UnknownHostException e) {
// Should not happen
throw new AssertionError(e);
}
}
/**
* Private Constructor. No External Instantiation.
*/
private MulticastDiscovery() {
// No External Instantiation
}
/**
* Starts Multicast Discovery Service. This can only
* be invoked by a Nebula ClusterManager.
*
* @throws IOException if occurred during operation
* @throws UnsupportedOperationException if invoked by non-ClusterManager nodes.
*/
public static void startService() throws IOException, UnsupportedOperationException {
// Only allowed for ClusterManagers
if (! Grid.isClusterManager()) {
throw new UnsupportedOperationException("Multicast Discovery Service can be enabled only for ClusterManagers");
}
// Start Service
Thread t = new Thread(new Runnable() {
public void run() {
try {
// Start Multicast Socket and listen for Requests
final MulticastSocket mSock = new MulticastSocket(SERVICE_PORT);
mSock.joinGroup(SERVICE_REQUEST_IP);
// Infinite Loop
while (true) {
// Buffer (for Greeting Message)
byte[] msg = new byte[GREET_MSG.getBytes("UTF-8").length];
// Create Datagram Packet
DatagramPacket packet = new DatagramPacket(msg, msg.length);
// Wait and Receive Request
mSock.receive(packet);
log.debug("[MulticastDiscovery] Received Discovery Request");
// Check if Greeting Message is valid
try {
String greet = new String(packet.getData());
if (!greet.equals(GREET_MSG)) {
throw new IllegalArgumentException("Invalid Greeting");
}
}
catch (Exception e) {
log.debug("Malformed Multicast Message Igonored");
continue;
}
// Respond
doRespond();
}
} catch (IOException e) {
log.error("[MulticastDiscovery] Service Failed on Receive",e);
}
}
});
t.setDaemon(true); // Run as Daemon thread
t.start(); // Start Service
log.debug("[MulticastDiscovery] Service Started");
}
/**
* Responds to a Multicast Discovery Request by publishing
* the IP Address of Service into response channel.
*/
protected static void doRespond() {
// Only allowed for ClusterManagers
if (! Grid.isClusterManager()) {
throw new UnsupportedOperationException("Multicast Discovery Service can be enabled only for ClusterManagers");
}
try {
// Get Broker Service URL
String serviceUrl = ClusterManager.getInstance().getClusterInfo().getServiceUrl();
byte[] hostInfo = NetUtils.getHostInfoAsBytes(serviceUrl);
// Create Response Packet
DatagramPacket response = new DatagramPacket(hostInfo, hostInfo.length, SERVICE_RESPONSE_IP, SERVICE_PORT);
// Create Multicast Socket
MulticastSocket resSock = new MulticastSocket();
// Send response
resSock.send(response);
log.debug("[MulticastDiscovery] Responded Discovery Request");
} catch (Exception e) {
log.error("[MulticastDiscovery] Service Failed to Reply",e);
}
}
/**
* Invoked by GridNodes to detect Clusters with in multicast
* reachable network range. The discovery process returns
* without results if no cluster was discovered with in
* a fixed duration, set by {@link #TIMEOUT}.
*
* @return IP Address and Port of detected Cluster (String)
*/
public static String discoverCluster() {
log.debug("[MulticastDiscovery] Attempting to discover cluster");
// Synchronization Mutex
final Object mutex = new Object();
// Create Instance of MulticastDiscovery
final MulticastDiscovery mDisc = new MulticastDiscovery();
// Execute on a separate Thread
new Thread(new Runnable(){
public void run() {
try {
// Attempt Discovery
mDisc.doDiscover();
} catch (IOException e) {
log.debug("[MulticastDiscovery] Failed to Discover",e);
}
// Notify waiting Threads
synchronized (mutex) {
mutex.notifyAll();
}
}
}).start();
// Wait till response come or timeout happens
synchronized (mutex) {
try {
mutex.wait(TIMEOUT);
} catch (InterruptedException e) {
// Should not happen
throw new AssertionError(e);
}
}
if (mDisc.cluster != null) {
log.info("[MulticastDiscovery] Discovered Cluster at " + mDisc.cluster);
}
return mDisc.cluster;
}
/**
* Discovery Process to identify Clusters with in
* network.
*
* @throws IOException if occurred during operation
*/
private void doDiscover() throws IOException {
// Send Request
byte[] greet = GREET_MSG.getBytes("UTF-8");
DatagramPacket request = new DatagramPacket(greet, greet.length, SERVICE_REQUEST_IP, SERVICE_PORT);
MulticastSocket reqSock = new MulticastSocket();
reqSock.send(request);
// Wait for Response
MulticastSocket resSock = new MulticastSocket(SERVICE_PORT);
resSock.joinGroup(SERVICE_RESPONSE_IP);
// 9 = # of bytes for an IP Address + 5 byte port
DatagramPacket response = new DatagramPacket(new byte[9],9);
// Receive
resSock.setSoTimeout((int)TIMEOUT);
try {
resSock.receive(response);
} catch (SocketTimeoutException e) {
log.debug("[MulticastDiscovery] Receive Timeout");
return;
}
byte[] data = response.getData();
byte[] ipBytes = Arrays.copyOfRange(data, 0,4);
byte[] portBytes = Arrays.copyOfRange(data, 4, 9);
InetAddress ip = InetAddress.getByAddress(ipBytes);
StringBuilder sb = new StringBuilder(ip.getHostAddress());
sb.append(":");
for(byte b:portBytes) {
sb.append(b);
}
this.cluster = sb.toString();
}
/**
* Attempts to discover peer clusters using Multicast
* Discovery. Each discovered Cluster will be notified
* to the {@code PeerClusterService} of the
* ClusterManager.
*
* @throws IOException if occurred during process
*/
public static void discoverPeerClusters() throws IOException {
// Only allowed for ClusterManagers
if (! Grid.isClusterManager()) {
throw new UnsupportedOperationException("Multicast Discovery Service can be enabled only for ClusterManagers");
}
log.info("[MulticastDiscovery] Discovering Peer Clusters...");
// Send Request
byte[] greet = GREET_MSG.getBytes("UTF-8");
DatagramPacket request = new DatagramPacket(greet, greet.length, SERVICE_REQUEST_IP, SERVICE_PORT);
MulticastSocket reqSock = new MulticastSocket();
reqSock.send(request);
// Response Socket
MulticastSocket resSock = new MulticastSocket(SERVICE_PORT);
resSock.joinGroup(SERVICE_RESPONSE_IP);
// 9 = # of bytes for an IP Address + 5 byte port
DatagramPacket response = new DatagramPacket(new byte[9],9);
// Set Socket Timeout
resSock.setSoTimeout((int)TIMEOUT);
try {
// Loop until Socket Timeout Occurs
while(true) {
// Receive
resSock.receive(response);
processPeerResponse(response.getData());
}
} catch (SocketTimeoutException e) {
log.debug("[MulticastDiscovery] Receive Timeout");
return;
}
finally {
log.info("[MulticastDiscovery] Peer Cluster Discovery Complete");
}
}
/**
* Support routine which processes a response received for a discovery
* request for peer clusters. Each invocation will be handled
* in a separate thread.
*
* @param data response data
*/
private static void processPeerResponse(final byte[] data) {
new Thread(new Runnable() {
@Override
public void run() {
try {
byte[] ipBytes = Arrays.copyOfRange(data, 0,4);
byte[] portBytes = Arrays.copyOfRange(data, 4, 9);
InetAddress ip = null;
ip = InetAddress.getByAddress(ipBytes);
StringBuilder sb = new StringBuilder(ip.getHostAddress());
sb.append(":");
for(byte b:portBytes) {
sb.append(b);
}
// Add Peer Cluster
ClusterManager.getInstance().getPeerService().addCluster(sb.toString());
} catch (Exception e) {
log.warn("[Discovery] Unable to resolve peer");
}
}
}).start();
}
}