Author: bstansberry(a)jboss.com
Date: 2008-10-28 18:25:35 -0400 (Tue, 28 Oct 2008)
New Revision: 2015
Added:
trunk/mod_cluster/src/main/java/org/jboss/modcluster/advertise/impl/OSResolver.java
trunk/mod_cluster/src/test/java/org/jboss/modcluster/advertise/
trunk/mod_cluster/src/test/java/org/jboss/modcluster/advertise/AdvertiseListenerImplTestCase.java
Modified:
trunk/mod_cluster/src/main/java/org/jboss/modcluster/advertise/impl/AdvertiseListenerImpl.java
Log:
[JBNATIVE-68] AdvertiseListenerImple avoids multicast cross-talk
Modified:
trunk/mod_cluster/src/main/java/org/jboss/modcluster/advertise/impl/AdvertiseListenerImpl.java
===================================================================
---
trunk/mod_cluster/src/main/java/org/jboss/modcluster/advertise/impl/AdvertiseListenerImpl.java 2008-10-28
19:06:50 UTC (rev 2014)
+++
trunk/mod_cluster/src/main/java/org/jboss/modcluster/advertise/impl/AdvertiseListenerImpl.java 2008-10-28
22:25:35 UTC (rev 2015)
@@ -26,8 +26,11 @@
import java.io.IOException;
import java.net.DatagramPacket;
+import java.net.Inet4Address;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
import java.net.MulticastSocket;
+import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -84,6 +87,8 @@
final Map<String, AdvertisedServer> servers = new HashMap<String,
AdvertisedServer>();
final MCMPHandler commHandler;
+ private final OSResolver osResolver;
+
private Thread workerThread;
/** The string manager for this package. */
@@ -112,11 +117,26 @@
* Create AdvertiseListener instance
* @param eventHandler The event handler that will be used for
* status and new server notifications.
+ * @param config our configuration
*/
public AdvertiseListenerImpl(MCMPHandler commHandler, MCMPHandlerConfiguration
config)
{
+ this(commHandler, config, new OSResolver());
+ }
+
+ /**
+ * Constructor meant for use by test cases; allows injection of a non-standard
OSResolver.
+ *
+ * @param eventHandler The event handler that will be used for
+ * status and new server notifications.
+ * @param config our configuration
+ * @param resolver the OSResolver
+ */
+ public AdvertiseListenerImpl(MCMPHandler commHandler, MCMPHandlerConfiguration config,
OSResolver resolver)
+ {
this.commHandler = commHandler;
-
+ this.osResolver = resolver == null ? new OSResolver() : resolver;
+
try
{
this.setGroupAddress(config.getAdvertiseGroupAddress());
@@ -228,7 +248,17 @@
{
if (!this.initialized)
{
- this.ms = new MulticastSocket(this.advertisePort);
+ // On Linux, we avoid cross-talk problem by binding the MulticastSocket
+ // to the multicast address. See
https://jira.jboss.org/jira/browse/JGRP-777
+ if(osResolver.isLinux())
+ {
+ this.ms = createMulticastSocket(this.groupAddress, this.advertisePort);
+ }
+ else
+ {
+ this.ms = new MulticastSocket(this.advertisePort);
+ }
+
this.ms.setTimeToLive(16);
this.ms.joinGroup(this.groupAddress);
@@ -391,7 +421,39 @@
return true;
}
+
+
+
+ private MulticastSocket createMulticastSocket(InetAddress mcast_addr, int port) throws
IOException
+ {
+ if(mcast_addr != null && !mcast_addr.isMulticastAddress()) {
+ log.warn("mcast_addr (" + mcast_addr + ") is not a multicast
address, will be ignored");
+ return new MulticastSocket(port);
+ }
+ SocketAddress saddr=new InetSocketAddress(mcast_addr, port);
+ MulticastSocket retval=null;
+
+ try {
+ retval=new MulticastSocket(saddr);
+ }
+ catch(IOException ex) {
+ StringBuilder sb=new StringBuilder();
+ String type=mcast_addr != null ? mcast_addr instanceof Inet4Address?
"IPv4" : "IPv6" : "n/a";
+ sb.append("could not bind to " + mcast_addr + " (" + type +
" address)");
+ sb.append("; make sure your mcast_addr is of the same type as the IP stack
(IPv4 or IPv6).");
+ sb.append("\nWill ignore mcast_addr, but this may lead to cross talking
" +
+ "(see
http://www.jboss.com/wiki/Edit.jsp?page=CrossTalking for
details). ");
+ sb.append("\nException was: " + ex);
+ log.warn(sb);
+ }
+ if(retval == null)
+ {
+ retval=new MulticastSocket(port);
+ }
+ return retval;
+ }
+
// ------------------------------------ AdvertiseListenerWorker Inner Class
class AdvertiseListenerWorker implements Runnable
{
Added:
trunk/mod_cluster/src/main/java/org/jboss/modcluster/advertise/impl/OSResolver.java
===================================================================
--- trunk/mod_cluster/src/main/java/org/jboss/modcluster/advertise/impl/OSResolver.java
(rev 0)
+++
trunk/mod_cluster/src/main/java/org/jboss/modcluster/advertise/impl/OSResolver.java 2008-10-28
22:25:35 UTC (rev 2015)
@@ -0,0 +1,81 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.jboss.modcluster.advertise.impl;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * Provides information about the underlying operating system.
+ * <p>
+ * This class basically provides a hook to allow tests to pretend the Linux
+ * OS is not Linux, for testing the JBNATIVE-68 fix. This default implementation
+ * does not pretend Linux isn't Linux, but subclasses can.
+ * </p>
+ *
+ * @author Brian Stansberry
+ */
+public class OSResolver
+{
+ /**
+ * Is the underlying operation system Linux?
+ *
+ * @return
+ */
+ public boolean isLinux()
+ {
+ return checkForPresence("os.name", "linux");
+ }
+
+ private boolean checkForPresence(final String key, String value)
+ {
+ try
+ {
+ String tmp= null;
+ if (System.getSecurityManager() == null)
+ {
+ tmp = System.getProperty(key);
+ }
+ else
+ {
+ // Use a different local var to limit scope of @SuppressWarnings
+ @SuppressWarnings("unchecked")
+ String prop = (String) AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ return System.getProperty(key);
+ }
+
+ });
+ tmp = prop;
+ }
+
+ return tmp != null && tmp.trim().toLowerCase().startsWith(value);
+ }
+ catch (Throwable t)
+ {
+ return false;
+ }
+ }
+}
Added:
trunk/mod_cluster/src/test/java/org/jboss/modcluster/advertise/AdvertiseListenerImplTestCase.java
===================================================================
---
trunk/mod_cluster/src/test/java/org/jboss/modcluster/advertise/AdvertiseListenerImplTestCase.java
(rev 0)
+++
trunk/mod_cluster/src/test/java/org/jboss/modcluster/advertise/AdvertiseListenerImplTestCase.java 2008-10-28
22:25:35 UTC (rev 2015)
@@ -0,0 +1,170 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.jboss.modcluster.advertise;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import org.easymock.EasyMock;
+import org.jboss.modcluster.advertise.impl.AdvertiseListenerImpl;
+import org.jboss.modcluster.advertise.impl.OSResolver;
+import org.jboss.modcluster.config.MCMPHandlerConfiguration;
+import org.jboss.modcluster.mcmp.MCMPHandler;
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Tests of {@link AdvertiseListenerImpl}.
+ *
+ * @author Brian Stansberry
+ */
+public class AdvertiseListenerImplTestCase
+{
+ private static final String ADVERTISE_GROUP = "224.0.1.106";
+ private static final int ADVERTISE_PORT = 23365;
+ private static final String RFC_822_FMT = "EEE, d MMM yyyy HH:mm:ss Z";
+ private static final DateFormat df = new SimpleDateFormat(RFC_822_FMT, Locale.US);
+ private static final String SERVER = "foo.bar.com";
+ private static final String SERVER_ADDRESS = SERVER + ":8888";
+
+ private MCMPHandler mcmpHandler = EasyMock.createStrictMock(MCMPHandler.class);
+ private MCMPHandlerConfiguration mcmpConfig =
EasyMock.createMock(MCMPHandlerConfiguration.class);
+
+ private MulticastSocket mcastSocket;
+ private AdvertiseListenerImpl testee;
+
+ @After
+ public void tearDown()
+ {
+ if (mcastSocket != null)
+ mcastSocket.close();
+ if (testee != null)
+ testee.stop();
+ }
+
+ @Test
+ public void testBasicOperation() throws IOException
+ {
+ basicOperationTest(false);
+ }
+
+ @Test
+ public void testBasicOperationWithLinux() throws IOException
+ {
+ basicOperationTest(true);
+ }
+
+ private void basicOperationTest(boolean linux) throws IOException
+ {
+ ConfigurableOSResolver resolver = new ConfigurableOSResolver(linux);
+
+
EasyMock.expect(this.mcmpConfig.getAdvertiseGroupAddress()).andReturn(ADVERTISE_GROUP);
+ EasyMock.expect(this.mcmpConfig.getAdvertisePort()).andReturn(23365);
+ EasyMock.expect(this.mcmpConfig.getAdvertiseSecurityKey()).andReturn(null);
+
+ EasyMock.replay(this.mcmpConfig);
+
+ this.testee = new AdvertiseListenerImpl(this.mcmpHandler, this.mcmpConfig,
resolver);
+ this.testee.start();
+
+ try
+ {
+ Thread.sleep(20);
+ }
+ catch (InterruptedException e)
+ {
+ Thread.interrupted();
+ }
+
+ EasyMock.verify(this.mcmpConfig);
+
+ this.mcastSocket = new MulticastSocket(ADVERTISE_PORT);
+ InetAddress mcastGroup = InetAddress.getByName(ADVERTISE_GROUP);
+ this.mcastSocket.joinGroup(InetAddress.getByName(ADVERTISE_GROUP));
+
+ String date = df.format(new Date());
+
+ StringBuilder data = new StringBuilder("HTTP/1.1 200 OK\r\n");
+ data.append("Date: ");
+ data.append(date);
+ data.append("\r\n");
+ data.append("Server: ");
+ data.append(SERVER);
+ data.append("\r\n");
+ data.append("X-Manager-Address: ");
+ data.append(SERVER_ADDRESS);
+ data.append("\r\n");
+
+ byte[] buf = data.toString().getBytes();
+ DatagramPacket packet = new DatagramPacket(buf, buf.length, mcastGroup,
ADVERTISE_PORT);
+
+ this.mcmpHandler.addProxy(SERVER_ADDRESS);
+ EasyMock.replay(this.mcmpHandler);
+
+ this.mcastSocket.send(packet);
+
+ try
+ {
+ Thread.sleep(20);
+ }
+ catch (InterruptedException e)
+ {
+ Thread.interrupted();
+ }
+
+ EasyMock.verify(this.mcmpHandler);
+ }
+
+ private static class ConfigurableOSResolver extends OSResolver
+ {
+ private boolean allowLinux;
+
+ public ConfigurableOSResolver(boolean allowLinux)
+ {
+ this.allowLinux = allowLinux;
+ }
+
+ public boolean isAllowLinux()
+ {
+ return allowLinux;
+ }
+
+ public void setAllowLinux(boolean allowLinux)
+ {
+ this.allowLinux = allowLinux;
+ }
+
+ @Override
+ public boolean isLinux()
+ {
+ return allowLinux && super.isLinux();
+ }
+
+ }
+}