Search code examples
jmsspring-jmsactivemq-artemis

How can I use the ActiveMQ Artemis management API to apply changes to multiple brokers?


I have a HA replicated cluster setup of ActiveMQ Artemis currently and would like to use the management API from my clients to run a few configuration operations when the brokers start up.

I can't figure a decent way to make sure these configurations apply to every broker in the cluster.

I've thought of just creating a connection to every broker directly, but that didn't seem like a great choice. I also had the idea of just using the JMS ExceptionListener to pick up whenever a failover occurs and re-running the management operations following that. I like this idea the most so far, but I'm struggling to implement it as the ActiveMQConnectionFactory doesn't have any option for me to apply an event listener.

Wondering if anyone else has ran into something similar and how they might have approached it.

I am using ActiveMQ Artemis 2.39.0 and Spring.

Here is the ConnectionFactory itself:

@Bean
public ActiveMQConnectionFactory activeMQConnectionFactory() throws NamingException{
    ArtemisTargetConfig config = (ArtemisTargetConfig) new InitialContext().lookup("java:comp/env/bean/ArtemisConfig");

    UDPBroadcastEndpointFactory udpCfg = new UDPBroadcastEndpointFactory();
    udpCfg.setGroupAddress(config.getGroupAddress()).setGroupPort(config.getGroupPort());
    DiscoveryGroupConfiguration groupConfiguration = new DiscoveryGroupConfiguration();
    groupConfiguration.setBroadcastEndpointFactory(udpCfg);

    ActiveMQConnectionFactory cf = ActiveMQJMSClient.createConnectionFactoryWithHA(groupConfiguration, JMSFactoryType.CF);
    cf.setUser(config.getUserName());
    cf.setPassword(EncryptionUtils.decryptWebappConfig(config.getPassword()));

    return cf;
}

Here is a mock up of what I have happening on app startup, and what I would like to happen to every broker.

@EnableJms
@Configuration
public class JmsConfig {

private static final Logger LOG = LoggerFactory.getLogger(JmsConfig.class);
private final ActiveMQConnectionFactory activeMQConnectionFactory;

@Bean
public void initializeSettings()
        throws JMSException {

    try (QueueConnection connection = activeMQConnectionFactory.createQueueConnection();
            QueueSession session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE)) {
        
        Queue managementQueue = session.createQueue("activemq.management");
        QueueRequestor requestor = new QueueRequestor(session, managementQueue);

        connection.start();

        // Change address settings.
        Message addressMessage = session.createMessage();
        json = """
                {
                    ABunchOfSettings: Settings
                }
                """;

        JMSManagementHelper.putOperationInvocation(addressMessage, ResourceNames.BROKER, "addAddressSettings", "address#", json);
        reply = requestor.request(addressMessage);

        if (JMSManagementHelper.hasOperationSucceeded(reply)) {
            LOG.info("Address settings applied");
        }
    }
}

Solution

  • There is no automated way to push configuration changes across every node in a cluster. However, many changes made via the management API are persisted to the journal (e.g. creating/deleting an address, queue, divert, core bridge, address-setting, etc.) and therefore will be reflected on the backup when it activates.

    If you really want to change the configuration for every node in a cluster then you'll need to connect to each node and change them one by one.

    That said, something as important as broker configuration should likely not be in the hands of applications. There are lots of back-end ways of managing configuration files for server resources. Doing this from a client application seems backwards to me.

    The underlying implementation of the JMS Connection has a way to listen for fail-over events. See org.apache.activemq.artemis.jms.client.ActiveMQConnection#setFailoverListener. However, I don't see how this will help you with configuring all the nodes in the cluster since it will only alert you when you fail-over from a primary to a backup.

    Lastly, you can simplify the method which creates your ConnectionFactory, e.g.:

    @Bean
    public ActiveMQConnectionFactory activeMQConnectionFactory() throws NamingException{
        ArtemisTargetConfig config = (ArtemisTargetConfig) new InitialContext().lookup("java:comp/env/bean/ArtemisConfig");
        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("udp://" + config.getGroupAddress() + ":" + config.getGroupPort() + "?ha=true");
        cf.setUser(config.getUserName());
        cf.setPassword(EncryptionUtils.decryptWebappConfig(config.getPassword()));
        return cf;
    }