This is an automated email from the ASF dual-hosted git repository.

mattrpav pushed a commit to branch activemq-6.2.x
in repository https://gitbox.apache.org/repos/asf/activemq.git


The following commit(s) were added to refs/heads/activemq-6.2.x by this push:
     new 05242aab21 [AMQ-9687] Add metrics about error and reconnect counts to 
network connector and network bridges (#2239)
05242aab21 is described below

commit 05242aab216ea2e91dd8190d38e588cbdec8112d
Author: Matt Pavlovich <[email protected]>
AuthorDate: Thu Jul 23 15:01:12 2026 -0500

    [AMQ-9687] Add metrics about error and reconnect counts to network 
connector and network bridges (#2239)
    
    - NetworkConnector statistics now parent the bridge statistics
     - Use a CAS AtomicBoolean to ensure only one of local exception or remote 
exception is counted in a bridge
     - Bridge statistics guard against shutdown leading to miscounts
     - More tests to affirm local and remote counted separately
    
    (cherry picked from commit cea5c0917beea41b934d9cbd5df773097a80fda3)
---
 .../activemq/broker/jmx/NetworkBridgeView.java     | 15 ++++
 .../broker/jmx/NetworkBridgeViewMBean.java         |  5 ++
 .../activemq/broker/jmx/NetworkConnectorView.java  | 25 ++++++
 .../broker/jmx/NetworkConnectorViewMBean.java      | 10 +++
 .../network/DemandForwardingBridgeSupport.java     | 50 +++++++++++-
 .../network/DiscoveryNetworkConnector.java         |  3 +
 .../org/apache/activemq/network/NetworkBridge.java |  6 ++
 .../activemq/network/NetworkBridgeStatistics.java  | 38 ++++++++-
 .../apache/activemq/network/NetworkConnector.java  | 55 +++++++++++++
 .../java/org/apache/activemq/bugs/AMQ4160Test.java | 15 ++++
 .../network/NetworkAdvancedStatisticsTest.java     | 90 +++++++++++++++++++++-
 .../localBroker-advancedNetworkStatistics.xml      | 31 +++++++-
 .../remoteBroker-advancedNetworkStatistics.xml     | 27 ++++++-
 13 files changed, 358 insertions(+), 12 deletions(-)

diff --git 
a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkBridgeView.java
 
b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkBridgeView.java
index 756ffbed7d..6b2798cf6c 100644
--- 
a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkBridgeView.java
+++ 
b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkBridgeView.java
@@ -99,6 +99,21 @@ public class NetworkBridgeView implements 
NetworkBridgeViewMBean {
         }
     }
 
+    @Override
+    public long getStartedTimestamp() {
+        return bridge.getStartedTimestamp();
+    }
+
+    @Override
+    public long getLocalExceptionCount() {
+        return bridge.getLocalExceptionCount();
+    }
+
+    @Override
+    public long getRemoteExceptionCount() {
+        return bridge.getRemoteExceptionCount();
+    }
+
     public void addNetworkDestinationView(NetworkDestinationView 
networkDestinationView){
         networkDestinationViewList.add(networkDestinationView);
     }
diff --git 
a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkBridgeViewMBean.java
 
b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkBridgeViewMBean.java
index 82fc9ca189..34d7d4b198 100644
--- 
a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkBridgeViewMBean.java
+++ 
b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkBridgeViewMBean.java
@@ -40,4 +40,9 @@ public interface NetworkBridgeViewMBean extends Service {
 
     void resetStats();
 
+    long getStartedTimestamp();
+
+    long getLocalExceptionCount();
+
+    long getRemoteExceptionCount();
 }
diff --git 
a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkConnectorView.java
 
b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkConnectorView.java
index 05e6747dd7..09e7da0755 100644
--- 
a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkConnectorView.java
+++ 
b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkConnectorView.java
@@ -203,4 +203,29 @@ public class NetworkConnectorView implements 
NetworkConnectorViewMBean {
     public boolean isAutoStart() {
         return connector.isAutoStart();
     }
+
+    @Override
+    public long getStartedTimestamp() {
+        return connector.getStartedTimestamp();
+    }
+
+    @Override
+    public long getStoppedTimestamp() {
+        return connector.getStoppedTimestamp();
+    }
+
+    @Override
+    public long getBridgeExceptionCount() {
+        return connector.getBridgeExceptionCounter().getCount();
+    }
+
+    @Override
+    public long getLocalExceptionCount() {
+        return connector.getLocalExceptionCounter().getCount();
+    }
+
+    @Override
+    public long getRemoteExceptionCount() {
+        return connector.getRemoteExceptionCounter().getCount();
+    }
 }
diff --git 
a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkConnectorViewMBean.java
 
b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkConnectorViewMBean.java
index e51b9a16ca..47a8da6c07 100644
--- 
a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkConnectorViewMBean.java
+++ 
b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/NetworkConnectorViewMBean.java
@@ -90,4 +90,14 @@ public interface NetworkConnectorViewMBean extends Service {
     void setRemotePassword(String remotePassword);
 
     boolean isAutoStart();
+
+    long getStartedTimestamp();
+
+    long getStoppedTimestamp();
+
+    long getBridgeExceptionCount();
+
+    long getLocalExceptionCount();
+
+    long getRemoteExceptionCount();
 }
diff --git 
a/activemq-broker/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java
 
b/activemq-broker/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java
index 762c6eeffd..c8bb1386c5 100644
--- 
a/activemq-broker/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java
+++ 
b/activemq-broker/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java
@@ -37,6 +37,7 @@ import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.regex.Pattern;
 
 import javax.management.ObjectName;
@@ -129,6 +130,8 @@ public abstract class DemandForwardingBridgeSupport 
implements NetworkBridge, Br
     protected final AtomicBoolean localBridgeStarted = new 
AtomicBoolean(false);
     protected final AtomicBoolean remoteBridgeStarted = new 
AtomicBoolean(false);
     protected final AtomicBoolean bridgeFailed = new AtomicBoolean();
+    // Ensures a single bridge failure counts exactly one local/remote 
exception
+    protected final AtomicBoolean bridgeExceptionCounted = new AtomicBoolean();
     protected final AtomicBoolean disposed = new AtomicBoolean();
     protected BrokerId localBrokerId;
     protected ActiveMQDestination[] excludedDestinations;
@@ -171,6 +174,7 @@ public abstract class DemandForwardingBridgeSupport 
implements NetworkBridge, Br
     private final ExecutorService syncExecutor = 
Executors.newSingleThreadExecutor();
     private Transport duplexInboundLocalBroker = null;
     private ProducerInfo duplexInboundLocalProducerInfo;
+    private final AtomicLong startedTimestamp = new AtomicLong(0L);
 
     public DemandForwardingBridgeSupport(NetworkBridgeConfiguration 
configuration, Transport localBroker, Transport remoteBroker) {
         this.configuration = configuration;
@@ -194,7 +198,15 @@ public abstract class DemandForwardingBridgeSupport 
implements NetworkBridge, Br
                 throw new IllegalArgumentException("BrokerService is null on " 
+ this);
             }
 
-            
networkBridgeStatistics.setEnabled(brokerService.isEnableStatistics());
+            if (brokerService.isEnableStatistics()) {
+                networkBridgeStatistics.setEnabled(true);
+                // Bridges are ephemeral - parent this bridge's statistics to 
the owning
+                // connector so its counts (including local/remote exceptions) 
roll up
+                // automatically and survive the bridge being torn down and 
recreated.
+                if (configuration instanceof NetworkConnector) {
+                    networkBridgeStatistics.setParent(((NetworkConnector) 
configuration).getNetworkBridgeStatistics());
+                }
+            }
 
             if (isDuplex()) {
                 duplexInboundLocalBroker = 
NetworkBridgeFactory.createLocalAsyncTransport(brokerService.getBroker().getVmConnectorURI());
@@ -260,6 +272,8 @@ public abstract class DemandForwardingBridgeSupport 
implements NetworkBridge, Br
                     triggerStartAsyncNetworkBridgeCreation();
                 } catch (IOException e) {
                     LOG.warn("Caught exception from remote start", e);
+                } finally {
+                    startedTimestamp.set(System.currentTimeMillis());
                 }
             } else {
                 LOG.warn("Bridge was disposed before the start() method was 
fully executed.");
@@ -336,6 +350,7 @@ public abstract class DemandForwardingBridgeSupport 
implements NetworkBridge, Br
                     startedLatch.countDown();
                     localStartedLatch.countDown();
                     staticDestinationsLatch.countDown();
+                    startedTimestamp.set(0L);
 
                     ss.throwFirstException();
                 }
@@ -650,12 +665,21 @@ public abstract class DemandForwardingBridgeSupport 
implements NetworkBridge, Br
     @Override
     public void serviceRemoteException(Throwable error) {
         if (!disposed.get()) {
+            // Count the single genuine failure that trips disposal, not the 
peer exceptions
+            // fired during teardown nor a second callback racing in before 
disposal completes.
+            // This rolls up to the owning connector automatically via the 
parent statistics
+            // wired in start().
+            if (bridgeExceptionCounted.compareAndSet(false, true)) {
+                networkBridgeStatistics.getRemoteExceptionCount().increment();
+            }
+
             if (error instanceof SecurityException || error instanceof 
GeneralSecurityException) {
                 LOG.error("Network connection between {} and {} shutdown due 
to a remote error: {}", localBroker, remoteBroker, error.toString());
             } else {
                 LOG.warn("Network connection between {} and {} shutdown due to 
a remote error: {}", localBroker, remoteBroker, error.toString());
             }
             LOG.debug("The remote Exception was: {}", error, error);
+
             brokerService.getTaskRunnerFactory().execute(new Runnable() {
                 @Override
                 public void run() {
@@ -1114,6 +1138,7 @@ public abstract class DemandForwardingBridgeSupport 
implements NetworkBridge, Br
 
     public void serviceLocalException(MessageDispatch messageDispatch, 
Throwable error) {
         LOG.trace("serviceLocalException: disposed {} ex", disposed.get(), 
error);
+
         if (!disposed.get()) {
             if (error instanceof DestinationDoesNotExistException && 
((DestinationDoesNotExistException) error).isTemporary()) {
                 // not a reason to terminate the bridge - temps can disappear 
with
@@ -1134,6 +1159,14 @@ public abstract class DemandForwardingBridgeSupport 
implements NetworkBridge, Br
                 return;
             }
 
+            // Count the single genuine failure that trips disposal, not the 
peer exceptions
+            // fired during teardown, a second callback racing in before 
disposal completes,
+            // nor the ignorable temporary-destination errors handled above. 
This rolls up to
+            // the owning connector automatically via the parent statistics 
wired in start().
+            if (bridgeExceptionCounted.compareAndSet(false, true)) {
+                networkBridgeStatistics.getLocalExceptionCount().increment();
+            }
+
             LOG.info("Network connection between {} and {} shutdown due to a 
local error: {}", localBroker, remoteBroker, error);
             LOG.debug("The local Exception was: {}", error, error);
 
@@ -1920,6 +1953,21 @@ public abstract class DemandForwardingBridgeSupport 
implements NetworkBridge, Br
         return networkBridgeStatistics.getEnqueues().getCount();
     }
 
+    @Override
+    public long getStartedTimestamp() {
+        return startedTimestamp.get();
+    }
+
+    @Override
+    public long getLocalExceptionCount() {
+        return networkBridgeStatistics.getLocalExceptionCount().getCount();
+    }
+
+    @Override
+    public long getRemoteExceptionCount() {
+        return networkBridgeStatistics.getRemoteExceptionCount().getCount();
+    }
+
     @Override
     public NetworkBridgeStatistics getNetworkBridgeStatistics() {
         return networkBridgeStatistics;
diff --git 
a/activemq-broker/src/main/java/org/apache/activemq/network/DiscoveryNetworkConnector.java
 
b/activemq-broker/src/main/java/org/apache/activemq/network/DiscoveryNetworkConnector.java
index 3b9696cf92..5532f3f98a 100644
--- 
a/activemq-broker/src/main/java/org/apache/activemq/network/DiscoveryNetworkConnector.java
+++ 
b/activemq-broker/src/main/java/org/apache/activemq/network/DiscoveryNetworkConnector.java
@@ -131,6 +131,7 @@ public class DiscoveryNetworkConnector extends 
NetworkConnector implements Disco
                 try {
                     remoteTransport = TransportFactory.connect(connectUri);
                 } catch (Exception e) {
+                    
networkBridgeStatistics.getRemoteExceptionCount().increment();
                     LOG.warn("Could not connect to remote URI: {}: {}", 
connectUri, e.getMessage());
                     LOG.debug("Connection failure exception: ", e);
                     try {
@@ -143,6 +144,7 @@ public class DiscoveryNetworkConnector extends 
NetworkConnector implements Disco
                 try {
                     localTransport = createLocalTransport();
                 } catch (Exception e) {
+                    
networkBridgeStatistics.getLocalExceptionCount().increment();
                     ServiceSupport.dispose(remoteTransport);
                     LOG.warn("Could not connect to local URI: {}: {}", 
localURI, e.getMessage());
                     LOG.debug("Connection failure exception: ", e);
@@ -164,6 +166,7 @@ public class DiscoveryNetworkConnector extends 
NetworkConnector implements Disco
                 }
                 bridge.start();
             } catch (Exception e) {
+                bridgeExceptionCounter.increment();
                 ServiceSupport.dispose(localTransport);
                 ServiceSupport.dispose(remoteTransport);
                 LOG.warn("Could not start network bridge between: {} and: {} 
due to: {}", localURI, uri, e.getMessage());
diff --git 
a/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridge.java 
b/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridge.java
index fb9e3d9f08..42d0cd38f8 100644
--- 
a/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridge.java
+++ 
b/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridge.java
@@ -95,4 +95,10 @@ public interface NetworkBridge extends Service {
     ObjectName getMbeanObjectName();
 
     void resetStats();
+
+    long getStartedTimestamp();
+
+    long getLocalExceptionCount();
+
+    long getRemoteExceptionCount();
 }
diff --git 
a/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridgeStatistics.java
 
b/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridgeStatistics.java
index 50c3951279..8b52db8194 100644
--- 
a/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridgeStatistics.java
+++ 
b/activemq-broker/src/main/java/org/apache/activemq/network/NetworkBridgeStatistics.java
@@ -17,6 +17,8 @@
 
 package org.apache.activemq.network;
 
+import java.util.Set;
+
 import org.apache.activemq.management.CountStatisticImpl;
 import org.apache.activemq.management.StatsImpl;
 
@@ -28,15 +30,17 @@ public class NetworkBridgeStatistics extends StatsImpl {
     protected CountStatisticImpl enqueues;
     protected CountStatisticImpl dequeues;
     protected CountStatisticImpl receivedCount;
+    protected CountStatisticImpl localExceptionCount;
+    protected CountStatisticImpl remoteExceptionCount;
 
     public NetworkBridgeStatistics() {
         enqueues = new CountStatisticImpl("enqueues", "The current number of 
enqueues this bridge has, which is the number of potential messages to be 
forwarded.");
         dequeues = new CountStatisticImpl("dequeues", "The current number of 
dequeues this bridge has, which is the number of messages received by the 
remote broker.");
         receivedCount = new CountStatisticImpl("receivedCount", "The number of 
messages that have been received by the NetworkBridge from the remote broker.  
Only applies for Duplex bridges.");
+        localExceptionCount = new CountStatisticImpl("localExceptionCount", 
"The number of exceptions that have been received by the NetworkBridge from the 
local broker.");
+        remoteExceptionCount = new CountStatisticImpl("remoteExceptionCount", 
"The number of exceptions that have been received by the NetworkBridge from the 
remote broker.");
 
-        addStatistic("enqueues", enqueues);
-        addStatistic("dequeues", dequeues);
-        addStatistic("receivedCount", receivedCount);
+        addStatistics(Set.of(enqueues, dequeues, receivedCount, 
localExceptionCount, remoteExceptionCount));
     }
 
     /**
@@ -69,6 +73,26 @@ public class NetworkBridgeStatistics extends StatsImpl {
         return receivedCount;
     }
 
+    /**
+     * The current number of exceptions this bridge has, which is the number of
+     * exceptions received from the remote broker.
+     *
+     * @return
+     */
+    public CountStatisticImpl getLocalExceptionCount() {
+        return localExceptionCount;
+    }
+
+    /**
+     * The current number of exceptions this bridge has, which is the number of
+     * exceptions received from the remote broker.
+     *
+     * @return
+     */
+    public CountStatisticImpl getRemoteExceptionCount() {
+        return remoteExceptionCount;
+    }
+
     @Override
     public void reset() {
         if (this.isDoReset()) {
@@ -76,6 +100,8 @@ public class NetworkBridgeStatistics extends StatsImpl {
             enqueues.reset();
             dequeues.reset();
             receivedCount.reset();
+            localExceptionCount.reset();
+            remoteExceptionCount.reset();
         }
     }
 
@@ -85,6 +111,8 @@ public class NetworkBridgeStatistics extends StatsImpl {
         enqueues.setEnabled(enabled);
         dequeues.setEnabled(enabled);
         receivedCount.setEnabled(enabled);
+        localExceptionCount.setEnabled(enabled);
+        remoteExceptionCount.setEnabled(enabled);
     }
 
     public void setParent(NetworkBridgeStatistics parent) {
@@ -92,10 +120,14 @@ public class NetworkBridgeStatistics extends StatsImpl {
             enqueues.setParent(parent.enqueues);
             dequeues.setParent(parent.dequeues);
             receivedCount.setParent(parent.receivedCount);
+            localExceptionCount.setParent(parent.localExceptionCount);
+            remoteExceptionCount.setParent(parent.remoteExceptionCount);
         } else {
             enqueues.setParent(null);
             dequeues.setParent(null);
             receivedCount.setParent(null);
+            localExceptionCount.setParent(null);
+            remoteExceptionCount.setParent(null);
         }
     }
 
diff --git 
a/activemq-broker/src/main/java/org/apache/activemq/network/NetworkConnector.java
 
b/activemq-broker/src/main/java/org/apache/activemq/network/NetworkConnector.java
index 1bbc483d5c..b08e03bab1 100644
--- 
a/activemq-broker/src/main/java/org/apache/activemq/network/NetworkConnector.java
+++ 
b/activemq-broker/src/main/java/org/apache/activemq/network/NetworkConnector.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
 
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
@@ -36,6 +37,8 @@ import org.apache.activemq.broker.jmx.NetworkBridgeView;
 import org.apache.activemq.broker.jmx.NetworkBridgeViewMBean;
 import org.apache.activemq.command.ActiveMQDestination;
 import org.apache.activemq.command.ConsumerId;
+import org.apache.activemq.management.CountStatistic;
+import org.apache.activemq.management.CountStatisticImpl;
 import org.apache.activemq.transport.Transport;
 import org.apache.activemq.util.ServiceStopper;
 import org.apache.activemq.util.ServiceSupport;
@@ -51,6 +54,13 @@ public abstract class NetworkConnector extends 
NetworkBridgeConfiguration implem
     protected URI localURI;
     protected ConnectionFilter connectionFilter;
     protected ConcurrentMap<URI, NetworkBridge> bridges = new 
ConcurrentHashMap<URI, NetworkBridge>();
+    protected final AtomicLong startedTimestamp = new AtomicLong(0L);
+    protected final AtomicLong stoppedTimestamp = new AtomicLong(0L);
+    protected final CountStatisticImpl bridgeExceptionCounter = new 
CountStatisticImpl("bridgeExceptionCount", "Count of exceptions when 
establishing network bridge.");
+    // Aggregates the local/remote exception counts (and message flow) of 
every bridge this
+    // connector creates. Each bridge sets this as the parent of its own 
statistics, so the
+    // counts roll up automatically and survive the ephemeral bridges being 
recreated.
+    protected final NetworkBridgeStatistics networkBridgeStatistics = new 
NetworkBridgeStatistics();
 
     protected ServiceSupport serviceSupport = new ServiceSupport() {
 
@@ -162,11 +172,15 @@ public abstract class NetworkConnector extends 
NetworkBridgeConfiguration implem
     @Override
     public void start() throws Exception {
         serviceSupport.start();
+        startedTimestamp.set(System.currentTimeMillis());
+        stoppedTimestamp.set(0L);
     }
 
     @Override
     public void stop() throws Exception {
         serviceSupport.stop();
+        stoppedTimestamp.set(System.currentTimeMillis());
+        startedTimestamp.set(0L);
     }
 
     protected void handleStart() throws Exception {
@@ -174,10 +188,22 @@ public abstract class NetworkConnector extends 
NetworkBridgeConfiguration implem
             throw new IllegalStateException("You must configure the 'localURI' 
property");
         }
         LOG.info("Network Connector {} started", this);
+
+        if (brokerService != null && brokerService.isEnableStatistics()) {
+            bridgeExceptionCounter.setEnabled(true);
+            bridgeExceptionCounter.setCount(0L);
+            networkBridgeStatistics.setEnabled(true);
+            networkBridgeStatistics.reset();
+        }
     }
 
     protected void handleStop(ServiceStopper stopper) throws Exception {
         LOG.info("Network Connector {} stopped", this);
+
+        if (brokerService != null && brokerService.isEnableStatistics()) {
+            bridgeExceptionCounter.reset();
+            networkBridgeStatistics.reset();
+        }
     }
 
     public boolean isStarted() {
@@ -255,4 +281,33 @@ public abstract class NetworkConnector extends 
NetworkBridgeConfiguration implem
     public Collection<NetworkBridge> activeBridges() {
         return bridges.values();
     }
+
+    public long getStartedTimestamp() {
+        return startedTimestamp.get();
+    }
+
+    public long getStoppedTimestamp() {
+        return stoppedTimestamp.get();
+    }
+
+    public CountStatistic getBridgeExceptionCounter() {
+        return bridgeExceptionCounter;
+    }
+
+    public CountStatistic getLocalExceptionCounter() {
+        return networkBridgeStatistics.getLocalExceptionCount();
+    }
+
+    public CountStatistic getRemoteExceptionCounter() {
+        return networkBridgeStatistics.getRemoteExceptionCount();
+    }
+
+    /**
+     * @return the connector-level statistics that aggregate every bridge's 
statistics.
+     * A bridge sets this as the parent of its own {@link 
NetworkBridgeStatistics} so the
+     * counts roll up automatically.
+     */
+    public NetworkBridgeStatistics getNetworkBridgeStatistics() {
+        return networkBridgeStatistics;
+    }
 }
diff --git 
a/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/AMQ4160Test.java 
b/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/AMQ4160Test.java
index 5563ded869..7ecfc8564b 100644
--- 
a/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/AMQ4160Test.java
+++ 
b/activemq-unit-tests/src/test/java/org/apache/activemq/bugs/AMQ4160Test.java
@@ -329,6 +329,21 @@ public class AMQ4160Test extends 
JmsMultipleBrokersTestSupport {
                         return next.getDequeueCounter();
                     }
 
+                    @Override
+                    public long getLocalExceptionCount() {
+                        return next.getLocalExceptionCount();
+                    }
+
+                    @Override
+                    public long getRemoteExceptionCount() {
+                        return next.getRemoteExceptionCount();
+                    }
+
+                    @Override
+                    public long getStartedTimestamp() {
+                        return next.getStartedTimestamp();
+                    }
+
                     @Override
                     public NetworkBridgeStatistics 
getNetworkBridgeStatistics() {
                         return next.getNetworkBridgeStatistics();
diff --git 
a/activemq-unit-tests/src/test/java/org/apache/activemq/network/NetworkAdvancedStatisticsTest.java
 
b/activemq-unit-tests/src/test/java/org/apache/activemq/network/NetworkAdvancedStatisticsTest.java
index e18a66c3b0..8df2f0ee10 100644
--- 
a/activemq-unit-tests/src/test/java/org/apache/activemq/network/NetworkAdvancedStatisticsTest.java
+++ 
b/activemq-unit-tests/src/test/java/org/apache/activemq/network/NetworkAdvancedStatisticsTest.java
@@ -17,6 +17,7 @@
 package org.apache.activemq.network;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -40,6 +41,7 @@ import org.apache.activemq.command.ActiveMQDestination;
 import org.apache.activemq.command.ActiveMQQueue;
 import org.apache.activemq.command.ActiveMQTopic;
 import org.apache.activemq.management.MessageFlowStats;
+import org.apache.activemq.transport.discovery.simple.SimpleDiscoveryAgent;
 import org.apache.activemq.util.Wait;
 import org.apache.activemq.util.Wait.Condition;
 import org.junit.Test;
@@ -87,12 +89,12 @@ public class NetworkAdvancedStatisticsTest extends 
BaseNetworkTest {
         ActiveMQConnectionFactory fac = new 
ActiveMQConnectionFactory(localURI);
         fac.setAlwaysSyncSend(true);
         fac.setDispatchAsync(false);
-        localConnection = fac.createConnection();
+        localConnection = fac.createConnection("localAdmin", "passwordA");
         localConnection.setClientID("localClientId");
 
         URI remoteURI = remoteBroker.getVmConnectorURI();
         fac = new ActiveMQConnectionFactory(remoteURI);
-        remoteConnection = fac.createConnection();
+        remoteConnection = fac.createConnection("remoteAdmin", "passwordB");
         remoteConnection.setClientID("remoteClientId");
 
         localSession = localConnection.createSession(false, 
Session.AUTO_ACKNOWLEDGE);
@@ -109,6 +111,57 @@ public class NetworkAdvancedStatisticsTest extends 
BaseNetworkTest {
         return 
"org/apache/activemq/network/localBroker-advancedNetworkStatistics.xml";
     }
 
+    //Added for AMQ-9687 test network bridge local/remote exception stats 
rolled up to the connector
+    @Test(timeout = 120 * 1000)
+    public void testNetworkAdvancedStatisticsErrors() throws Exception {
+        final NetworkConnector localNetworkConnector = 
localBroker.getNetworkConnectorByName("local-to-remote");
+        assertNotNull(localNetworkConnector);
+
+        final String originalLocalPassword = 
localNetworkConnector.getPassword();
+        final String originalRemotePassword = 
localNetworkConnector.getRemotePassword();
+
+        assertTrue(localNetworkConnector.isAutoStart());
+        assertTrue(localNetworkConnector.isStarted());
+
+        // Use a non-retrying discovery agent so each induced fault produces a 
single,
+        // definitive exception count instead of an ever-growing series of 
reconnects.
+        ((SimpleDiscoveryAgent) ((DiscoveryNetworkConnector) 
localNetworkConnector).getDiscoveryAgent())
+                .setMaxReconnectAttempts(1);
+
+        // Healthy bridge: no exceptions rolled up to the connector.
+        assertNetworkStats(false, false, 0L, 0L, localNetworkConnector);
+
+        try {
+            // Remote error only: a bad remote password is rejected by the 
remote broker and
+            // surfaces as a remote exception on the bridge, rolled up to the 
connector.
+            restartConnector(localNetworkConnector, originalLocalPassword, 
"wrongRemotePassword");
+            assertNetworkStats(false, false, 0L, 1L, localNetworkConnector);
+
+            // Local error only: a bad local password is rejected by the local 
broker and
+            // surfaces as a local exception on the bridge, rolled up to the 
connector.
+            restartConnector(localNetworkConnector, "wrongLocalPassword", 
originalRemotePassword);
+            assertNetworkStats(false, false, 1L, 0L, localNetworkConnector);
+        } finally {
+            restartConnector(localNetworkConnector, originalLocalPassword, 
originalRemotePassword);
+        }
+    }
+
+    /**
+     * Stops the connector, applies the given local/remote credentials, and 
starts it again,
+     * waiting for each transition to complete. A stop/start cycle resets the 
connector's
+     * exception counters, so each scenario starts from a clean baseline.
+     */
+    private static void restartConnector(final NetworkConnector 
networkConnector, final String localPassword, final String remotePassword) 
throws Exception {
+        networkConnector.stop();
+        assertTrue(Wait.waitFor(networkConnector::isStopped));
+
+        networkConnector.setPassword(localPassword);
+        networkConnector.setRemotePassword(remotePassword);
+
+        networkConnector.start();
+        assertTrue(Wait.waitFor(networkConnector::isStarted));
+    }
+
     //Added for AMQ-9437 test advancedStatistics for networkEnqueue and 
networkDequeue
     @Test(timeout = 60 * 1000)
     public void testNetworkAdvancedStatistics() throws Exception {
@@ -191,7 +244,7 @@ public class NetworkAdvancedStatisticsTest extends 
BaseNetworkTest {
         assertEquals(lastIncludedSentMessageID, 
localBrokerIncludedMessageFlowStats.getDequeuedMessageID().getValue());
         
assertNotNull(localBrokerIncludedMessageFlowStats.getDequeuedMessageBrokerInTime().getValue());
         
assertNotNull(localBrokerIncludedMessageFlowStats.getDequeuedMessageBrokerOutTime().getValue());
-        
assertTrue(localBrokerIncludedMessageFlowStats.getDequeuedMessageClientID().getValue().startsWith("networkConnector"));
+        
assertTrue(localBrokerIncludedMessageFlowStats.getDequeuedMessageClientID().getValue().startsWith("local-to-remote"));
         
assertNotNull(localBrokerIncludedMessageFlowStats.getDequeuedMessageTimestamp().getValue());
 
         if(includedDestination.isTopic() && !durable) {
@@ -265,4 +318,35 @@ public class NetworkAdvancedStatisticsTest extends 
BaseNetworkTest {
         }));
     }
 
+    protected static void assertNetworkStats(final boolean 
connectorStartTimestampZeroExpected, final boolean 
bridgeStartTimestampZeroExpected, final long localExceptionCount, final long 
remoteExceptionCount, final NetworkConnector networkConnector) throws Exception 
{
+        if(connectorStartTimestampZeroExpected) {
+            assertEquals(Long.valueOf(0L), 
Long.valueOf(networkConnector.getStartedTimestamp()));
+        } else {
+            assertNotEquals(Long.valueOf(0L), 
Long.valueOf(networkConnector.getStartedTimestamp()));
+        }
+
+        for(var networkBridge : networkConnector.activeBridges()) {
+            if(bridgeStartTimestampZeroExpected) {
+                assertEquals(Long.valueOf(0L), 
Long.valueOf(networkBridge.getStartedTimestamp()));
+            } else {
+                assertNotEquals(Long.valueOf(0L), 
Long.valueOf(networkBridge.getStartedTimestamp()));
+            }
+        }
+
+        // Bridges are ephemeral, so exception counts are asserted at the 
connector level where
+        // they are rolled up. Failures arrive asynchronously, so first wait 
for the counters to
+        // reach the expected values, then assert they are exact - the 
connector uses a
+        // non-retrying discovery agent, so each fault yields a single, 
definitive count.
+        assertTrue("Timed out waiting for connector exception counts local>=" 
+ localExceptionCount
+                        + " remote>=" + remoteExceptionCount,
+                Wait.waitFor(new Wait.Condition() {
+                    @Override
+                    public boolean isSatisified() throws Exception {
+                        return 
networkConnector.getLocalExceptionCounter().getCount() >= localExceptionCount &&
+                               
networkConnector.getRemoteExceptionCounter().getCount() >= remoteExceptionCount;
+                    }
+                }));
+        assertEquals(localExceptionCount, 
networkConnector.getLocalExceptionCounter().getCount());
+        assertEquals(remoteExceptionCount, 
networkConnector.getRemoteExceptionCounter().getCount());
+    }
 }
diff --git 
a/activemq-unit-tests/src/test/resources/org/apache/activemq/network/localBroker-advancedNetworkStatistics.xml
 
b/activemq-unit-tests/src/test/resources/org/apache/activemq/network/localBroker-advancedNetworkStatistics.xml
index f17fa9bcc5..f6438afd74 100644
--- 
a/activemq-unit-tests/src/test/resources/org/apache/activemq/network/localBroker-advancedNetworkStatistics.xml
+++ 
b/activemq-unit-tests/src/test/resources/org/apache/activemq/network/localBroker-advancedNetworkStatistics.xml
@@ -42,7 +42,11 @@
          dynamicOnly = "false"
          conduitSubscriptions = "true"
          decreaseNetworkConsumerPriority = "false"
-         name="networkConnector">
+         name="local-to-remote"
+         userName="localAdmin"
+         password="passwordA"
+         remoteUserName="remoteAdmin"
+         remotePassword="passwordB">
          <dynamicallyIncludedDestinations>
             <queue physicalName="include.test.foo"/>
             <topic physicalName="include.test.durable"/>
@@ -56,10 +60,33 @@
       </networkConnector>
     </networkConnectors>
 
+    <plugins>
+      <authorizationPlugin>
+        <map>
+          <authorizationMap>
+            <authorizationEntries>
+              <authorizationEntry queue=">" read="admins" write="admins" 
admin="admins" />
+              <authorizationEntry topic=">" read="admins" write="admins" 
admin="admins" />
+              <authorizationEntry topic="ActiveMQ.Advisory.>" read="*" 
write="*" admin="*"/>
+            </authorizationEntries>
+            <tempDestinationAuthorizationEntry>
+              <tempDestinationAuthorizationEntry read="admins" write="admins" 
admin="admins"/>
+            </tempDestinationAuthorizationEntry>
+          </authorizationMap>
+        </map>
+      </authorizationPlugin>
+      <simpleAuthenticationPlugin>
+        <users>
+          <authenticationUser username="localAdmin" password="passwordA" 
groups="admins"/>
+          <authenticationUser username="remoteAdmin" password="passwordB" 
groups="admins"/>
+        </users>
+      </simpleAuthenticationPlugin>
+    </plugins>
+
     <transportConnectors>
       <transportConnector uri="tcp://localhost:61616"/>
     </transportConnectors>
-    
+
   </broker>
 </beans>
 
diff --git 
a/activemq-unit-tests/src/test/resources/org/apache/activemq/network/remoteBroker-advancedNetworkStatistics.xml
 
b/activemq-unit-tests/src/test/resources/org/apache/activemq/network/remoteBroker-advancedNetworkStatistics.xml
index d2b3c0f33d..85d99e508e 100644
--- 
a/activemq-unit-tests/src/test/resources/org/apache/activemq/network/remoteBroker-advancedNetworkStatistics.xml
+++ 
b/activemq-unit-tests/src/test/resources/org/apache/activemq/network/remoteBroker-advancedNetworkStatistics.xml
@@ -37,9 +37,30 @@
       </policyMap>
     </destinationPolicy>
     <networkConnectors>
-      <networkConnector uri="static:(tcp://localhost:61616)" />
-      </networkConnectors>
-
+      <networkConnector name="remote-to-local" 
uri="static:(tcp://localhost:61616)" userName="localAdmin" password="passwordA" 
remoteUserName="remoteAdmin" remotePassword="passwordB"/>
+    </networkConnectors>
+    <plugins>
+      <authorizationPlugin>
+        <map>
+          <authorizationMap>
+            <authorizationEntries>
+              <authorizationEntry queue=">" read="admins" write="admins" 
admin="admins" />
+              <authorizationEntry topic=">" read="admins" write="admins" 
admin="admins" />
+              <authorizationEntry topic="ActiveMQ.Advisory.>" read="*" 
write="*" admin="*"/>
+            </authorizationEntries>
+            <tempDestinationAuthorizationEntry>
+              <tempDestinationAuthorizationEntry read="admins" write="admins" 
admin="admins"/>
+            </tempDestinationAuthorizationEntry>
+          </authorizationMap>
+        </map>
+      </authorizationPlugin>
+      <simpleAuthenticationPlugin>
+        <users>
+          <authenticationUser username="localAdmin" password="passwordA" 
groups="admins"/>
+          <authenticationUser username="remoteAdmin" password="passwordB" 
groups="admins"/>
+        </users>
+      </simpleAuthenticationPlugin>
+    </plugins>
     <transportConnectors>
       <transportConnector uri="tcp://localhost:61617"/>
     </transportConnectors>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information, visit: https://activemq.apache.org/contact



Reply via email to