This is an automated email from the ASF dual-hosted git repository.
csutherl pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new a530ce4304 Skip tribes membership tests when IP multicast is unavailable (#1052)
a530ce4304 is described below
commit a530ce4304e503a5ae8e4314a7c5cf21fb605373
Author: Coty Sutherland <[email protected]>
AuthorDate: Tue Aug 25 14:38:19 2026 -0400
Skip tribes membership tests when IP multicast is unavailable (#1052)
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
test/org/apache/catalina/tribes/TesterUtil.java | 64 ++++++++++++++++++++++
.../group/TestGroupChannelMemberArrival.java | 3 +
.../interceptors/TestDomainFilterInterceptor.java | 4 ++
.../interceptors/TestNonBlockingCoordinator.java | 7 +++
.../group/interceptors/TestOrderInterceptor.java | 7 +++
.../group/interceptors/TestTcpFailureDetector.java | 3 +
6 files changed, 88 insertions(+)
diff --git a/test/org/apache/catalina/tribes/TesterUtil.java b/test/org/apache/catalina/tribes/TesterUtil.java
index 54a8db3957..78458cbd74 100644
--- a/test/org/apache/catalina/tribes/TesterUtil.java
+++ b/test/org/apache/catalina/tribes/TesterUtil.java
@@ -16,6 +16,15 @@
*/
package org.apache.catalina.tribes;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MulticastSocket;
+import java.net.StandardSocketOptions;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+
import org.apache.catalina.tribes.group.interceptors.DomainFilterInterceptor;
import org.apache.catalina.tribes.util.UUIDGenerator;
@@ -29,6 +38,61 @@ public class TesterUtil {
}
+ /*
+ * Determines whether IP multicast is actually usable in the current
+ * environment. The tribes membership tests rely on the default
+ * {@link org.apache.catalina.tribes.membership.McastService} which
+ * discovers members via IP multicast. Some environments (containers,
+ * minimal CI images, hosts with multicast blocked by a firewall or with no
+ * multicast-capable network interface) cannot deliver multicast traffic. In
+ * those environments the affected tests would fail on a membership timeout
+ * through no fault of the code under test, so they should be skipped instead.
+ *
+ * This performs a functional check - it sends a multicast datagram to the
+ * default group over loopback and confirms it is received - rather than
+ * simply inspecting interface flags, so that it also detects environments
+ * where multicast sockets can be created but traffic is silently dropped.
+ * Any failure is treated as "multicast not available" so a hostile
+ * environment results in a skipped test rather than a spurious failure.
+ */
+ public static boolean isMulticastAvailable() {
+ InetAddress group;
+ try {
+ // Same default group as McastService
+ group = InetAddress.getByName("228.0.0.4");
+ } catch (UnknownHostException e) {
+ return false;
+ }
+
+ // Bind to an ephemeral port (not the McastService default) so this probe
+ // never clashes with a running membership service on the same host.
+ try (MulticastSocket socket = new MulticastSocket(0)) {
+ // Loopback must be enabled for the probe to receive its own packet.
+ socket.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, Boolean.TRUE);
+ socket.setSoTimeout(1000);
+
+ int port = socket.getLocalPort();
+ InetSocketAddress groupAddress = new InetSocketAddress(group, port);
+ // null network interface -> let the OS pick the default, mirroring
+ // McastServiceImpl when no bind address is configured.
+ socket.joinGroup(groupAddress, null);
+ try {
+ byte[] probe = "tomcat-tribes-multicast-probe".getBytes(StandardCharsets.UTF_8);
+ socket.send(new DatagramPacket(probe, probe.length, group, port));
+
+ DatagramPacket received = new DatagramPacket(new byte[probe.length], probe.length);
+ // Throws SocketTimeoutException if the packet is not delivered.
+ socket.receive(received);
+ return received.getLength() == probe.length;
+ } finally {
+ socket.leaveGroup(groupAddress, null);
+ }
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+
/*
* Configures a set of channels to use a random domain. Use to ensure that
* multiple instance of the test suite do not interfere when running on the
diff --git a/test/org/apache/catalina/tribes/group/TestGroupChannelMemberArrival.java b/test/org/apache/catalina/tribes/group/TestGroupChannelMemberArrival.java
index 1173bded69..34ad534f4d 100644
--- a/test/org/apache/catalina/tribes/group/TestGroupChannelMemberArrival.java
+++ b/test/org/apache/catalina/tribes/group/TestGroupChannelMemberArrival.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import org.junit.After;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
@@ -37,6 +38,8 @@ public class TestGroupChannelMemberArrival {
@Before
public void setUp() throws Exception {
+ Assume.assumeTrue("Skipping test - IP multicast is not available in this environment",
+ TesterUtil.isMulticastAvailable());
for (int i = 0; i < channels.length; i++) {
channels[i] = new GroupChannel();
((ReceiverBase) channels[i].getChannelReceiver()).setHost("localhost");
diff --git a/test/org/apache/catalina/tribes/group/interceptors/TestDomainFilterInterceptor.java b/test/org/apache/catalina/tribes/group/interceptors/TestDomainFilterInterceptor.java
index 626f878a5f..55c45f4f1d 100644
--- a/test/org/apache/catalina/tribes/group/interceptors/TestDomainFilterInterceptor.java
+++ b/test/org/apache/catalina/tribes/group/interceptors/TestDomainFilterInterceptor.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import org.junit.After;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
@@ -27,6 +28,7 @@ import org.apache.catalina.tribes.Channel;
import org.apache.catalina.tribes.ManagedChannel;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.TesterUtil;
import org.apache.catalina.tribes.group.GroupChannel;
import org.apache.catalina.tribes.util.UUIDGenerator;
@@ -37,6 +39,8 @@ public class TestDomainFilterInterceptor {
@Before
public void setUp() throws Exception {
+ Assume.assumeTrue("Skipping test - IP multicast is not available in this environment",
+ TesterUtil.isMulticastAvailable());
for (int i = 0; i < channels.length; i++) {
channels[i] = new GroupChannel();
channels[i].getMembershipService().setPayload( ("Channel-" + (i + 1)).getBytes("ASCII"));
diff --git a/test/org/apache/catalina/tribes/group/interceptors/TestNonBlockingCoordinator.java b/test/org/apache/catalina/tribes/group/interceptors/TestNonBlockingCoordinator.java
index 1ad80db300..3c61c0952c 100644
--- a/test/org/apache/catalina/tribes/group/interceptors/TestNonBlockingCoordinator.java
+++ b/test/org/apache/catalina/tribes/group/interceptors/TestNonBlockingCoordinator.java
@@ -21,6 +21,7 @@ import java.util.logging.LogManager;
import org.junit.After;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
@@ -43,6 +44,8 @@ public class TestNonBlockingCoordinator {
@Before
public void setUp() throws Exception {
+ Assume.assumeTrue("Skipping test - IP multicast is not available in this environment",
+ TesterUtil.isMulticastAvailable());
LogManager.getLogManager().getLogger(
"org.apache.catalina.tribes.group.interceptors.TestNonBlockingCoordinator").setLevel(Level.ALL);
try {
@@ -177,6 +180,10 @@ public class TestNonBlockingCoordinator {
@After
public void tearDown() throws Exception {
log.info("tearDown");
+ if (channels == null) {
+ // setUp was skipped (e.g. multicast unavailable)
+ return;
+ }
for ( int i=0; i<CHANNEL_COUNT; i++ ) {
channels[i].stop(Channel.DEFAULT);
}
diff --git a/test/org/apache/catalina/tribes/group/interceptors/TestOrderInterceptor.java b/test/org/apache/catalina/tribes/group/interceptors/TestOrderInterceptor.java
index c5d718e29e..704472259a 100644
--- a/test/org/apache/catalina/tribes/group/interceptors/TestOrderInterceptor.java
+++ b/test/org/apache/catalina/tribes/group/interceptors/TestOrderInterceptor.java
@@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
@@ -48,6 +49,8 @@ public class TestOrderInterceptor {
@Before
public void setUp() throws Exception {
+ Assume.assumeTrue("Skipping test - IP multicast is not available in this environment",
+ TesterUtil.isMulticastAvailable());
System.out.println("Setup");
channels = new GroupChannel[channelCount];
orderitcs = new OrderInterceptor[channelCount];
@@ -152,6 +155,10 @@ public class TestOrderInterceptor {
@After
public void tearDown() throws Exception {
System.out.println("tearDown");
+ if (channels == null) {
+ // setUp was skipped (e.g. multicast unavailable)
+ return;
+ }
for ( int i=0; i<channelCount; i++ ) {
channels[i].stop(Channel.DEFAULT);
}
diff --git a/test/org/apache/catalina/tribes/group/interceptors/TestTcpFailureDetector.java b/test/org/apache/catalina/tribes/group/interceptors/TestTcpFailureDetector.java
index 7c8a87c699..fdcd7d6345 100644
--- a/test/org/apache/catalina/tribes/group/interceptors/TestTcpFailureDetector.java
+++ b/test/org/apache/catalina/tribes/group/interceptors/TestTcpFailureDetector.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import org.junit.After;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
@@ -43,6 +44,8 @@ public class TestTcpFailureDetector {
@Before
public void setUp() throws Exception {
+ Assume.assumeTrue("Skipping test - IP multicast is not available in this environment",
+ TesterUtil.isMulticastAvailable());
channel1 = new GroupChannel();
channel2 = new GroupChannel();
((ReceiverBase) channel1.getChannelReceiver()).setHost("localhost");
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.