This is an automated email from the ASF dual-hosted git repository.
csutherl pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new 28efaf2b24 Skip tribes membership tests when IP multicast is
unavailable (#1052)
28efaf2b24 is described below
commit 28efaf2b24b51b300e04e041bd7901fedb326953
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 6163361579..58f9af09be 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 075ce5452b..4def88c6ec 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 66d74b1ce0..35398458a2 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");
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]