Fix JMX initialization problem in injvm-dtest framework

Patch by Francisco Guerrerro, Doug Rohrer; reviewed by TBD for CASSANDRA-20539

Co-authored-by: Doug Rohrer <jeetkundoug@apache.org>
This commit is contained in:
Francisco Guerrero 2025-04-08 14:19:56 -07:00
parent 8404d2fd5c
commit d3658f0491
3 changed files with 40 additions and 4 deletions

View File

@ -645,9 +645,14 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
initialized = true;
}
private synchronized void startJmx()
private synchronized void setupMbeanWrapper()
{
this.isolatedJmx = new IsolatedJmx(this, inInstancelogger);
this.isolatedJmx.setupMBeanWrapper();
}
private synchronized void startJmx()
{
isolatedJmx.startJmx();
}
@ -707,6 +712,8 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
assert config.networkTopology().contains(config.broadcastAddress()) : String.format("Network topology %s doesn't contain the address %s",
config.networkTopology(), config.broadcastAddress());
DistributedTestInitialLocationProvider.assign(config.networkTopology());
if (config.has(JMX))
setupMbeanWrapper();
DatabaseDescriptor.daemonInitialization();
if (config.has(JMX))
startJmx();

View File

@ -24,6 +24,7 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorServer;
@ -76,6 +77,8 @@ public class IsolatedJmx
{
try
{
Objects.requireNonNull(wrapper, "Must call setupMBeanWrapper before use");
// Several RMI threads hold references to in-jvm dtest objects, and are, by default, kept
// alive for long enough (minutes) to keep classloaders from being collected.
// Set these two system properties to a low value to allow cleanup to occur fast enough
@ -88,8 +91,6 @@ public class IsolatedJmx
int jmxPort = config.jmxPort();
String hostname = addr.getHostAddress();
wrapper = new MBeanWrapper.InstanceMBeanWrapper(hostname + ":" + jmxPort);
((MBeanWrapper.DelegatingMbeanWrapper) MBeanWrapper.instance).setDelegate(wrapper);
// CASSANDRA-18508: Sensitive JMX SSL configuration options can be easily exposed
Map<String, Object> jmxServerOptionsMap = (Map<String, Object>) config.getParams().get("jmx_server_options");
@ -158,6 +159,15 @@ public class IsolatedJmx
}
}
public void setupMBeanWrapper()
{
InetAddress addr = config.broadcastAddress().getAddress();
int jmxPort = config.jmxPort();
String hostname = addr.getHostAddress();
wrapper = new MBeanWrapper.InstanceMBeanWrapper(hostname + ':' + jmxPort);
((MBeanWrapper.DelegatingMbeanWrapper) MBeanWrapper.instance).setDelegate(wrapper);
}
/**
* Builds {@code EncryptionOptions} from the map based SSL configuration properties.
*

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.distributed.test.jmx;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -66,6 +67,15 @@ public class JMXTestsUtil
"org.apache.cassandra.db:type=CIDRGroupsMappingManager:loadCidrGroupsCache", // AllowAllCIDRAuthorizer doesn't support this operation, as feature is disabled by default
"org.apache.cassandra.db:type=StorageService:forceRemoveCompletion" // deprecated (TCM)
);
// This set of mbeans are registered early enough during the startup of a
// Cassandra instance for in-jvm dtests to avoid missing registration of mbeans.
// We ignore both "org.apache.cassandra.diag:type=DiagnosticEventService" and
// "org.apache.cassandra.diag:type=LastEventIdBroadcaster" because they are being intialized
// outside the scope of the in-jvm Instance initialization.
private static final Set<String> EXPECTED_MBEANS_TO_BE_REGISTERED = Set.of(
"org.apache.cassandra.db:type=EndpointSnitchInfo",
"org.apache.cassandra.db:type=LocationInfo"
);
/**
* Tests JMX getters and operations and allows passing JMX Env used for the client JMX connection.
@ -75,6 +85,7 @@ public class JMXTestsUtil
*/
public static void testAllValidGetters(Cluster cluster, Map<String, ?> jmxEnv) throws Exception
{
Set<String> missingExpectedMbeans = new HashSet<>(EXPECTED_MBEANS_TO_BE_REGISTERED);
for (IInvokableInstance instance : cluster)
{
if (instance.isShutdown())
@ -91,6 +102,7 @@ public class JMXTestsUtil
{
if (!name.getDomain().startsWith("org.apache.cassandra"))
continue;
missingExpectedMbeans.remove(name.getCanonicalName());
MBeanInfo info = mbsc.getMBeanInfo(name);
for (MBeanAttributeInfo a : info.getAttributes())
{
@ -123,7 +135,7 @@ public class JMXTestsUtil
}
}
}
if (!errors.isEmpty())
if (!errors.isEmpty() || !missingExpectedMbeans.isEmpty())
{
AssertionError root = new AssertionError();
for (Named error : errors)
@ -132,6 +144,13 @@ public class JMXTestsUtil
logger.error("Error {}", error.getMessage());
root.addSuppressed(error);
}
for (String missingMbean : missingExpectedMbeans)
{
// The Named object's message has the cause also so this only logs the message
String errorMessage = String.format("Expected mbean %s was not found", missingMbean);
logger.error(errorMessage);
root.addSuppressed(new RuntimeException(errorMessage));
}
throw root;
}
}