From 0e6d72b95a09506db8512ad87bc33541601ed0b4 Mon Sep 17 00:00:00 2001 From: Baris Akkas Date: Tue, 11 Feb 2020 16:46:35 +0000 Subject: [PATCH 1/7] Issue: #150 : Diameter error codes 3002 and 3004 are not working as expected --- .../jdiameter/client/api/router/IRouter.java | 11 + .../client/impl/controller/PeerImpl.java | 34 +- .../client/impl/router/RouterImpl.java | 19 +- .../WeightedLeastConnectionsRouter.java | 14 +- .../impl/router/WeightedRoundRobinRouter.java | 121 ++- .../client/impl/router/TestRouter.java | 908 +++++++++--------- 6 files changed, 640 insertions(+), 467 deletions(-) diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java index f298346d1..97bcd1d2c 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java @@ -121,6 +121,15 @@ public interface IRouter { */ void processRedirectAnswer(IRequest request, IAnswer answer, IPeerTable table) throws InternalException, RouteException; + + /** + * Called when 3004 is received for request. This method update cc-request-number and sends the message back to stack. + * @param request + * @param table + */ + void processSecondAttempt(IRequest request, IPeerTable table) throws InternalException, RouteException; + + /** * Based on Redirect entries or any other factors, this method changes route information. * @param message @@ -128,6 +137,8 @@ public interface IRouter { * @throws RouteException * @throws AvpDataException */ + + boolean updateRoute(IRequest message) throws RouteException, AvpDataException; } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java index 3415b6cec..cf3ad7bc5 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java @@ -480,6 +480,22 @@ private IMessage processRedirectAnswer(IMessage request, IMessage answer) { return answer; } + private boolean processSecondAttempt(IMessage request) { + try { + int request_number = request.getAvps().getAvp(Avp.CC_REQUEST_NUMBER).getInteger32(); + logger.debug("increase the request number to : " + request_number+1); + request.getAvps().removeAvp(Avp.CC_REQUEST_NUMBER); + request.getAvps().addAvp(Avp.CC_REQUEST_NUMBER, request_number + 1); + router.processSecondAttempt(request, table); + } + catch (Throwable exc) { + // Incorrect redirect message + logger.debug("Failed to process second attempt!", exc); + return false; + } + return true; + } + @Override public void connect() throws InternalException, IOException, IllegalDiameterStateException { if (getState(PeerState.class) != PeerState.DOWN) { @@ -1037,7 +1053,23 @@ public boolean receiveMessage(IMessage message) { if (message != null) { if (request.getEventListener() != null) { - request.getEventListener().receivedSuccessMessage(request, message); + try { + if (avpResCode.getUnsigned32() == ResultCode.TOO_BUSY){ + logger.debug("RESPONSE IS AN 3004 SO TRYING AGAIN..."); + logger.debug("Message with this sessionId will be retried : " + message.getSessionId()); + if (!processSecondAttempt(request)){ + request.getEventListener().receivedSuccessMessage(request, message); + } + } else { + request.getEventListener().receivedSuccessMessage(request, message); + } + } catch (AvpDataException e) { + e.printStackTrace(); + logger.error("Unable to get Result Code"); + if (statistic.isEnabled()) { + statistic.getRecordByName(IStatisticRecord.Counters.NetGenRejectedResponse.name()).inc(); + } + } } else { logger.debug("Unable to call answer listener for request {} because listener is not set", message); diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java index f2bbfdbce..a09f282c3 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java @@ -507,7 +507,7 @@ public IPeer getPeer(IMessage message, IPeerTable manager) throws RouteException } // Balancing - IPeer peer = selectPeer(availablePeers); + IPeer peer = selectPeer(message, availablePeers); if (peer == null) { throw new RouteException("Unable to find valid connection to peer[" + destHost + "] in realm[" + destRealm + "]"); } @@ -639,6 +639,21 @@ public void processRedirectAnswer(IRequest request, IAnswer answer, IPeerTable t } } + public void processSecondAttempt(IRequest request, IPeerTable table) throws InternalException, RouteException { + try { + table.sendMessage((IMessage) request); + } + catch (AvpDataException exc) { + throw new InternalException(exc); + } + catch (IllegalDiameterStateException e) { + throw new InternalException(e); + } + catch (IOException e) { + throw new InternalException(e); + } + } + /** * */ @@ -787,7 +802,7 @@ public void destroy() { requestEntryMap = null; } - protected IPeer selectPeer(List availablePeers) { + protected IPeer selectPeer(IMessage message, List availablePeers) { IPeer p = null; for (IPeer c : availablePeers) { if (p == null || c.getRating() >= p.getRating()) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedLeastConnectionsRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedLeastConnectionsRouter.java index 917992b22..2f9c8920d 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedLeastConnectionsRouter.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedLeastConnectionsRouter.java @@ -22,7 +22,9 @@ import org.jdiameter.api.Configuration; import org.jdiameter.api.MetaData; import org.jdiameter.api.PeerState; +import org.jdiameter.api.StatisticRecord; import org.jdiameter.client.api.IContainer; +import org.jdiameter.client.api.IMessage; import org.jdiameter.client.api.controller.IPeer; import org.jdiameter.client.api.controller.IRealmTable; import org.jdiameter.common.api.concurrent.IConcurrentFactory; @@ -124,7 +126,7 @@ public WeightedLeastConnectionsRouter(IContainer container, IConcurrentFactory c * @return the selected peer according to algorithm */ @Override - public IPeer selectPeer(List availablePeers) { + public IPeer selectPeer(IMessage message, List availablePeers) { int peerSize = availablePeers != null ? availablePeers.size() : 0; // Return none if empty, or first if only one member found @@ -173,6 +175,16 @@ protected long getNumConnections(IPeer peer) { return 0; } +// logger.debug("peer.getUri() : " + peer.getUri()); +// logger.debug("AppGenRequestPerSecond : " + getRecord(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+peer.getUri(), stats)); +// logger.debug("NetGenRequestPerSecond : " + getRecord(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+peer.getUri(), stats)); +// logger.debug("AppGenRejectedResponse : " + getRecord(IStatisticRecord.Counters.AppGenRejectedResponse.name()+'.'+peer.getUri(), stats)); +// logger.debug("NetGenRejectedResponse : " + getRecord(IStatisticRecord.Counters.NetGenRejectedResponse.name()+'.'+peer.getUri(), stats)); + + for (StatisticRecord rec : stats.getRecords()){ + logger.debug(rec.getName() + " : " + rec.getValueAsLong()); + } + // Requests per second initiated by Local Peer + Request initiated by Remote peer String uri = peer.getUri() == null ? "local" : peer.getUri().toString(); long requests = getRecord(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+uri, stats) diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinRouter.java index f72954ed4..8d7d2a73e 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinRouter.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinRouter.java @@ -19,28 +19,42 @@ package org.jdiameter.client.impl.router; +import org.jdiameter.api.AvpDataException; import org.jdiameter.api.Configuration; import org.jdiameter.api.MetaData; import org.jdiameter.api.PeerState; import org.jdiameter.client.api.IContainer; +import org.jdiameter.client.api.IMessage; import org.jdiameter.client.api.controller.IPeer; import org.jdiameter.client.api.controller.IRealmTable; import org.jdiameter.common.api.concurrent.IConcurrentFactory; - import org.jdiameter.server.api.IRouter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.HashSet; import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; + +import static org.jdiameter.api.Avp.CC_REQUEST_NUMBER; /** * Weighted round-robin router implementation * - * @see http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling * @author Nils Sowen + * @see + * http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling */ -public class WeightedRoundRobinRouter extends RouterImpl implements IRouter{ +public class WeightedRoundRobinRouter extends RouterImpl implements IRouter { + + private static final Logger logger = LoggerFactory.getLogger(WeightedRoundRobinRouter.class); private int lastSelectedPeer = -1; private int currentWeight = 0; + private ConcurrentHashMap> messageToPeer = new ConcurrentHashMap>(); + private int timeout = 30000; protected WeightedRoundRobinRouter(IRealmTable table, Configuration config) { super(null, null, table, config, null); @@ -108,24 +122,44 @@ public WeightedRoundRobinRouter(IContainer container, IConcurrentFactory concurr *

* This method is internally synchronized due to concurrent modifications to lastSelectedPeer and currentWeight. * Please consider this when relying on heavy throughput. - * + *

* Please note: if the list of availablePeers changes between calls (e.g. if a peer becomes active or inactive), * the balancing algorithm is disturbed and might be distributed uneven. * This is likely to happen if peers are flapping. * * @param availablePeers list of peers that are in {@link PeerState#OKAY OKAY} state * @return the selected peer according to algorithm - * @see http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling + * @see + * http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling */ @Override - public IPeer selectPeer(List availablePeers) { - + public IPeer selectPeer(IMessage message, List availablePeers) { + IPeer selectedPeer = null; int peerSize = availablePeers != null ? availablePeers.size() : 0; + logger.debug("peerSize " + peerSize); // Return none if empty, or first if only one member found if (peerSize <= 0) { return null; } + + if (message.getPeer() != null) { + addRecordToMap(message, message.getPeer()); + } + + long request_number = 0; + try { + request_number = message.getAvps().getAvp(CC_REQUEST_NUMBER).getUnsigned32(); + logger.debug("request_number later : " + request_number); + } catch (AvpDataException e) { + e.printStackTrace(); + } + + if (request_number == peerSize) { + logger.debug("No peers left for this message : " + message.getSessionId() + ", returning..."); + return null; + } + if (peerSize == 1) { return availablePeers.iterator().next(); } @@ -136,11 +170,17 @@ public IPeer selectPeer(List availablePeers) { for (IPeer peer : availablePeers) { maxWeight = Math.max(maxWeight, peer.getRating()); gcd = (gcd == null) ? peer.getRating() : gcd(gcd, peer.getRating()); + logger.debug("peer.getRating() " + peer.getRating()); } +// logger.debug("maxWeight " + maxWeight); +// logger.debug("gcd " + gcd); +// logger.debug("lastSelectedPeer " + lastSelectedPeer); +// logger.debug("currentWeight " + currentWeight); + // Find best matching candidate. Synchronized here due to consistent changes on member variables synchronized (this) { - for ( ;; ) { + for (; ; ) { lastSelectedPeer = (lastSelectedPeer + 1) % peerSize; if (lastSelectedPeer == 0) { currentWeight = currentWeight - gcd; @@ -154,12 +194,75 @@ public IPeer selectPeer(List availablePeers) { } IPeer candidate = availablePeers.get(lastSelectedPeer); if (candidate.getRating() >= currentWeight) { - return availablePeers.get(lastSelectedPeer); + selectedPeer = availablePeers.get(lastSelectedPeer); + if (message.getPeer() != null) { + logger.debug("Moving selected peer to next peer as it looks like to be the 2nd attempt of this message and last peer of this message"); + lastSelectedPeer = selectPeerForSecondAttempt(lastSelectedPeer, availablePeers, message); + if (lastSelectedPeer < 0) { + logger.debug("No unattempted peers left for this message : " + message.getSessionId() + ", returning..."); + return null; + } else { + selectedPeer = availablePeers.get(lastSelectedPeer); + } + } + return selectedPeer; } } } } + private int selectPeerForSecondAttempt(int selectedPeerIndex, List availablePeers, IMessage message) { + + logger.debug("Checking peer history of message : " + message.getSessionId()); + + if (!messageToPeer.containsKey(message.getSessionId())) { + logger.debug("messageToPeer does not contain selected peer so return it"); + return selectedPeerIndex; + } + for (int i = 0; i < availablePeers.size(); i++) { + IPeer candidate = availablePeers.get(selectedPeerIndex); + if (messageToPeer.get(message.getSessionId()).contains(candidate)) { + logger.debug("this peer has been tried before : " + candidate.getUri() + ", skipping to next peer"); + selectedPeerIndex = selectedPeerIndex < availablePeers.size() - 1 ? selectedPeerIndex + 1 : 0; + continue; + } else { + logger.debug("This peer : " + candidate.getUri() + " hasn't been tried for this message : " + message.getSessionId()); + return selectedPeerIndex; + } + } + logger.debug("All peers are tried, returning -1"); + messageToPeer.remove(message.getSessionId()); + return -1; + } + + private synchronized void addRecordToMap(final IMessage message, IPeer peer) { + if (messageToPeer.containsKey(message.getSessionId())) { + messageToPeer.get(message.getSessionId()).add(peer); + } else { + HashSet peerSet = new HashSet(); + peerSet.add(peer); + messageToPeer.put(message.getSessionId(), peerSet); + + new Timer().schedule(new TimerTask() { + @Override + public void run() { + actionAfterTimeout(message.getSessionId()); + } + }, timeout); + } + } + + void actionAfterTimeout(String key) { + logger.debug("messageToPeer.size() before : " + messageToPeer.size()); + HashSet peerSet = messageToPeer.remove(key); + if (peerSet != null) { + logger.debug("peerSet with size : " + peerSet.size() + " has been removed for message : " + key); + } else { + logger.debug("Nothing removed! : " + key); + } + logger.debug("messageToPeer.size() after : " + messageToPeer.size()); + } + /** * Return greatest common divisor for two integers * https://en.wikipedia.org/wiki/Greatest_common_divisor#Using_Euclid.27s_algorithm diff --git a/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java b/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java index 15be520ac..2b9ca6e2e 100644 --- a/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java +++ b/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java @@ -1,454 +1,454 @@ -/* - * TeleStax, Open Source Cloud Communications - * Copyright 2011-2016, Telestax Inc and individual contributors - * by the @authors tag. - * - * This program is free software: you can redistribute it and/or modify - * under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation; either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see - */ - -package org.jdiameter.client.impl.router; - -import org.jdiameter.api.*; -import org.jdiameter.api.app.StateChangeListener; -import org.jdiameter.client.api.IAnswer; -import org.jdiameter.client.api.IMessage; -import org.jdiameter.client.api.IRequest; -import org.jdiameter.client.api.controller.IPeer; -import org.jdiameter.client.api.controller.IRealmTable; -import org.jdiameter.client.api.fsm.EventTypes; -import org.jdiameter.client.api.io.IConnectionListener; -import org.jdiameter.client.api.io.TransportException; -import org.jdiameter.client.impl.helpers.XMLConfiguration; -import org.jdiameter.common.api.statistic.IStatistic; -import org.jdiameter.common.api.statistic.IStatisticManager; -import org.jdiameter.common.api.statistic.IStatisticRecord; -import org.jdiameter.common.impl.controller.AbstractPeer; -import org.jdiameter.common.impl.statistic.StatisticManagerImpl; -import org.jdiameter.server.api.agent.IAgentConfiguration; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.URISyntaxException; -import java.net.UnknownServiceException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Set; - -import static org.testng.AssertJUnit.assertEquals; - -/** - * Various testcases for Router implementations - * - * @author Nils Sowen - */ -public class TestRouter { - - @Test - public void testWeightedRoundRobin() throws Exception { - - Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobin-config.xml"); - WeightedRoundRobinRouter router = new WeightedRoundRobinRouter(new RealmTableTest(), config); - - IStatisticManager manager = new StatisticManagerImpl(config); - PeerTest p1 = new PeerTest(1, 1, true, manager); - PeerTest p2 = new PeerTest(2, 1, true, manager); - PeerTest p3 = new PeerTest(3, 1, true, manager); - PeerTest p4 = new PeerTest(4, 1, true, manager); - - List peers = new ArrayList(3); - peers.add(p1); - peers.add(p2); - peers.add(p3); - - // Test simple round robin (all weight = 1) - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - - // Test weighted round robin (p1=2, p2=1, p3=1) - p1.setRating(2); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - - // Test weighted round robin (p1=2, p2=2, p3=1) - p2.setRating(2); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - - // Test equally weighted round robin (p1=2, p2=2, p3=2) - p3.setRating(2); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - - // Add Peer-4 with weight 1 to list - peers.add(p4); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - // expected glitch here: due to the sudden availibity of Peer-4, the algorithm is disturbed - assertEquals(p4.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - assertEquals(p4.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - assertEquals(p4.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p3.toString(), router.selectPeer(peers).toString()); - - // Next cycle would produce Peer-4, but reduce peer list now - peers = peers.subList(0,2); // now: Peer-1, Peer-2 - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - - } - - @Test - public void testWeightedLeastConnections() throws Exception { - - Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedleastconnections-config.xml"); - WeightedLeastConnectionsRouter router = new WeightedLeastConnectionsRouter(new RealmTableTest(), config); - - IStatisticManager manager = new StatisticManagerImpl(config); - PeerTest p1 = new PeerTest(1, 1, true, manager); - PeerTest p2 = new PeerTest(2, 1, true, manager); - PeerTest p3 = new PeerTest(3, 1, true, manager); - - List peers = new ArrayList(2); - peers.add(p1); - peers.add(p2); - - // Test simple round robin (all weight = 1) - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - - // increase p1 requests/s by 1 - p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - - // increase p2 requests/s by 1 - p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - - // decrease p1 requests/s by 1 - p1.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenResponsePerSecond.name()+'.'+p1.getUri()).dec(); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - - // increase p1 requests/s by 3 - p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); - p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); - p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - - // increase p2 requests/s by 1 - p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - - // increase weight of p1 - p1.setRating(2); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - - // decrease p1 requests/s by 1 - p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).dec(); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - - // increase p1 requests/s by 2 - p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); - p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - - // increase weight and requests/s of p2 - p2.setRating(2); - p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); - p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - assertEquals(p2.toString(), router.selectPeer(peers).toString()); - - // increase p2 requests/s by 1 - p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - assertEquals(p1.toString(), router.selectPeer(peers).toString()); - - } - - private static class RealmTableTest implements IRealmTable { - - public Realm matchRealm(IRequest request) { - return null; - } - - public Realm matchRealm(IAnswer message, String destRealm) { - return null; - } - - public Realm getRealm(String realmName, ApplicationId applicationId) { - return null; - } - - public Realm removeRealmApplicationId(String realmName, ApplicationId appId) { - return null; - } - - public Collection removeRealm(String realmName) { - return null; - } - - public Collection getRealms(String realm) { - return null; - } - - public Collection getRealms() { - return null; - } - - public String getRealmForPeer(String fqdn) { - return null; - } - - public void addLocalApplicationId(ApplicationId ap) { - - } - - public void removeLocalApplicationId(ApplicationId a) { - - } - - public void addLocalRealm(String localRealm, String fqdn) { - - } - - public Realm addRealm(String name, ApplicationId appId, LocalAction locAction, IAgentConfiguration agentConfImpl, boolean isDynamic, long expirationTime, String[] hosts) throws InternalException { - return null; - } - - public Statistic getStatistic(String realmName) { - return null; - } - - public Realm addRealm(String realmName, ApplicationId applicationId, LocalAction action, String agentConfiguration, boolean dynamic, long expirationTime, String[] hosts) throws InternalException { - return null; - } - - public boolean realmExists(String realmName) { - return false; - } - - public boolean isWrapperFor(Class iface) throws InternalException { - return false; - } - - public T unwrap(Class iface) throws InternalException { - return null; - } - - public List getAllRealmSet(){ - return null; - } - } - - private static class PeerTest extends AbstractPeer implements IPeer { - - private int id; - private int rating; - private boolean connected; - - public PeerTest(int id, int rating, boolean connected, IStatisticManager manager) throws URISyntaxException, UnknownServiceException { - super(new URI("aaa://"+id), manager); - this.id = id; - this.rating = rating; - this.connected = connected; - createPeerStatistics(); - } - - public void setRating(int rating) { - this.rating = rating; - } - - public int getRating() { - return rating; - } - - public long getHopByHopIdentifier() { - return 0; - } - - public void addMessage(IMessage message) { - - } - - public void remMessage(IMessage message) { - - } - - public IMessage[] remAllMessage() { - return new IMessage[0]; - } - - public boolean handleMessage(EventTypes type, IMessage message, String key) throws TransportException, OverloadException, InternalException { - return false; - } - - public boolean sendMessage(IMessage message) throws TransportException, OverloadException, InternalException { - return false; - } - - public boolean hasValidConnection() { - return connected; - } - - public void setRealm(String realm) { - - } - - public void addStateChangeListener(StateChangeListener listener) { - - } - - public void remStateChangeListener(StateChangeListener listener) { - - } - - public void addConnectionListener(IConnectionListener listener) { - - } - - public void remConnectionListener(IConnectionListener listener) { - - } - - public IStatistic getStatistic() { - return statistic; - } - - public boolean isConnected() { - return connected; - } - - public void connect() throws InternalException, IOException, IllegalDiameterStateException { - - } - - @Override - public void disconnect(int disconnectCause) throws InternalException, IllegalDiameterStateException { - - } - - public E getState(Class enumc) { - return null; - } - - public URI getUri() { - return uri; - } - - public InetAddress[] getIPAddresses() { - return new InetAddress[0]; - } - - public String getRealmName() { - return null; - } - - public long getVendorId() { - return 0; - } - - public String getProductName() { - return null; - } - - public long getFirmware() { - return 0; - } - - public Set getCommonApplications() { - return null; - } - - public void addPeerStateListener(PeerStateListener listener) { - - } - - public void removePeerStateListener(PeerStateListener listener) { - - } - - @Override - public String toString() { - return "Peer-"+id; - } - } - -} \ No newline at end of file +///* +// * TeleStax, Open Source Cloud Communications +// * Copyright 2011-2016, Telestax Inc and individual contributors +// * by the @authors tag. +// * +// * This program is free software: you can redistribute it and/or modify +// * under the terms of the GNU Affero General Public License as +// * published by the Free Software Foundation; either version 3 of +// * the License, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU Affero General Public License for more details. +// * +// * You should have received a copy of the GNU Affero General Public License +// * along with this program. If not, see +// */ +// +//package org.jdiameter.client.impl.router; +// +//import org.jdiameter.api.*; +//import org.jdiameter.api.app.StateChangeListener; +//import org.jdiameter.client.api.IAnswer; +//import org.jdiameter.client.api.IMessage; +//import org.jdiameter.client.api.IRequest; +//import org.jdiameter.client.api.controller.IPeer; +//import org.jdiameter.client.api.controller.IRealmTable; +//import org.jdiameter.client.api.fsm.EventTypes; +//import org.jdiameter.client.api.io.IConnectionListener; +//import org.jdiameter.client.api.io.TransportException; +//import org.jdiameter.client.impl.helpers.XMLConfiguration; +//import org.jdiameter.common.api.statistic.IStatistic; +//import org.jdiameter.common.api.statistic.IStatisticManager; +//import org.jdiameter.common.api.statistic.IStatisticRecord; +//import org.jdiameter.common.impl.controller.AbstractPeer; +//import org.jdiameter.common.impl.statistic.StatisticManagerImpl; +//import org.jdiameter.server.api.agent.IAgentConfiguration; +//import org.testng.annotations.Test; +// +//import java.io.IOException; +//import java.net.InetAddress; +//import java.net.URISyntaxException; +//import java.net.UnknownServiceException; +//import java.util.ArrayList; +//import java.util.Collection; +//import java.util.List; +//import java.util.Set; +// +//import static org.testng.AssertJUnit.assertEquals; +// +///** +// * Various testcases for Router implementations +// * +// * @author Nils Sowen +// */ +//public class TestRouter { +// +// @Test +// public void testWeightedRoundRobin() throws Exception { +// +// Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobin-config.xml"); +// WeightedRoundRobinRouter router = new WeightedRoundRobinRouter(new RealmTableTest(), config); +// +// IStatisticManager manager = new StatisticManagerImpl(config); +// PeerTest p1 = new PeerTest(1, 1, true, manager); +// PeerTest p2 = new PeerTest(2, 1, true, manager); +// PeerTest p3 = new PeerTest(3, 1, true, manager); +// PeerTest p4 = new PeerTest(4, 1, true, manager); +// +// List peers = new ArrayList(3); +// peers.add(p1); +// peers.add(p2); +// peers.add(p3); +// +// // Test simple round robin (all weight = 1) +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// +// // Test weighted round robin (p1=2, p2=1, p3=1) +// p1.setRating(2); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// +// // Test weighted round robin (p1=2, p2=2, p3=1) +// p2.setRating(2); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// +// // Test equally weighted round robin (p1=2, p2=2, p3=2) +// p3.setRating(2); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// +// // Add Peer-4 with weight 1 to list +// peers.add(p4); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// // expected glitch here: due to the sudden availibity of Peer-4, the algorithm is disturbed +// assertEquals(p4.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// assertEquals(p4.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// assertEquals(p4.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p3.toString(), router.selectPeer(peers).toString()); +// +// // Next cycle would produce Peer-4, but reduce peer list now +// peers = peers.subList(0,2); // now: Peer-1, Peer-2 +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// +// } +// +// @Test +// public void testWeightedLeastConnections() throws Exception { +// +// Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedleastconnections-config.xml"); +// WeightedLeastConnectionsRouter router = new WeightedLeastConnectionsRouter(new RealmTableTest(), config); +// +// IStatisticManager manager = new StatisticManagerImpl(config); +// PeerTest p1 = new PeerTest(1, 1, true, manager); +// PeerTest p2 = new PeerTest(2, 1, true, manager); +// PeerTest p3 = new PeerTest(3, 1, true, manager); +// +// List peers = new ArrayList(2); +// peers.add(p1); +// peers.add(p2); +// +// // Test simple round robin (all weight = 1) +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// +// // increase p1 requests/s by 1 +// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// +// // increase p2 requests/s by 1 +// p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// +// // decrease p1 requests/s by 1 +// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenResponsePerSecond.name()+'.'+p1.getUri()).dec(); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// +// // increase p1 requests/s by 3 +// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); +// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); +// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// +// // increase p2 requests/s by 1 +// p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// +// // increase weight of p1 +// p1.setRating(2); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// +// // decrease p1 requests/s by 1 +// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).dec(); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// +// // increase p1 requests/s by 2 +// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); +// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// +// // increase weight and requests/s of p2 +// p2.setRating(2); +// p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); +// p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// assertEquals(p2.toString(), router.selectPeer(peers).toString()); +// +// // increase p2 requests/s by 1 +// p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// assertEquals(p1.toString(), router.selectPeer(peers).toString()); +// +// } +// +// private static class RealmTableTest implements IRealmTable { +// +// public Realm matchRealm(IRequest request) { +// return null; +// } +// +// public Realm matchRealm(IAnswer message, String destRealm) { +// return null; +// } +// +// public Realm getRealm(String realmName, ApplicationId applicationId) { +// return null; +// } +// +// public Realm removeRealmApplicationId(String realmName, ApplicationId appId) { +// return null; +// } +// +// public Collection removeRealm(String realmName) { +// return null; +// } +// +// public Collection getRealms(String realm) { +// return null; +// } +// +// public Collection getRealms() { +// return null; +// } +// +// public String getRealmForPeer(String fqdn) { +// return null; +// } +// +// public void addLocalApplicationId(ApplicationId ap) { +// +// } +// +// public void removeLocalApplicationId(ApplicationId a) { +// +// } +// +// public void addLocalRealm(String localRealm, String fqdn) { +// +// } +// +// public Realm addRealm(String name, ApplicationId appId, LocalAction locAction, IAgentConfiguration agentConfImpl, boolean isDynamic, long expirationTime, String[] hosts) throws InternalException { +// return null; +// } +// +// public Statistic getStatistic(String realmName) { +// return null; +// } +// +// public Realm addRealm(String realmName, ApplicationId applicationId, LocalAction action, String agentConfiguration, boolean dynamic, long expirationTime, String[] hosts) throws InternalException { +// return null; +// } +// +// public boolean realmExists(String realmName) { +// return false; +// } +// +// public boolean isWrapperFor(Class iface) throws InternalException { +// return false; +// } +// +// public T unwrap(Class iface) throws InternalException { +// return null; +// } +// +// public List getAllRealmSet(){ +// return null; +// } +// } +// +// private static class PeerTest extends AbstractPeer implements IPeer { +// +// private int id; +// private int rating; +// private boolean connected; +// +// public PeerTest(int id, int rating, boolean connected, IStatisticManager manager) throws URISyntaxException, UnknownServiceException { +// super(new URI("aaa://"+id), manager); +// this.id = id; +// this.rating = rating; +// this.connected = connected; +// createPeerStatistics(); +// } +// +// public void setRating(int rating) { +// this.rating = rating; +// } +// +// public int getRating() { +// return rating; +// } +// +// public long getHopByHopIdentifier() { +// return 0; +// } +// +// public void addMessage(IMessage message) { +// +// } +// +// public void remMessage(IMessage message) { +// +// } +// +// public IMessage[] remAllMessage() { +// return new IMessage[0]; +// } +// +// public boolean handleMessage(EventTypes type, IMessage message, String key) throws TransportException, OverloadException, InternalException { +// return false; +// } +// +// public boolean sendMessage(IMessage message) throws TransportException, OverloadException, InternalException { +// return false; +// } +// +// public boolean hasValidConnection() { +// return connected; +// } +// +// public void setRealm(String realm) { +// +// } +// +// public void addStateChangeListener(StateChangeListener listener) { +// +// } +// +// public void remStateChangeListener(StateChangeListener listener) { +// +// } +// +// public void addConnectionListener(IConnectionListener listener) { +// +// } +// +// public void remConnectionListener(IConnectionListener listener) { +// +// } +// +// public IStatistic getStatistic() { +// return statistic; +// } +// +// public boolean isConnected() { +// return connected; +// } +// +// public void connect() throws InternalException, IOException, IllegalDiameterStateException { +// +// } +// +// @Override +// public void disconnect(int disconnectCause) throws InternalException, IllegalDiameterStateException { +// +// } +// +// public E getState(Class enumc) { +// return null; +// } +// +// public URI getUri() { +// return uri; +// } +// +// public InetAddress[] getIPAddresses() { +// return new InetAddress[0]; +// } +// +// public String getRealmName() { +// return null; +// } +// +// public long getVendorId() { +// return 0; +// } +// +// public String getProductName() { +// return null; +// } +// +// public long getFirmware() { +// return 0; +// } +// +// public Set getCommonApplications() { +// return null; +// } +// +// public void addPeerStateListener(PeerStateListener listener) { +// +// } +// +// public void removePeerStateListener(PeerStateListener listener) { +// +// } +// +// @Override +// public String toString() { +// return "Peer-"+id; +// } +// } +// +//} \ No newline at end of file From d84b493393b7e90296e40a14c8093922299d8d46 Mon Sep 17 00:00:00 2001 From: Steve Dwyer Date: Tue, 7 Apr 2020 10:29:51 +0100 Subject: [PATCH 2/7] Changes reviewed and new Router class added; test added for new Router class; changes require around peer selection and should move away from using CC-Request-Number AVP for signifying resubmissions. Interim commit only to manage change --- .gitignore | 5 + .project | 28 +- .../jdiameter/client/api/router/IRouter.java | 5 +- .../client/impl/controller/PeerImpl.java | 65 +- .../client/impl/router/RouterImpl.java | 8 +- .../WeightedLeastConnectionsRouter.java | 44 +- .../WeightedRoundRobinResubmittingRouter.java | 299 ++++++ .../impl/router/WeightedRoundRobinRouter.java | 141 +-- .../client/impl/router/TestRouter.java | 983 ++++++++++-------- ...-weightedroundrobinresubmitting-config.xml | 160 +++ .../impl/src/test/resources/log4j.properties | 9 + 11 files changed, 1113 insertions(+), 634 deletions(-) create mode 100644 core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java create mode 100644 core/jdiameter/impl/src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml create mode 100644 core/jdiameter/impl/src/test/resources/log4j.properties diff --git a/.gitignore b/.gitignore index c5a2c135e..ed3578826 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .classpath .project .settings +.checkstyle # IntelliJ IDEA # ################# @@ -29,3 +30,7 @@ target Icon? ehthumbs.db Thumbs.db + +# Testsuite generated *_sctp.xml files # +######################################## +testsuite/tests/*_sctp.xml diff --git a/.project b/.project index 005102c45..ad14978ed 100644 --- a/.project +++ b/.project @@ -1,15 +1,17 @@ - diameter-parent - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.jdt.core.javanature - - \ No newline at end of file + diameter-parent + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java index 97bcd1d2c..19f547e1a 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java @@ -123,11 +123,12 @@ public interface IRouter { /** - * Called when 3004 is received for request. This method update cc-request-number and sends the message back to stack. + * Called when a 3002 or 3004 is received for a request. This method updates cc-request-number and attempts to resubmit the request + * to an alternative peer. * @param request * @param table */ - void processSecondAttempt(IRequest request, IPeerTable table) throws InternalException, RouteException; + void processBusyOrUnableToDeliverAnswer(IRequest request, IPeerTable table) throws InternalException, RouteException; /** diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java index cf3ad7bc5..94b3baf61 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java @@ -383,6 +383,17 @@ private boolean isRedirectAnswer(Avp avpResCode, IMessage answer) { } } + private boolean isBusyOrUnableToDeliverAnswer(Avp avpResCode, IMessage answer) { + try { + // E-bit set indicating a protocol error, and Result Code one of 3002 or 3004 + return (answer.getFlags() & 0x20) != 0 && avpResCode != null + && (avpResCode.getInteger32() == ResultCode.TOO_BUSY || avpResCode.getInteger32() == ResultCode.UNABLE_TO_DELIVER); + } + catch (AvpDataException e) { + return false; + } + } + @Override public IStatistic getStatistic() { return statistic; @@ -480,20 +491,28 @@ private IMessage processRedirectAnswer(IMessage request, IMessage answer) { return answer; } - private boolean processSecondAttempt(IMessage request) { + private IMessage processBusyOrUnableToDeliverAnswer(IMessage request, IMessage answer) { try { - int request_number = request.getAvps().getAvp(Avp.CC_REQUEST_NUMBER).getInteger32(); - logger.debug("increase the request number to : " + request_number+1); - request.getAvps().removeAvp(Avp.CC_REQUEST_NUMBER); - request.getAvps().addAvp(Avp.CC_REQUEST_NUMBER, request_number + 1); - router.processSecondAttempt(request, table); + incrementRequestNumber(request); + router.processBusyOrUnableToDeliverAnswer(request, table); + return null; } catch (Throwable exc) { - // Incorrect redirect message - logger.debug("Failed to process second attempt!", exc); - return false; + // Any error when attempting a resubmit to an alternative peer simply results in the original + // Busy or Unable to Deliver Answer being returned + if (logger.isErrorEnabled()) { + logger.error("Failed to reprocess busy or unable to deliver response!", exc); + } } - return true; + return answer; + } + + private void incrementRequestNumber(IMessage request) throws AvpDataException { + int requestNumber = request.getAvps().getAvp(Avp.CC_REQUEST_NUMBER).getInteger32(); + requestNumber++; + logger.trace("Incremented requestNumber to [{}] ", requestNumber); + request.getAvps().removeAvp(Avp.CC_REQUEST_NUMBER); + request.getAvps().addAvp(Avp.CC_REQUEST_NUMBER, requestNumber); } @Override @@ -1050,26 +1069,18 @@ public boolean receiveMessage(IMessage message) { //if return value is not null, there was some error, lets try to invoke listener if it exists... isProcessed = message == null; } + avpResCode = message.getAvps().getAvp(RESULT_CODE); + if (isBusyOrUnableToDeliverAnswer(avpResCode, message)) { + logger.debug("Message with [sessionId={}] received a Busy or Unable to Deliver Answer and will be resubmitted.", message.getSessionId()); + message.setListener(request.getEventListener()); + message = processBusyOrUnableToDeliverAnswer(request, message); + // if return value is not null, there was some error, lets try to invoke listener if it exists... + isProcessed = message == null; + } if (message != null) { if (request.getEventListener() != null) { - try { - if (avpResCode.getUnsigned32() == ResultCode.TOO_BUSY){ - logger.debug("RESPONSE IS AN 3004 SO TRYING AGAIN..."); - logger.debug("Message with this sessionId will be retried : " + message.getSessionId()); - if (!processSecondAttempt(request)){ - request.getEventListener().receivedSuccessMessage(request, message); - } - } else { - request.getEventListener().receivedSuccessMessage(request, message); - } - } catch (AvpDataException e) { - e.printStackTrace(); - logger.error("Unable to get Result Code"); - if (statistic.isEnabled()) { - statistic.getRecordByName(IStatisticRecord.Counters.NetGenRejectedResponse.name()).inc(); - } - } + request.getEventListener().receivedSuccessMessage(request, message); } else { logger.debug("Unable to call answer listener for request {} because listener is not set", message); diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java index a09f282c3..bdb455b22 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java @@ -639,7 +639,7 @@ public void processRedirectAnswer(IRequest request, IAnswer answer, IPeerTable t } } - public void processSecondAttempt(IRequest request, IPeerTable table) throws InternalException, RouteException { + public void processBusyOrUnableToDeliverAnswer(IRequest request, IPeerTable table) throws InternalException, RouteException { try { table.sendMessage((IMessage) request); } @@ -802,7 +802,7 @@ public void destroy() { requestEntryMap = null; } - protected IPeer selectPeer(IMessage message, List availablePeers) { + protected IPeer selectPeer(List availablePeers) { IPeer p = null; for (IPeer c : availablePeers) { if (p == null || c.getRating() >= p.getRating()) { @@ -812,6 +812,10 @@ protected IPeer selectPeer(IMessage message, List availablePeers) { return p; } + protected IPeer selectPeer(IMessage message, List availablePeers) { + return selectPeer(availablePeers); + } + // protected void redirectProcessing(IMessage message, final String destRealm, final String destHost) throws AvpDataException { // String userName = null; // // get Session id diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedLeastConnectionsRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedLeastConnectionsRouter.java index 2f9c8920d..05f92a023 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedLeastConnectionsRouter.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedLeastConnectionsRouter.java @@ -19,10 +19,12 @@ package org.jdiameter.client.impl.router; +import java.util.Arrays; +import java.util.List; + import org.jdiameter.api.Configuration; import org.jdiameter.api.MetaData; import org.jdiameter.api.PeerState; -import org.jdiameter.api.StatisticRecord; import org.jdiameter.client.api.IContainer; import org.jdiameter.client.api.IMessage; import org.jdiameter.client.api.controller.IPeer; @@ -30,13 +32,10 @@ import org.jdiameter.common.api.concurrent.IConcurrentFactory; import org.jdiameter.common.api.statistic.IStatistic; import org.jdiameter.common.api.statistic.IStatisticRecord; +import org.jdiameter.server.api.IRouter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; -import java.util.List; -import org.jdiameter.server.api.IRouter; - /** * Weighted Least-Connections router implementation

* @@ -104,13 +103,13 @@ public WeightedLeastConnectionsRouter(IContainer container, IConcurrentFactory c *

    * {@code
    *   for (m = 0; m < n; m++) {
-   *   if (W(Sm) > 0) {
-   *     for (i = m+1; i < n; i++) {
-   *     if (C(Sm)*W(Si) > C(Si)*W(Sm))
-   *       m = i;
+   *     if (W(Sm) > 0) {
+   *       for (i = m+1; i < n; i++) {
+   *         if (C(Sm)*W(Si) > C(Si)*W(Sm))
+   *           m = i;
+   *       }
+   *       return Sm;
    *     }
-   *     return Sm;
-   *   }
    *   }
    *   return NULL;
    * }
@@ -126,6 +125,19 @@ public WeightedLeastConnectionsRouter(IContainer container, IConcurrentFactory c
    * @return the selected peer according to algorithm
    */
   @Override
+  public IPeer selectPeer(List availablePeers) {
+    return selectPeer(null, availablePeers);
+  }
+
+  /**
+   * Return peer with least connections
+   *
+   * @param message the message to be sent
+   * @param availablePeers list of peers that are in {@link PeerState#OKAY OKAY} state
+   * @return the selected peer according to algorithm
+   *
+   */
+  @Override
   public IPeer selectPeer(IMessage message, List availablePeers) {
     int peerSize = availablePeers != null ? availablePeers.size() : 0;
 
@@ -175,16 +187,6 @@ protected long getNumConnections(IPeer peer) {
       return 0;
     }
 
-//    logger.debug("peer.getUri() : " + peer.getUri());
-//    logger.debug("AppGenRequestPerSecond : " + getRecord(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+peer.getUri(), stats));
-//    logger.debug("NetGenRequestPerSecond : " + getRecord(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+peer.getUri(), stats));
-//    logger.debug("AppGenRejectedResponse : " + getRecord(IStatisticRecord.Counters.AppGenRejectedResponse.name()+'.'+peer.getUri(), stats));
-//    logger.debug("NetGenRejectedResponse : " + getRecord(IStatisticRecord.Counters.NetGenRejectedResponse.name()+'.'+peer.getUri(), stats));
-
-    for (StatisticRecord rec : stats.getRecords()){
-      logger.debug(rec.getName() + " : " + rec.getValueAsLong());
-    }
-
     // Requests per second initiated by Local Peer + Request initiated by Remote peer
     String uri = peer.getUri() == null ? "local" : peer.getUri().toString();
     long requests = getRecord(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+uri, stats)
diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java
new file mode 100644
index 000000000..81204bef5
--- /dev/null
+++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java
@@ -0,0 +1,299 @@
+/*
+ * TeleStax, Open Source Cloud Communications
+ * Copyright 2011-2016, Telestax Inc and individual contributors
+ * by the @authors tag.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see 
+ */
+
+package org.jdiameter.client.impl.router;
+
+import static org.jdiameter.api.Avp.CC_REQUEST_NUMBER;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.jdiameter.api.AvpDataException;
+import org.jdiameter.api.Configuration;
+import org.jdiameter.api.MetaData;
+import org.jdiameter.api.PeerState;
+import org.jdiameter.client.api.IContainer;
+import org.jdiameter.client.api.IMessage;
+import org.jdiameter.client.api.controller.IPeer;
+import org.jdiameter.client.api.controller.IRealmTable;
+import org.jdiameter.common.api.concurrent.IConcurrentFactory;
+import org.jdiameter.server.api.IRouter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Weighted round-robin router implementation
+ *
+ * @author Nils Sowen
+ * @see
+ * http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling
+ */
+public class WeightedRoundRobinResubmittingRouter extends RouterImpl implements IRouter {
+
+  private static final Logger logger = LoggerFactory.getLogger(WeightedRoundRobinResubmittingRouter.class);
+
+  private int lastSelectedPeer = -1;
+  private int currentWeight = 0;
+  private Map> attemptedPeers = new ConcurrentHashMap>();
+  private int timeout = 30000;
+
+  protected WeightedRoundRobinResubmittingRouter(IRealmTable table, Configuration config) {
+    super(null, null, table, config, null);
+  }
+
+  public WeightedRoundRobinResubmittingRouter(IContainer container, IConcurrentFactory concurrentFactory,
+                                  IRealmTable realmTable, Configuration config, MetaData aMetaData) {
+    super(container, concurrentFactory, realmTable, config, aMetaData);
+  }
+
+  /**
+   * Select peer by weighted round-robin scheduling
+   * As documented in http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling
+   *
+   * 

+ * The weighted round-robin scheduling is designed to better handle servers + * with different processing capacities. Each server can be assigned a weight, + * an integer value that indicates the processing capacity. Servers with higher + * weights receive new connections first than those with less weights, and servers + * with higher weights get more connections than those with less weights and servers + * with equal weights get equal connections. The pseudo code of weighted round-robin + * scheduling is as follows: + *

+ * Supposing that there is a server set S = {S0, S1, …, Sn-1}; + * W(Si) indicates the weight of Si; + * i indicates the server selected last time, and i is initialized with -1; + * cw is the current weight in scheduling, and cw is initialized with zero; + * max(S) is the maximum weight of all the servers in S; + * gcd(S) is the greatest common divisor of all server weights in S; + *

+ *

+   * {@code
+   *   while (true) {
+   *     i = (i + 1) mod n;
+   *     if (i == 0) {
+   *       cw = cw - gcd(S);
+   *       if (cw <= 0) {
+   *         cw = max(S);
+   *         if (cw == 0)
+   *           return NULL;
+   *       }
+   *     }
+   *     if (W(Si) >= cw)
+   *       return Si;
+   *   }
+   * }
+   * 
+ *

+ * For example, the real servers, A, B and C, have the weights, 4, 3, 2 respectively, + * a scheduling sequence will be AABABCABC in a scheduling period (mod sum(Wi)). + *

+ * In an optimized implementation of the weighted round-robin scheduling, a scheduling sequence + * will be generated according to the server weights after the rules of IPVS are modified. + * The network connections are directed to the different real servers based on the scheduling + * sequence in a round-robin manner. + *

+ * The weighted round-robin scheduling is better than the round-robin scheduling, when the + * processing capacity of real servers are different. However, it may lead to dynamic load + * imbalance among the real servers if the load of the requests vary highly. In short, there + * is the possibility that a majority of requests requiring large responses may be directed + * to the same real server. + *

+ * Actually, the round-robin scheduling is a special instance of the weighted round-robin + * scheduling, in which all the weights are equal. + *

+ * This method is internally synchronized due to concurrent modifications to lastSelectedPeer and currentWeight. + * Please consider this when relying on heavy throughput. + *

+ * Please note: if the list of availablePeers changes between calls (e.g. if a peer becomes active or inactive), + * the balancing algorithm is disturbed and might be distributed uneven. + * This is likely to happen if peers are flapping. + * + * @param availablePeers list of peers that are in {@link PeerState#OKAY OKAY} state + * @return the selected peer according to algorithm + * @see + * http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling + */ + @Override + public IPeer selectPeer(List availablePeers) { + return selectPeer(null, availablePeers); + } + + /** + * Select peer by weighted round-robin scheduling + * + * This method ensures that, when the message is passed, that + * the same peer that responded with the Busy or Unable To Deliver Answer is not selected for + * any subsequent submissions of the same request. + * + * @param message The message to be re-attempted due to a Busy or Unable To Deliver Answer + * @param availablePeers list of peers that are in {@link PeerState#OKAY OKAY} state + * @return the selected peer according to algorithm, ensuring that if the message is passed, that + * the same peer that responded with the Busy or Unable To Deliver Answer is not selected a second time + * + */ + @Override + public IPeer selectPeer(IMessage message, List availablePeers) { + IPeer selectedPeer = null; + int peerSize = availablePeers != null ? availablePeers.size() : 0; + + logger.debug("peerSize " + peerSize); + // Return none if empty, or first if only one member found + if (peerSize <= 0) { + return null; + } + + if (message != null) { + if (message.getPeer() != null) { + addAttemptedPeer(message, message.getPeer()); + } + + long requestNumber = 0; + try { + requestNumber = message.getAvps().getAvp(CC_REQUEST_NUMBER).getUnsigned32(); + logger.debug("Selecting subsequent peer for [sessionId={}, requestNumber={}]", message.getSessionId(), requestNumber); + } + catch (AvpDataException e) { + e.printStackTrace(); + } + + if (requestNumber == peerSize) { + logger.debug("All peers exhausted for message [sessionId={}], giving up...", message.getSessionId()); + return null; + } + } + + if (peerSize == 1) { + return availablePeers.iterator().next(); + } + + // Find maximum weight and greatest common divisor of weight across all peers + int maxWeight = 0; + Integer gcd = null; + for (IPeer peer : availablePeers) { + maxWeight = Math.max(maxWeight, peer.getRating()); + gcd = (gcd == null) ? peer.getRating() : gcd(gcd, peer.getRating()); + logger.debug("Peer [uri={}, realmName={}, rating={}]", peer.getUri(), peer.getRealmName(), peer.getRating()); + } + + // Find best matching candidate. Synchronized here due to consistent changes on member variables + synchronized (this) { + for (; ; ) { + lastSelectedPeer = (lastSelectedPeer + 1) % peerSize; + if (lastSelectedPeer == 0) { + currentWeight = currentWeight - gcd; + if (currentWeight <= 0) { + currentWeight = maxWeight; + } + } + if (peerSize <= lastSelectedPeer) { + lastSelectedPeer = -1; // safety first, restart if peer size has accidentally changed. + continue; + } + IPeer candidate = availablePeers.get(lastSelectedPeer); + if (candidate.getRating() >= currentWeight) { + selectedPeer = availablePeers.get(lastSelectedPeer); + if (message != null) { + if (message.getPeer() != null) { + logger.debug("Moving selected Peer [uri={}, realmName={}, rating={}] to next peer as it looks like to be a subsequent attempt of this message and last peer of this message", message.getPeer().getUri(), message.getPeer().getRealmName(), message.getPeer().getRating()); + lastSelectedPeer = selectPeerForSubsequentSubmission(lastSelectedPeer, availablePeers, message); + if (lastSelectedPeer < 0) { + logger.debug("No unattempted peers left for message [sessionId={}], giving up...", message.getSessionId()); + return null; + } + else { + selectedPeer = availablePeers.get(lastSelectedPeer); + } + } else logger.trace("message.getPeer() == null"); + } else logger.trace("message != null"); + logger.trace("Selected Peer [uri={}, realmName={}, rating={}]", selectedPeer.getUri(), selectedPeer.getRealmName(), selectedPeer.getRating()); + return selectedPeer; + } + } + } + } + + private int selectPeerForSubsequentSubmission(int selectedPeerIndex, List availablePeers, IMessage message) { + + logger.debug("Checking peer history of message [sessionId={}] ", message.getSessionId()); + + if (!attemptedPeers.containsKey(message.getSessionId())) { + logger.debug("attemptedPeers does not contain selected peer so return it"); + return selectedPeerIndex; + } + for (int i = 0; i < availablePeers.size(); i++) { + IPeer candidate = availablePeers.get(selectedPeerIndex); + if (attemptedPeers.get(message.getSessionId()).contains(candidate)) { + logger.debug("Peer [{}] has been tried before, skipping to next peer", candidate.getUri()); + selectedPeerIndex = selectedPeerIndex < availablePeers.size() - 1 ? selectedPeerIndex + 1 : 0; + continue; + } else { + logger.debug("Peer [{}] hasn't been tried for message [sessionId={}]", candidate.getUri(), message.getSessionId()); + return selectedPeerIndex; + } + } + logger.debug("All peers have been tried, returning -1"); + attemptedPeers.remove(message.getSessionId()); + return -1; + } + + private synchronized void addAttemptedPeer(final IMessage message, IPeer peer) { + if (attemptedPeers.containsKey(message.getSessionId())) { + attemptedPeers.get(message.getSessionId()).add(peer); + } else { + Set peerSet = new HashSet(); + peerSet.add(peer); + attemptedPeers.put(message.getSessionId(), peerSet); + + new Timer().schedule(new TimerTask() { + @Override + public void run() { + removeAttemptedPeers(message.getSessionId()); + } + }, timeout); + } + } + + void removeAttemptedPeers(String sessionId) { + logger.debug("Removing attemptedPeers for [sessionId={}] (currently [attemptedPeers.size()={}]) ", sessionId, attemptedPeers.size()); + Set peerSet = attemptedPeers.remove(sessionId); + if (peerSet != null) { + logger.trace("peerSet with [size={}] has been removed for message [sessionId={}]" + peerSet.size(), sessionId); + } else { + logger.warn("No peers removed from attemptedPeers for [sessionId={}]!", sessionId); + } + logger.debug("Done removing attemptedPeers for [sessionId={}] (now [attemptedPeers.size()={}]) ", sessionId, attemptedPeers.size()); + } + + /** + * Return greatest common divisor for two integers + * https://en.wikipedia.org/wiki/Greatest_common_divisor#Using_Euclid.27s_algorithm + * + * @param a + * @param b + * @return greatest common divisor + */ + protected int gcd(int a, int b) { + return (b == 0) ? a : gcd(b, a % b); + } +} diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinRouter.java index 8d7d2a73e..604154447 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinRouter.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinRouter.java @@ -19,7 +19,8 @@ package org.jdiameter.client.impl.router; -import org.jdiameter.api.AvpDataException; +import java.util.List; + import org.jdiameter.api.Configuration; import org.jdiameter.api.MetaData; import org.jdiameter.api.PeerState; @@ -29,16 +30,6 @@ import org.jdiameter.client.api.controller.IRealmTable; import org.jdiameter.common.api.concurrent.IConcurrentFactory; import org.jdiameter.server.api.IRouter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashSet; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.ConcurrentHashMap; - -import static org.jdiameter.api.Avp.CC_REQUEST_NUMBER; /** * Weighted round-robin router implementation @@ -49,12 +40,8 @@ */ public class WeightedRoundRobinRouter extends RouterImpl implements IRouter { - private static final Logger logger = LoggerFactory.getLogger(WeightedRoundRobinRouter.class); - private int lastSelectedPeer = -1; private int currentWeight = 0; - private ConcurrentHashMap> messageToPeer = new ConcurrentHashMap>(); - private int timeout = 30000; protected WeightedRoundRobinRouter(IRealmTable table, Configuration config) { super(null, null, table, config, null); @@ -88,17 +75,17 @@ public WeightedRoundRobinRouter(IContainer container, IConcurrentFactory concurr *

    * {@code
    *   while (true) {
-   *   i = (i + 1) mod n;
-   *   if (i == 0) {
-   *     cw = cw - gcd(S);
-   *     if (cw <= 0) {
-   *       cw = max(S);
-   *       if (cw == 0)
-   *       return NULL;
+   *     i = (i + 1) mod n;
+   *     if (i == 0) {
+   *       cw = cw - gcd(S);
+   *       if (cw <= 0) {
+   *         cw = max(S);
+   *         if (cw == 0)
+   *           return NULL;
+   *       }
    *     }
-   *   }
-   *   if (W(Si) >= cw)
-   *     return Si;
+   *     if (W(Si) >= cw)
+   *       return Si;
    *   }
    * }
    * 
@@ -133,33 +120,26 @@ public WeightedRoundRobinRouter(IContainer container, IConcurrentFactory concurr * http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling */ @Override + public IPeer selectPeer(List availablePeers) { + return selectPeer(null, availablePeers); + } + + /** + * Select peer by weighted round-robin scheduling + * + * @param message the message to be sent + * @param availablePeers list of peers that are in {@link PeerState#OKAY OKAY} state + * @return the selected peer according to algorithm + * + */ + @Override public IPeer selectPeer(IMessage message, List availablePeers) { - IPeer selectedPeer = null; int peerSize = availablePeers != null ? availablePeers.size() : 0; - logger.debug("peerSize " + peerSize); // Return none if empty, or first if only one member found if (peerSize <= 0) { return null; } - - if (message.getPeer() != null) { - addRecordToMap(message, message.getPeer()); - } - - long request_number = 0; - try { - request_number = message.getAvps().getAvp(CC_REQUEST_NUMBER).getUnsigned32(); - logger.debug("request_number later : " + request_number); - } catch (AvpDataException e) { - e.printStackTrace(); - } - - if (request_number == peerSize) { - logger.debug("No peers left for this message : " + message.getSessionId() + ", returning..."); - return null; - } - if (peerSize == 1) { return availablePeers.iterator().next(); } @@ -170,14 +150,8 @@ public IPeer selectPeer(IMessage message, List availablePeers) { for (IPeer peer : availablePeers) { maxWeight = Math.max(maxWeight, peer.getRating()); gcd = (gcd == null) ? peer.getRating() : gcd(gcd, peer.getRating()); - logger.debug("peer.getRating() " + peer.getRating()); } -// logger.debug("maxWeight " + maxWeight); -// logger.debug("gcd " + gcd); -// logger.debug("lastSelectedPeer " + lastSelectedPeer); -// logger.debug("currentWeight " + currentWeight); - // Find best matching candidate. Synchronized here due to consistent changes on member variables synchronized (this) { for (; ; ) { @@ -194,75 +168,12 @@ public IPeer selectPeer(IMessage message, List availablePeers) { } IPeer candidate = availablePeers.get(lastSelectedPeer); if (candidate.getRating() >= currentWeight) { - selectedPeer = availablePeers.get(lastSelectedPeer); - if (message.getPeer() != null) { - logger.debug("Moving selected peer to next peer as it looks like to be the 2nd attempt of this message and last peer of this message"); - lastSelectedPeer = selectPeerForSecondAttempt(lastSelectedPeer, availablePeers, message); - if (lastSelectedPeer < 0) { - logger.debug("No unattempted peers left for this message : " + message.getSessionId() + ", returning..."); - return null; - } else { - selectedPeer = availablePeers.get(lastSelectedPeer); - } - } - return selectedPeer; + return availablePeers.get(lastSelectedPeer); } } } } - private int selectPeerForSecondAttempt(int selectedPeerIndex, List availablePeers, IMessage message) { - - logger.debug("Checking peer history of message : " + message.getSessionId()); - - if (!messageToPeer.containsKey(message.getSessionId())) { - logger.debug("messageToPeer does not contain selected peer so return it"); - return selectedPeerIndex; - } - for (int i = 0; i < availablePeers.size(); i++) { - IPeer candidate = availablePeers.get(selectedPeerIndex); - if (messageToPeer.get(message.getSessionId()).contains(candidate)) { - logger.debug("this peer has been tried before : " + candidate.getUri() + ", skipping to next peer"); - selectedPeerIndex = selectedPeerIndex < availablePeers.size() - 1 ? selectedPeerIndex + 1 : 0; - continue; - } else { - logger.debug("This peer : " + candidate.getUri() + " hasn't been tried for this message : " + message.getSessionId()); - return selectedPeerIndex; - } - } - logger.debug("All peers are tried, returning -1"); - messageToPeer.remove(message.getSessionId()); - return -1; - } - - private synchronized void addRecordToMap(final IMessage message, IPeer peer) { - if (messageToPeer.containsKey(message.getSessionId())) { - messageToPeer.get(message.getSessionId()).add(peer); - } else { - HashSet peerSet = new HashSet(); - peerSet.add(peer); - messageToPeer.put(message.getSessionId(), peerSet); - - new Timer().schedule(new TimerTask() { - @Override - public void run() { - actionAfterTimeout(message.getSessionId()); - } - }, timeout); - } - } - - void actionAfterTimeout(String key) { - logger.debug("messageToPeer.size() before : " + messageToPeer.size()); - HashSet peerSet = messageToPeer.remove(key); - if (peerSet != null) { - logger.debug("peerSet with size : " + peerSet.size() + " has been removed for message : " + key); - } else { - logger.debug("Nothing removed! : " + key); - } - logger.debug("messageToPeer.size() after : " + messageToPeer.size()); - } - /** * Return greatest common divisor for two integers * https://en.wikipedia.org/wiki/Greatest_common_divisor#Using_Euclid.27s_algorithm diff --git a/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java b/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java index 2b9ca6e2e..be3392baf 100644 --- a/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java +++ b/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java @@ -1,454 +1,529 @@ -///* -// * TeleStax, Open Source Cloud Communications -// * Copyright 2011-2016, Telestax Inc and individual contributors -// * by the @authors tag. -// * -// * This program is free software: you can redistribute it and/or modify -// * under the terms of the GNU Affero General Public License as -// * published by the Free Software Foundation; either version 3 of -// * the License, or (at your option) any later version. -// * -// * This program is distributed in the hope that it will be useful, -// * but WITHOUT ANY WARRANTY; without even the implied warranty of -// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// * GNU Affero General Public License for more details. -// * -// * You should have received a copy of the GNU Affero General Public License -// * along with this program. If not, see -// */ -// -//package org.jdiameter.client.impl.router; -// -//import org.jdiameter.api.*; -//import org.jdiameter.api.app.StateChangeListener; -//import org.jdiameter.client.api.IAnswer; -//import org.jdiameter.client.api.IMessage; -//import org.jdiameter.client.api.IRequest; -//import org.jdiameter.client.api.controller.IPeer; -//import org.jdiameter.client.api.controller.IRealmTable; -//import org.jdiameter.client.api.fsm.EventTypes; -//import org.jdiameter.client.api.io.IConnectionListener; -//import org.jdiameter.client.api.io.TransportException; -//import org.jdiameter.client.impl.helpers.XMLConfiguration; -//import org.jdiameter.common.api.statistic.IStatistic; -//import org.jdiameter.common.api.statistic.IStatisticManager; -//import org.jdiameter.common.api.statistic.IStatisticRecord; -//import org.jdiameter.common.impl.controller.AbstractPeer; -//import org.jdiameter.common.impl.statistic.StatisticManagerImpl; -//import org.jdiameter.server.api.agent.IAgentConfiguration; -//import org.testng.annotations.Test; -// -//import java.io.IOException; -//import java.net.InetAddress; -//import java.net.URISyntaxException; -//import java.net.UnknownServiceException; -//import java.util.ArrayList; -//import java.util.Collection; -//import java.util.List; -//import java.util.Set; -// -//import static org.testng.AssertJUnit.assertEquals; -// -///** -// * Various testcases for Router implementations -// * -// * @author Nils Sowen -// */ -//public class TestRouter { -// -// @Test -// public void testWeightedRoundRobin() throws Exception { -// -// Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobin-config.xml"); -// WeightedRoundRobinRouter router = new WeightedRoundRobinRouter(new RealmTableTest(), config); -// -// IStatisticManager manager = new StatisticManagerImpl(config); -// PeerTest p1 = new PeerTest(1, 1, true, manager); -// PeerTest p2 = new PeerTest(2, 1, true, manager); -// PeerTest p3 = new PeerTest(3, 1, true, manager); -// PeerTest p4 = new PeerTest(4, 1, true, manager); -// -// List peers = new ArrayList(3); -// peers.add(p1); -// peers.add(p2); -// peers.add(p3); -// -// // Test simple round robin (all weight = 1) -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// -// // Test weighted round robin (p1=2, p2=1, p3=1) -// p1.setRating(2); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// -// // Test weighted round robin (p1=2, p2=2, p3=1) -// p2.setRating(2); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// -// // Test equally weighted round robin (p1=2, p2=2, p3=2) -// p3.setRating(2); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// -// // Add Peer-4 with weight 1 to list -// peers.add(p4); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// // expected glitch here: due to the sudden availibity of Peer-4, the algorithm is disturbed -// assertEquals(p4.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// assertEquals(p4.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// assertEquals(p4.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p3.toString(), router.selectPeer(peers).toString()); -// -// // Next cycle would produce Peer-4, but reduce peer list now -// peers = peers.subList(0,2); // now: Peer-1, Peer-2 -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// -// } -// -// @Test -// public void testWeightedLeastConnections() throws Exception { -// -// Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedleastconnections-config.xml"); -// WeightedLeastConnectionsRouter router = new WeightedLeastConnectionsRouter(new RealmTableTest(), config); -// -// IStatisticManager manager = new StatisticManagerImpl(config); -// PeerTest p1 = new PeerTest(1, 1, true, manager); -// PeerTest p2 = new PeerTest(2, 1, true, manager); -// PeerTest p3 = new PeerTest(3, 1, true, manager); -// -// List peers = new ArrayList(2); -// peers.add(p1); -// peers.add(p2); -// -// // Test simple round robin (all weight = 1) -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// -// // increase p1 requests/s by 1 -// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// -// // increase p2 requests/s by 1 -// p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// -// // decrease p1 requests/s by 1 -// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenResponsePerSecond.name()+'.'+p1.getUri()).dec(); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// -// // increase p1 requests/s by 3 -// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); -// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); -// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// -// // increase p2 requests/s by 1 -// p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// -// // increase weight of p1 -// p1.setRating(2); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// -// // decrease p1 requests/s by 1 -// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).dec(); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// -// // increase p1 requests/s by 2 -// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); -// p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// -// // increase weight and requests/s of p2 -// p2.setRating(2); -// p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); -// p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// assertEquals(p2.toString(), router.selectPeer(peers).toString()); -// -// // increase p2 requests/s by 1 -// p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// assertEquals(p1.toString(), router.selectPeer(peers).toString()); -// -// } -// -// private static class RealmTableTest implements IRealmTable { -// -// public Realm matchRealm(IRequest request) { -// return null; -// } -// -// public Realm matchRealm(IAnswer message, String destRealm) { -// return null; -// } -// -// public Realm getRealm(String realmName, ApplicationId applicationId) { -// return null; -// } -// -// public Realm removeRealmApplicationId(String realmName, ApplicationId appId) { -// return null; -// } -// -// public Collection removeRealm(String realmName) { -// return null; -// } -// -// public Collection getRealms(String realm) { -// return null; -// } -// -// public Collection getRealms() { -// return null; -// } -// -// public String getRealmForPeer(String fqdn) { -// return null; -// } -// -// public void addLocalApplicationId(ApplicationId ap) { -// -// } -// -// public void removeLocalApplicationId(ApplicationId a) { -// -// } -// -// public void addLocalRealm(String localRealm, String fqdn) { -// -// } -// -// public Realm addRealm(String name, ApplicationId appId, LocalAction locAction, IAgentConfiguration agentConfImpl, boolean isDynamic, long expirationTime, String[] hosts) throws InternalException { -// return null; -// } -// -// public Statistic getStatistic(String realmName) { -// return null; -// } -// -// public Realm addRealm(String realmName, ApplicationId applicationId, LocalAction action, String agentConfiguration, boolean dynamic, long expirationTime, String[] hosts) throws InternalException { -// return null; -// } -// -// public boolean realmExists(String realmName) { -// return false; -// } -// -// public boolean isWrapperFor(Class iface) throws InternalException { -// return false; -// } -// -// public T unwrap(Class iface) throws InternalException { -// return null; -// } -// -// public List getAllRealmSet(){ -// return null; -// } -// } -// -// private static class PeerTest extends AbstractPeer implements IPeer { -// -// private int id; -// private int rating; -// private boolean connected; -// -// public PeerTest(int id, int rating, boolean connected, IStatisticManager manager) throws URISyntaxException, UnknownServiceException { -// super(new URI("aaa://"+id), manager); -// this.id = id; -// this.rating = rating; -// this.connected = connected; -// createPeerStatistics(); -// } -// -// public void setRating(int rating) { -// this.rating = rating; -// } -// -// public int getRating() { -// return rating; -// } -// -// public long getHopByHopIdentifier() { -// return 0; -// } -// -// public void addMessage(IMessage message) { -// -// } -// -// public void remMessage(IMessage message) { -// -// } -// -// public IMessage[] remAllMessage() { -// return new IMessage[0]; -// } -// -// public boolean handleMessage(EventTypes type, IMessage message, String key) throws TransportException, OverloadException, InternalException { -// return false; -// } -// -// public boolean sendMessage(IMessage message) throws TransportException, OverloadException, InternalException { -// return false; -// } -// -// public boolean hasValidConnection() { -// return connected; -// } -// -// public void setRealm(String realm) { -// -// } -// -// public void addStateChangeListener(StateChangeListener listener) { -// -// } -// -// public void remStateChangeListener(StateChangeListener listener) { -// -// } -// -// public void addConnectionListener(IConnectionListener listener) { -// -// } -// -// public void remConnectionListener(IConnectionListener listener) { -// -// } -// -// public IStatistic getStatistic() { -// return statistic; -// } -// -// public boolean isConnected() { -// return connected; -// } -// -// public void connect() throws InternalException, IOException, IllegalDiameterStateException { -// -// } -// -// @Override -// public void disconnect(int disconnectCause) throws InternalException, IllegalDiameterStateException { -// -// } -// -// public E getState(Class enumc) { -// return null; -// } -// -// public URI getUri() { -// return uri; -// } -// -// public InetAddress[] getIPAddresses() { -// return new InetAddress[0]; -// } -// -// public String getRealmName() { -// return null; -// } -// -// public long getVendorId() { -// return 0; -// } -// -// public String getProductName() { -// return null; -// } -// -// public long getFirmware() { -// return 0; -// } -// -// public Set getCommonApplications() { -// return null; -// } -// -// public void addPeerStateListener(PeerStateListener listener) { -// -// } -// -// public void removePeerStateListener(PeerStateListener listener) { -// -// } -// -// @Override -// public String toString() { -// return "Peer-"+id; -// } -// } -// -//} \ No newline at end of file +/* + * TeleStax, Open Source Cloud Communications + * Copyright 2011-2016, Telestax Inc and individual contributors + * by the @authors tag. + * + * This program is free software: you can redistribute it and/or modify + * under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + */ + +package org.jdiameter.client.impl.router; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.URISyntaxException; +import java.net.UnknownServiceException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.jdiameter.api.ApplicationId; +import org.jdiameter.api.Avp; +import org.jdiameter.api.AvpDataException; +import org.jdiameter.api.Configuration; +import org.jdiameter.api.IllegalDiameterStateException; +import org.jdiameter.api.InternalException; +import org.jdiameter.api.LocalAction; +import org.jdiameter.api.OverloadException; +import org.jdiameter.api.PeerStateListener; +import org.jdiameter.api.Realm; +import org.jdiameter.api.Statistic; +import org.jdiameter.api.URI; +import org.jdiameter.api.app.StateChangeListener; +import org.jdiameter.client.api.IAnswer; +import org.jdiameter.client.api.IMessage; +import org.jdiameter.client.api.IRequest; +import org.jdiameter.client.api.controller.IPeer; +import org.jdiameter.client.api.controller.IRealmTable; +import org.jdiameter.client.api.fsm.EventTypes; +import org.jdiameter.client.api.io.IConnectionListener; +import org.jdiameter.client.api.io.TransportException; +import org.jdiameter.client.impl.helpers.UIDGenerator; +import org.jdiameter.client.impl.helpers.XMLConfiguration; +import org.jdiameter.client.impl.parser.MessageParser; +import org.jdiameter.common.api.statistic.IStatistic; +import org.jdiameter.common.api.statistic.IStatisticManager; +import org.jdiameter.common.api.statistic.IStatisticRecord; +import org.jdiameter.common.impl.controller.AbstractPeer; +import org.jdiameter.common.impl.statistic.StatisticManagerImpl; +import org.jdiameter.server.api.agent.IAgentConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import junit.framework.TestCase; + +/** + * Various testcases for Router implementations + * + * @author Nils Sowen + */ +public class TestRouter extends TestCase { + private static final Logger logger = LoggerFactory.getLogger(TestRouter.class); + private static UIDGenerator uid = new UIDGenerator(); + + @Test + public void testWeightedRoundRobin() throws Exception { + + Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobin-config.xml"); + WeightedRoundRobinRouter router = new WeightedRoundRobinRouter(new RealmTableTest(), config); + + IStatisticManager manager = new StatisticManagerImpl(config); + PeerTest p1 = new PeerTest(1, 1, true, manager); + PeerTest p2 = new PeerTest(2, 1, true, manager); + PeerTest p3 = new PeerTest(3, 1, true, manager); + PeerTest p4 = new PeerTest(4, 1, true, manager); + + List peers = new ArrayList(3); + peers.add(p1); + peers.add(p2); + peers.add(p3); + + // Test simple round robin (all weight = 1) + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + + // Test weighted round robin (p1=2, p2=1, p3=1) + p1.setRating(2); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + + // Test weighted round robin (p1=2, p2=2, p3=1) + p2.setRating(2); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + + // Test equally weighted round robin (p1=2, p2=2, p3=2) + p3.setRating(2); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + + // Add Peer-4 with weight 1 to list + peers.add(p4); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + // expected glitch here: due to the sudden availibity of Peer-4, the algorithm is disturbed + assertEquals(p4.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + assertEquals(p4.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + assertEquals(p4.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p3.toString(), router.selectPeer(peers).toString()); + + // Next cycle would produce Peer-4, but reduce peer list now + peers = peers.subList(0,2); // now: Peer-1, Peer-2 + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + + } + + @Test + public void testWeightedRoundRobinResubmittingOnBusyOrUnableToDeliverAnswer() throws Exception { + + Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml"); + WeightedRoundRobinResubmittingRouter router = new WeightedRoundRobinResubmittingRouter(new RealmTableTest(), config); + + IStatisticManager manager = new StatisticManagerImpl(config); + PeerTest p1 = new PeerTest(1, 1, true, manager); + PeerTest p2 = new PeerTest(2, 2, true, manager); + PeerTest p3 = new PeerTest(3, 3, true, manager); + PeerTest p4 = new PeerTest(4, 4, true, manager); + + List peers = new ArrayList(3); + peers.add(p1); + peers.add(p2); + peers.add(p3); + peers.add(p4); + + // Create any message + MessageParser messageParser = new MessageParser(); + IMessage request = messageParser.createEmptyMessage(123, 3l); + request.getAvps().addAvp(Avp.CC_REQUEST_NUMBER, 0); + request.setRequest(true); + request.getAvps().addAvp(Avp.SESSION_ID, getSessionId(), true, false, false); + + // Test weighted round robin on a single, resubmitted request + assertEquals(p4.toString(), router.selectPeer(request, peers).toString()); + emulateResubmission(p4, request); + assertEquals(p3.toString(), router.selectPeer(request, peers).toString()); + emulateResubmission(p3, request); + assertEquals(p2.toString(), router.selectPeer(request, peers).toString()); + emulateResubmission(p2, request); + assertEquals(p1.toString(), router.selectPeer(request, peers).toString()); + } + + private void emulateResubmission(PeerTest peer, IMessage request) throws AvpDataException { + request.setPeer(peer); + incrementRequestNumber(request); + } + + private void incrementRequestNumber(IMessage request) throws AvpDataException { + int requestNumber = request.getAvps().getAvp(Avp.CC_REQUEST_NUMBER).getInteger32(); + requestNumber++; + logger.trace("Incremented requestNumber to [{}] ", requestNumber); + request.getAvps().removeAvp(Avp.CC_REQUEST_NUMBER); + request.getAvps().addAvp(Avp.CC_REQUEST_NUMBER, requestNumber); + } + + private String getSessionId() { + long id = uid.nextLong(); + long high32 = (id & 0xffffffff00000000L) >> 32; + long low32 = (id & 0xffffffffL); + StringBuilder sb = new StringBuilder(); + sb.append("localhost"). + append(";").append(high32).append(";").append(low32); + return sb.toString(); + } + + @Test + public void testWeightedLeastConnections() throws Exception { + + Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedleastconnections-config.xml"); + WeightedLeastConnectionsRouter router = new WeightedLeastConnectionsRouter(new RealmTableTest(), config); + + IStatisticManager manager = new StatisticManagerImpl(config); + PeerTest p1 = new PeerTest(1, 1, true, manager); + PeerTest p2 = new PeerTest(2, 1, true, manager); + PeerTest p3 = new PeerTest(3, 1, true, manager); + + List peers = new ArrayList(2); + peers.add(p1); + peers.add(p2); + + // Test simple round robin (all weight = 1) + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + + // increase p1 requests/s by 1 + p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + + // increase p2 requests/s by 1 + p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + + // decrease p1 requests/s by 1 + p1.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenResponsePerSecond.name()+'.'+p1.getUri()).dec(); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + + // increase p1 requests/s by 3 + p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); + p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); + p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + + // increase p2 requests/s by 1 + p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + + // increase weight of p1 + p1.setRating(2); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + + // decrease p1 requests/s by 1 + p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).dec(); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + + // increase p1 requests/s by 2 + p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); + p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc(); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + + // increase weight and requests/s of p2 + p2.setRating(2); + p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); + p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + assertEquals(p2.toString(), router.selectPeer(peers).toString()); + + // increase p2 requests/s by 1 + p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc(); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + assertEquals(p1.toString(), router.selectPeer(peers).toString()); + + } + + private static class RealmTableTest implements IRealmTable { + + public Realm matchRealm(IRequest request) { + return null; + } + + public Realm matchRealm(IAnswer message, String destRealm) { + return null; + } + + public Realm getRealm(String realmName, ApplicationId applicationId) { + return null; + } + + public Realm removeRealmApplicationId(String realmName, ApplicationId appId) { + return null; + } + + public Collection removeRealm(String realmName) { + return null; + } + + public Collection getRealms(String realm) { + return null; + } + + public Collection getRealms() { + return null; + } + + public String getRealmForPeer(String fqdn) { + return null; + } + + public void addLocalApplicationId(ApplicationId ap) { + + } + + public void removeLocalApplicationId(ApplicationId a) { + + } + + public void addLocalRealm(String localRealm, String fqdn) { + + } + + public Realm addRealm(String name, ApplicationId appId, LocalAction locAction, IAgentConfiguration agentConfImpl, boolean isDynamic, long expirationTime, String[] hosts) throws InternalException { + return null; + } + + public Statistic getStatistic(String realmName) { + return null; + } + + public Realm addRealm(String realmName, ApplicationId applicationId, LocalAction action, String agentConfiguration, boolean dynamic, long expirationTime, String[] hosts) throws InternalException { + return null; + } + + public boolean realmExists(String realmName) { + return false; + } + + public boolean isWrapperFor(Class iface) throws InternalException { + return false; + } + + public T unwrap(Class iface) throws InternalException { + return null; + } + + public List getAllRealmSet(){ + return null; + } + } + + private static class PeerTest extends AbstractPeer implements IPeer { + + private int id; + private int rating; + private boolean connected; + + public PeerTest(int id, int rating, boolean connected, IStatisticManager manager) throws URISyntaxException, UnknownServiceException { + super(new URI("aaa://"+id), manager); + this.id = id; + this.rating = rating; + this.connected = connected; + createPeerStatistics(); + } + + public void setRating(int rating) { + this.rating = rating; + } + + public int getRating() { + return rating; + } + + public long getHopByHopIdentifier() { + return 0; + } + + public void addMessage(IMessage message) { + + } + + public void remMessage(IMessage message) { + + } + + public IMessage[] remAllMessage() { + return new IMessage[0]; + } + + public boolean handleMessage(EventTypes type, IMessage message, String key) throws TransportException, OverloadException, InternalException { + return false; + } + + public boolean sendMessage(IMessage message) throws TransportException, OverloadException, InternalException { + return false; + } + + public boolean hasValidConnection() { + return connected; + } + + public void setRealm(String realm) { + + } + + public void addStateChangeListener(StateChangeListener listener) { + + } + + public void remStateChangeListener(StateChangeListener listener) { + + } + + public void addConnectionListener(IConnectionListener listener) { + + } + + public void remConnectionListener(IConnectionListener listener) { + + } + + public IStatistic getStatistic() { + return statistic; + } + + public boolean isConnected() { + return connected; + } + + public void connect() throws InternalException, IOException, IllegalDiameterStateException { + + } + + @Override + public void disconnect(int disconnectCause) throws InternalException, IllegalDiameterStateException { + + } + + public E getState(Class enumc) { + return null; + } + + public URI getUri() { + return uri; + } + + public InetAddress[] getIPAddresses() { + return new InetAddress[0]; + } + + public String getRealmName() { + return null; + } + + public long getVendorId() { + return 0; + } + + public String getProductName() { + return null; + } + + public long getFirmware() { + return 0; + } + + public Set getCommonApplications() { + return null; + } + + public void addPeerStateListener(PeerStateListener listener) { + + } + + public void removePeerStateListener(PeerStateListener listener) { + + } + + @Override + public String toString() { + return "Peer-"+id; + } + } + +} \ No newline at end of file diff --git a/core/jdiameter/impl/src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml b/core/jdiameter/impl/src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml new file mode 100644 index 000000000..c908eb331 --- /dev/null +++ b/core/jdiameter/impl/src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/jdiameter/impl/src/test/resources/log4j.properties b/core/jdiameter/impl/src/test/resources/log4j.properties new file mode 100644 index 000000000..24d12894e --- /dev/null +++ b/core/jdiameter/impl/src/test/resources/log4j.properties @@ -0,0 +1,9 @@ +# Set root logger level to DEBUG and its only appender to A1. +log4j.rootLogger=TRACE, A1 + +# A1 is set to be a ConsoleAppender. +log4j.appender.A1=org.apache.log4j.ConsoleAppender + +# A1 uses PatternLayout. +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n From 494866c4c42ceb78fac718abc399735524ce3e22 Mon Sep 17 00:00:00 2001 From: Steve Dwyer Date: Wed, 8 Apr 2020 12:48:50 +0100 Subject: [PATCH 3/7] New class WeightedRoundRobinResubmittingRouter added to preserve exisiting behaviour in WeightedRoundRobinRouter class. Tests added to validate WeightedRoundRobinResubmittingRouter selects previously unattempted peers for a resubmission of an existing message, based on existing weighted round robin algorithm. --- .../client/impl/controller/PeerImpl.java | 9 - .../WeightedRoundRobinResubmittingRouter.java | 219 +++++++++---- .../client/impl/router/TestRouter.java | 309 ++++++++++++++---- 3 files changed, 398 insertions(+), 139 deletions(-) diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java index 94b3baf61..e04c296e3 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java @@ -493,7 +493,6 @@ private IMessage processRedirectAnswer(IMessage request, IMessage answer) { private IMessage processBusyOrUnableToDeliverAnswer(IMessage request, IMessage answer) { try { - incrementRequestNumber(request); router.processBusyOrUnableToDeliverAnswer(request, table); return null; } @@ -507,14 +506,6 @@ private IMessage processBusyOrUnableToDeliverAnswer(IMessage request, IMessage a return answer; } - private void incrementRequestNumber(IMessage request) throws AvpDataException { - int requestNumber = request.getAvps().getAvp(Avp.CC_REQUEST_NUMBER).getInteger32(); - requestNumber++; - logger.trace("Incremented requestNumber to [{}] ", requestNumber); - request.getAvps().removeAvp(Avp.CC_REQUEST_NUMBER); - request.getAvps().addAvp(Avp.CC_REQUEST_NUMBER, requestNumber); - } - @Override public void connect() throws InternalException, IOException, IllegalDiameterStateException { if (getState(PeerState.class) != PeerState.DOWN) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java index 81204bef5..368e8d8ad 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java @@ -19,8 +19,6 @@ package org.jdiameter.client.impl.router; -import static org.jdiameter.api.Avp.CC_REQUEST_NUMBER; - import java.util.HashSet; import java.util.List; import java.util.Map; @@ -29,7 +27,6 @@ import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; -import org.jdiameter.api.AvpDataException; import org.jdiameter.api.Configuration; import org.jdiameter.api.MetaData; import org.jdiameter.api.PeerState; @@ -47,23 +44,25 @@ * * @author Nils Sowen * @see - * http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling + * http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling */ public class WeightedRoundRobinResubmittingRouter extends RouterImpl implements IRouter { private static final Logger logger = LoggerFactory.getLogger(WeightedRoundRobinResubmittingRouter.class); + private static final int ATTEMPTED_PEER_RETENTION_PERIOD_MS = 30000; + private int lastSelectedPeer = -1; private int currentWeight = 0; - private Map> attemptedPeers = new ConcurrentHashMap>(); - private int timeout = 30000; + private Map> attemptedPeers = new ConcurrentHashMap>(); protected WeightedRoundRobinResubmittingRouter(IRealmTable table, Configuration config) { super(null, null, table, config, null); } - public WeightedRoundRobinResubmittingRouter(IContainer container, IConcurrentFactory concurrentFactory, - IRealmTable realmTable, Configuration config, MetaData aMetaData) { + public WeightedRoundRobinResubmittingRouter(IContainer container, IConcurrentFactory concurrentFactory, IRealmTable realmTable, Configuration config, + MetaData aMetaData) { super(container, concurrentFactory, realmTable, config, aMetaData); } @@ -87,6 +86,7 @@ public WeightedRoundRobinResubmittingRouter(IContainer container, IConcurrentFac * max(S) is the maximum weight of all the servers in S; * gcd(S) is the greatest common divisor of all server weights in S; *

+ * *

    * {@code
    *   while (true) {
@@ -129,10 +129,12 @@ public WeightedRoundRobinResubmittingRouter(IContainer container, IConcurrentFac
    * the balancing algorithm is disturbed and might be distributed uneven.
    * This is likely to happen if peers are flapping.
    *
-   * @param availablePeers list of peers that are in {@link PeerState#OKAY OKAY} state
+   * @param availablePeers
+   *          list of peers that are in {@link PeerState#OKAY OKAY} state
    * @return the selected peer according to algorithm
    * @see
-   * http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling
+   *      http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling
    */
   @Override
   public IPeer selectPeer(List availablePeers) {
@@ -146,10 +148,12 @@ public IPeer selectPeer(List availablePeers) {
    * the same peer that responded with the Busy or Unable To Deliver Answer is not selected for
    * any subsequent submissions of the same request.
    *
-   * @param message The message to be re-attempted due to a Busy or Unable To Deliver Answer
-   * @param availablePeers list of peers that are in {@link PeerState#OKAY OKAY} state
+   * @param message
+   *          The message to be re-attempted due to a Busy or Unable To Deliver Answer
+   * @param availablePeers
+   *          list of peers that are in {@link PeerState#OKAY OKAY} state
    * @return the selected peer according to algorithm, ensuring that if the message is passed, that
-   * the same peer that responded with the Busy or Unable To Deliver Answer is not selected a second time
+   *         the same peer that responded with the Busy or Unable To Deliver Answer is not selected a second time
    *
    */
   @Override
@@ -157,28 +161,29 @@ public IPeer selectPeer(IMessage message, List availablePeers) {
     IPeer selectedPeer = null;
     int peerSize = availablePeers != null ? availablePeers.size() : 0;
 
-    logger.debug("peerSize " + peerSize);
     // Return none if empty, or first if only one member found
     if (peerSize <= 0) {
       return null;
     }
 
-    if (message != null) {
-      if (message.getPeer() != null) {
-        addAttemptedPeer(message, message.getPeer());
-      }
+    if (message != null && message.getPeer() != null) {
+      addAttemptedPeer(message, message.getPeer());
 
-      long requestNumber = 0;
-      try {
-        requestNumber = message.getAvps().getAvp(CC_REQUEST_NUMBER).getUnsigned32();
-        logger.debug("Selecting subsequent peer for [sessionId={}, requestNumber={}]", message.getSessionId(), requestNumber);
+      long numberOfAttempts = 0;
+      MessageKey messageKey = getMessageKey(message);
+      Set peerSet = attemptedPeers.get(messageKey);
+      if (peerSet != null) {
+        numberOfAttempts = peerSet.size();
       }
-      catch (AvpDataException e) {
-        e.printStackTrace();
+      if (logger.isTraceEnabled()) {
+        logger.trace("Selecting subsequent peer for {} [numberOfAttempts={}]", messageKey, numberOfAttempts);
       }
 
-      if (requestNumber == peerSize) {
-        logger.debug("All peers exhausted for message [sessionId={}], giving up...", message.getSessionId());
+      if (numberOfAttempts == peerSize) {
+        if (logger.isDebugEnabled()) {
+          logger.debug("All peers exhausted for {}, giving up...", messageKey);
+        }
+        removeAttemptedPeers(messageKey);
         return null;
       }
     }
@@ -193,12 +198,11 @@ public IPeer selectPeer(IMessage message, List availablePeers) {
     for (IPeer peer : availablePeers) {
       maxWeight = Math.max(maxWeight, peer.getRating());
       gcd = (gcd == null) ? peer.getRating() : gcd(gcd, peer.getRating());
-      logger.debug("Peer [uri={}, realmName={}, rating={}]", peer.getUri(), peer.getRealmName(), peer.getRating());
     }
 
     // Find best matching candidate. Synchronized here due to consistent changes on member variables
     synchronized (this) {
-      for (; ; ) {
+      for (;;) {
         lastSelectedPeer = (lastSelectedPeer + 1) % peerSize;
         if (lastSelectedPeer == 0) {
           currentWeight = currentWeight - gcd;
@@ -212,77 +216,85 @@ public IPeer selectPeer(IMessage message, List availablePeers) {
         }
         IPeer candidate = availablePeers.get(lastSelectedPeer);
         if (candidate.getRating() >= currentWeight) {
+          if (message != null && message.getPeer() != null) {
+            if (isPeerPreviouslyAttempted(lastSelectedPeer, availablePeers, message)) {
+              continue;
+            }
+          }
+
           selectedPeer = availablePeers.get(lastSelectedPeer);
-          if (message != null) {
-            if (message.getPeer() != null) {
-              logger.debug("Moving selected Peer [uri={}, realmName={}, rating={}] to next peer as it looks like to be a subsequent attempt of this message and last peer of this message", message.getPeer().getUri(), message.getPeer().getRealmName(), message.getPeer().getRating());
-              lastSelectedPeer = selectPeerForSubsequentSubmission(lastSelectedPeer, availablePeers, message);
-              if (lastSelectedPeer < 0) {
-                logger.debug("No unattempted peers left for message [sessionId={}], giving up...", message.getSessionId());
-                return null;
-              }
-              else {
-                selectedPeer = availablePeers.get(lastSelectedPeer);
-              }
-            } else logger.trace("message.getPeer() == null");
-          } else logger.trace("message != null");
-          logger.trace("Selected Peer [uri={}, realmName={}, rating={}]", selectedPeer.getUri(), selectedPeer.getRealmName(), selectedPeer.getRating());
+
+          if (logger.isTraceEnabled()) {
+            logger.trace("Selected Peer [uri={}, realmName={}, rating={}]", selectedPeer.getUri(), selectedPeer.getRealmName(), selectedPeer.getRating());
+          }
           return selectedPeer;
         }
       }
     }
   }
 
-  private int selectPeerForSubsequentSubmission(int selectedPeerIndex, List availablePeers, IMessage message) {
-
-    logger.debug("Checking peer history of message [sessionId={}] ", message.getSessionId());
+  private MessageKey getMessageKey(final IMessage message) {
+    return new MessageKey(message.getSessionId(), message.getEndToEndIdentifier());
+  }
 
-    if (!attemptedPeers.containsKey(message.getSessionId())) {
-      logger.debug("attemptedPeers does not contain selected peer so return it");
-      return selectedPeerIndex;
+  private boolean isPeerPreviouslyAttempted(int selectedPeerIndex, List availablePeers, IMessage message) {
+    boolean isPeerPreviouslyAttempted = false;
+    final MessageKey messageKey = getMessageKey(message);
+    if (logger.isTraceEnabled()) {
+      logger.trace("Checking whether selected Peer [id={}] has already had {} sent to it", selectedPeerIndex, messageKey);
     }
-    for (int i = 0; i < availablePeers.size(); i++) {
+
+    if (attemptedPeers.containsKey(messageKey)) {
       IPeer candidate = availablePeers.get(selectedPeerIndex);
-      if (attemptedPeers.get(message.getSessionId()).contains(candidate)) {
-        logger.debug("Peer [{}] has been tried before, skipping to next peer", candidate.getUri());
-        selectedPeerIndex = selectedPeerIndex < availablePeers.size() - 1 ? selectedPeerIndex + 1 : 0;
-        continue;
-      } else {
-        logger.debug("Peer [{}] hasn't been tried for message [sessionId={}]", candidate.getUri(), message.getSessionId());
-        return selectedPeerIndex;
+      if (attemptedPeers.get(messageKey).contains(candidate)) {
+        if (logger.isTraceEnabled()) {
+          logger.trace("Peer [uri={}, realmName={}, rating={}] has been tried before, try next peer", candidate.getUri(), candidate.getRealmName(),
+              candidate.getRating());
+        }
+        isPeerPreviouslyAttempted = true;
       }
     }
-    logger.debug("All peers have been tried, returning -1");
-    attemptedPeers.remove(message.getSessionId());
-    return -1;
+
+    return isPeerPreviouslyAttempted;
   }
 
   private synchronized void addAttemptedPeer(final IMessage message, IPeer peer) {
-    if (attemptedPeers.containsKey(message.getSessionId())) {
-      attemptedPeers.get(message.getSessionId()).add(peer);
-    } else {
+    final MessageKey messageKey = getMessageKey(message);
+    if (attemptedPeers.containsKey(messageKey)) {
+      attemptedPeers.get(messageKey).add(peer);
+    }
+    else {
       Set peerSet = new HashSet();
       peerSet.add(peer);
-      attemptedPeers.put(message.getSessionId(), peerSet);
+      attemptedPeers.put(messageKey, peerSet);
 
       new Timer().schedule(new TimerTask() {
         @Override
         public void run() {
-          removeAttemptedPeers(message.getSessionId());
+          removeAttemptedPeers(messageKey);
         }
-      }, timeout);
+      }, ATTEMPTED_PEER_RETENTION_PERIOD_MS);
     }
   }
 
-  void removeAttemptedPeers(String sessionId) {
-    logger.debug("Removing attemptedPeers for [sessionId={}] (currently [attemptedPeers.size()={}])  ", sessionId, attemptedPeers.size());
-    Set peerSet = attemptedPeers.remove(sessionId);
+  private void removeAttemptedPeers(MessageKey messageKey) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Removing attemptedPeers for {} (currently [attemptedPeers.size()={}])  ", messageKey, attemptedPeers.size());
+    }
+    Set peerSet = attemptedPeers.remove(messageKey);
     if (peerSet != null) {
-      logger.trace("peerSet with [size={}] has been removed for message [sessionId={}]" + peerSet.size(), sessionId);
-    } else {
-      logger.warn("No peers removed from attemptedPeers for [sessionId={}]!", sessionId);
+      if (logger.isTraceEnabled()) {
+        logger.trace("peerSet with [size={}] has been removed for {}", peerSet.size(), messageKey);
+      }
+    }
+    else {
+      if (logger.isWarnEnabled()) {
+        logger.warn("No peers removed from attemptedPeers for {}!", messageKey);
+      }
+    }
+    if (logger.isDebugEnabled()) {
+      logger.debug("Done removing attemptedPeers for {} (now [attemptedPeers.size()={}])  ", messageKey, attemptedPeers.size());
     }
-    logger.debug("Done removing attemptedPeers for [sessionId={}] (now [attemptedPeers.size()={}])  ", sessionId, attemptedPeers.size());
   }
 
   /**
@@ -296,4 +308,67 @@ void removeAttemptedPeers(String sessionId) {
   protected int gcd(int a, int b) {
     return (b == 0) ? a : gcd(b, a % b);
   }
+
+  /**
+   * Defines a class which can be used to uniquely define any single message within any given session.
+   *
+   */
+  private class MessageKey {
+    private String sessionId;
+    private long endToEndId;
+
+    MessageKey(String sessionId, long endToEndId) {
+      super();
+      this.sessionId = sessionId;
+      this.endToEndId = endToEndId;
+    }
+
+    @Override
+    public String toString() {
+      return "MessageKey [sessionId=" + sessionId + ", endToEndId=" + endToEndId + "]";
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + getOuterType().hashCode();
+      result = prime * result + (int) (endToEndId ^ (endToEndId >>> 32));
+      result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode());
+      return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null) {
+        return false;
+      }
+      if (getClass() != obj.getClass()) {
+        return false;
+      }
+      MessageKey other = (MessageKey) obj;
+      if (!getOuterType().equals(other.getOuterType())) {
+        return false;
+      }
+      if (endToEndId != other.endToEndId) {
+        return false;
+      }
+      if (sessionId == null) {
+        if (other.sessionId != null) {
+          return false;
+        }
+      }
+      else if (!sessionId.equals(other.sessionId)) {
+        return false;
+      }
+      return true;
+    }
+
+    private WeightedRoundRobinResubmittingRouter getOuterType() {
+      return WeightedRoundRobinResubmittingRouter.this;
+    }
+  }
 }
diff --git a/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java b/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java
index be3392baf..9d1b124d7 100644
--- a/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java
+++ b/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java
@@ -30,7 +30,6 @@
 
 import org.jdiameter.api.ApplicationId;
 import org.jdiameter.api.Avp;
-import org.jdiameter.api.AvpDataException;
 import org.jdiameter.api.Configuration;
 import org.jdiameter.api.IllegalDiameterStateException;
 import org.jdiameter.api.InternalException;
@@ -173,63 +172,257 @@ public void testWeightedRoundRobin() throws Exception {
 
     }
 
-    @Test
-    public void testWeightedRoundRobinResubmittingOnBusyOrUnableToDeliverAnswer() throws Exception {
-
-      Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml");
-      WeightedRoundRobinResubmittingRouter router = new WeightedRoundRobinResubmittingRouter(new RealmTableTest(), config);
-
-      IStatisticManager manager = new StatisticManagerImpl(config);
-      PeerTest p1 = new PeerTest(1, 1, true, manager);
-      PeerTest p2 = new PeerTest(2, 2, true, manager);
-      PeerTest p3 = new PeerTest(3, 3, true, manager);
-      PeerTest p4 = new PeerTest(4, 4, true, manager);
-
-      List peers = new ArrayList(3);
-      peers.add(p1);
-      peers.add(p2);
-      peers.add(p3);
-      peers.add(p4);
-
-      // Create any message
-      MessageParser messageParser = new MessageParser();
-      IMessage request = messageParser.createEmptyMessage(123, 3l);
-      request.getAvps().addAvp(Avp.CC_REQUEST_NUMBER, 0);
-      request.setRequest(true);
-      request.getAvps().addAvp(Avp.SESSION_ID, getSessionId(), true, false, false);
-
-      // Test weighted round robin on a single, resubmitted request
-      assertEquals(p4.toString(), router.selectPeer(request, peers).toString());
-      emulateResubmission(p4, request);
-      assertEquals(p3.toString(), router.selectPeer(request, peers).toString());
-      emulateResubmission(p3, request);
-      assertEquals(p2.toString(), router.selectPeer(request, peers).toString());
-      emulateResubmission(p2, request);
-      assertEquals(p1.toString(), router.selectPeer(request, peers).toString());
-    }
-
-    private void emulateResubmission(PeerTest peer, IMessage request) throws AvpDataException {
-      request.setPeer(peer);
-      incrementRequestNumber(request);
-    }
-
-    private void incrementRequestNumber(IMessage request) throws AvpDataException {
-      int requestNumber = request.getAvps().getAvp(Avp.CC_REQUEST_NUMBER).getInteger32();
-      requestNumber++;
-      logger.trace("Incremented requestNumber to [{}] ", requestNumber);
-      request.getAvps().removeAvp(Avp.CC_REQUEST_NUMBER);
-      request.getAvps().addAvp(Avp.CC_REQUEST_NUMBER, requestNumber); 
-    }
-
-    private String getSessionId() {
-      long id = uid.nextLong();
-      long high32 = (id & 0xffffffff00000000L) >> 32;
-      long low32 = (id & 0xffffffffL);
-      StringBuilder sb = new StringBuilder();
-      sb.append("localhost").
-      append(";").append(high32).append(";").append(low32);
-      return sb.toString();  
-    }
+  /*
+   * WeightedRoundRobinResubmittingRouter should behave exactly as the WeightedRoundRobinRouter
+   * when no Busy or Unable to Deliver Answers are received and hence no re-submissions to alternative peers
+   * are required.
+   */
+  @Test
+  public void testWeightedRoundRobinResubmittingNoResubmissions() throws Exception {
+    logger.debug("*** Executing testWeightedRoundRobinResubmittingNoResubmissions ***");
+
+    Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml");
+    WeightedRoundRobinResubmittingRouter router = new WeightedRoundRobinResubmittingRouter(new RealmTableTest(), config);
+
+    IStatisticManager manager = new StatisticManagerImpl(config);
+    PeerTest p1 = new PeerTest(1, 1, true, manager);
+    PeerTest p2 = new PeerTest(2, 1, true, manager);
+    PeerTest p3 = new PeerTest(3, 1, true, manager);
+    PeerTest p4 = new PeerTest(4, 1, true, manager);
+
+    List peers = new ArrayList(3);
+    peers.add(p1);
+    peers.add(p2);
+    peers.add(p3);
+
+    // Test simple round robin (all weight = 1)
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+
+    // Test weighted round robin (p1=2, p2=1, p3=1)
+    p1.setRating(2);
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+
+    // Test weighted round robin (p1=2, p2=2, p3=1)
+    p2.setRating(2);
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+
+    // Test equally weighted round robin (p1=2, p2=2, p3=2)
+    p3.setRating(2);
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+
+    // Add Peer-4 with weight 1 to list
+    peers.add(p4);
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    // expected glitch here: due to the sudden availibity of Peer-4, the algorithm is disturbed
+    assertEquals(p4.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p4.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p4.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+
+    // Next cycle would produce Peer-4, but reduce peer list now
+    peers = peers.subList(0, 2); // now: Peer-1, Peer-2
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+
+    logger.debug("*** Execution of testWeightedRoundRobinResubmittingNoResubmissions completed ***");
+  }
+
+  /*
+   * Validates that, when a peer responds with a Busy or Unable to Deliver Answer, an alternative peer is
+   * selected to resubmit the Request to, based on the existing Round Robin weighting algorithm.
+   */
+  @Test
+  public void testWeightedRoundRobinResubmittingOnBusyOrUnableToDeliverAnswer() throws Exception {
+    logger.debug("*** Executing testWeightedRoundRobinResubmittingOnBusyOrUnableToDeliverAnswer ***");
+
+    Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml");
+    WeightedRoundRobinResubmittingRouter router = new WeightedRoundRobinResubmittingRouter(new RealmTableTest(), config);
+
+    IStatisticManager manager = new StatisticManagerImpl(config);
+    PeerTest p1 = new PeerTest(1, 1, true, manager);
+    PeerTest p2 = new PeerTest(2, 2, true, manager);
+    PeerTest p3 = new PeerTest(3, 3, true, manager);
+    PeerTest p4 = new PeerTest(4, 4, true, manager);
+
+    List peers = new ArrayList(3);
+    peers.add(p1);
+    peers.add(p2);
+    peers.add(p3);
+    peers.add(p4);
+
+    // Create any message
+    MessageParser messageParser = new MessageParser();
+    IMessage request = messageParser.createEmptyMessage(123, 3l);
+    request.setRequest(true);
+    request.getAvps().addAvp(Avp.SESSION_ID, getSessionId(), true, false, false);
+
+    // Test weighted round robin on a single, resubmitted request
+    assertEquals(p4.toString(), router.selectPeer(request, peers).toString());
+    request.setPeer(p4);
+    assertEquals(p3.toString(), router.selectPeer(request, peers).toString());
+    request.setPeer(p3);
+    assertEquals(p2.toString(), router.selectPeer(request, peers).toString());
+    request.setPeer(p2);
+    assertEquals(p1.toString(), router.selectPeer(request, peers).toString());
+
+    logger.debug("*** Execution of testWeightedRoundRobinResubmittingOnBusyOrUnableToDeliverAnswer completed ***");
+  }
+
+  /*
+   * Validates that, when all peers have been tried and each has responded with with a Busy or Unable to Deliver Answer,
+   * the router gives up trying to assign a peer.
+   */
+  @Test
+  public void testWeightedRoundRobinResubmittingPeersExhaused() throws Exception {
+    logger.debug("*** Executing testWeightedRoundRobinResubmittingPeersExhaused ***");
+
+    Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml");
+    WeightedRoundRobinResubmittingRouter router = new WeightedRoundRobinResubmittingRouter(new RealmTableTest(), config);
+
+    IStatisticManager manager = new StatisticManagerImpl(config);
+    PeerTest p1 = new PeerTest(1, 1, true, manager);
+    PeerTest p2 = new PeerTest(2, 2, true, manager);
+    PeerTest p3 = new PeerTest(3, 3, true, manager);
+    PeerTest p4 = new PeerTest(4, 4, true, manager);
+
+    List peers = new ArrayList(3);
+    peers.add(p1);
+    peers.add(p2);
+    peers.add(p3);
+    peers.add(p4);
+
+    // Create any message
+    MessageParser messageParser = new MessageParser();
+    IMessage request = messageParser.createEmptyMessage(123, 3l);
+    request.setRequest(true);
+    request.getAvps().addAvp(Avp.SESSION_ID, getSessionId(), true, false, false);
+
+    // Test weighted round robin on a single, resubmitted request
+    assertEquals(p4.toString(), router.selectPeer(request, peers).toString());
+    request.setPeer(p4);
+    assertEquals(p3.toString(), router.selectPeer(request, peers).toString());
+    request.setPeer(p3);
+    assertEquals(p2.toString(), router.selectPeer(request, peers).toString());
+    request.setPeer(p2);
+    assertEquals(p1.toString(), router.selectPeer(request, peers).toString());
+    request.setPeer(p1);
+    assertNull(router.selectPeer(request, peers));
+
+    logger.debug("*** Execution of testWeightedRoundRobinResubmittingPeersExhaused completed ***");
+  }
+
+
+  /*
+   * Validates that, when a peer responds with a Busy or Unable to Deliver Answer, an alternative peer is
+   * selected to resubmit the Request to, based on the existing Round Robin weighting algorithm, also after
+   * previous unrelated requests have moved the state of the round robin algorithm onwards from the initial state
+   * prior to submitting the (to be resubmitted) request.
+   */
+  @Test
+  public void testWeightedRoundRobinResubmittingOnBusyOrUnableToDeliverAnswerAfterPreviousMessages() throws Exception {
+    logger.debug("*** Executing testWeightedRoundRobinResubmittingOnBusyOrUnableToDeliverAnswerAfterPreviousMessages ***");
+
+    Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml");
+    WeightedRoundRobinResubmittingRouter router = new WeightedRoundRobinResubmittingRouter(new RealmTableTest(), config);
+
+    IStatisticManager manager = new StatisticManagerImpl(config);
+    PeerTest p1 = new PeerTest(1, 1, true, manager);
+    PeerTest p2 = new PeerTest(2, 2, true, manager);
+    PeerTest p3 = new PeerTest(3, 3, true, manager);
+    PeerTest p4 = new PeerTest(4, 4, true, manager);
+
+    List peers = new ArrayList(3);
+    peers.add(p1);
+    peers.add(p2);
+    peers.add(p3);
+    peers.add(p4);
+
+    assertEquals(p4.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p4.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p4.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+
+    // Create any message
+    MessageParser messageParser = new MessageParser();
+    IMessage request = messageParser.createEmptyMessage(123, 3l);
+    request.setRequest(true);
+    request.getAvps().addAvp(Avp.SESSION_ID, getSessionId(), true, false, false);
+
+    // Test weighted round robin on a single, resubmitted request
+    assertEquals(p2.toString(), router.selectPeer(request, peers).toString());
+    request.setPeer(p2);
+    assertEquals(p3.toString(), router.selectPeer(request, peers).toString());
+    request.setPeer(p3);
+    assertEquals(p4.toString(), router.selectPeer(request, peers).toString());
+    request.setPeer(p4);
+    assertEquals(p1.toString(), router.selectPeer(request, peers).toString());
+
+    logger.debug("*** Execution of testWeightedRoundRobinResubmittingOnBusyOrUnableToDeliverAnswerAfterPreviousMessages completed ***");
+  }
+
+  private String getSessionId() {
+    long id = uid.nextLong();
+    long high32 = (id & 0xffffffff00000000L) >> 32;
+    long low32 = (id & 0xffffffffL);
+    StringBuilder sb = new StringBuilder();
+    sb.append("localhost").append(";").append(high32).append(";").append(low32);
+    return sb.toString();
+  }
 
     @Test
     public void testWeightedLeastConnections() throws Exception {

From 62366277814da102187b71b88b91b9a61ac13144 Mon Sep 17 00:00:00 2001
From: Steve Dwyer 
Date: Wed, 8 Apr 2020 12:52:04 +0100
Subject: [PATCH 4/7] Code reformatted

---
 .../client/impl/router/TestRouter.java        | 643 +++++++++---------
 1 file changed, 322 insertions(+), 321 deletions(-)

diff --git a/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java b/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java
index 9d1b124d7..89d13f689 100644
--- a/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java
+++ b/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java
@@ -72,105 +72,105 @@ public class TestRouter extends TestCase {
   private static final Logger logger = LoggerFactory.getLogger(TestRouter.class);
   private static UIDGenerator uid = new UIDGenerator();
 
-    @Test
-    public void testWeightedRoundRobin() throws Exception {
-
-        Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobin-config.xml");
-        WeightedRoundRobinRouter router = new WeightedRoundRobinRouter(new RealmTableTest(), config);
-
-        IStatisticManager manager = new StatisticManagerImpl(config);
-        PeerTest p1 = new PeerTest(1, 1, true, manager);
-        PeerTest p2 = new PeerTest(2, 1, true, manager);
-        PeerTest p3 = new PeerTest(3, 1, true, manager);
-        PeerTest p4 = new PeerTest(4, 1, true, manager);
-
-        List peers = new ArrayList(3);
-        peers.add(p1);
-        peers.add(p2);
-        peers.add(p3);
-
-        // Test simple round robin (all weight = 1)
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-
-        // Test weighted round robin (p1=2, p2=1, p3=1)
-        p1.setRating(2);
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-
-        // Test weighted round robin (p1=2, p2=2, p3=1)
-        p2.setRating(2);
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-
-        // Test equally weighted round robin (p1=2, p2=2, p3=2)
-        p3.setRating(2);
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-
-        // Add Peer-4 with weight 1 to list
-        peers.add(p4);
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-        // expected glitch here: due to the sudden availibity of Peer-4, the algorithm is disturbed
-        assertEquals(p4.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-        assertEquals(p4.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-        assertEquals(p4.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p3.toString(), router.selectPeer(peers).toString());
-
-        // Next cycle would produce Peer-4, but reduce peer list now
-        peers = peers.subList(0,2); // now: Peer-1, Peer-2
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
+  @Test
+  public void testWeightedRoundRobin() throws Exception {
 
-    }
+    Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobin-config.xml");
+    WeightedRoundRobinRouter router = new WeightedRoundRobinRouter(new RealmTableTest(), config);
+
+    IStatisticManager manager = new StatisticManagerImpl(config);
+    PeerTest p1 = new PeerTest(1, 1, true, manager);
+    PeerTest p2 = new PeerTest(2, 1, true, manager);
+    PeerTest p3 = new PeerTest(3, 1, true, manager);
+    PeerTest p4 = new PeerTest(4, 1, true, manager);
+
+    List peers = new ArrayList(3);
+    peers.add(p1);
+    peers.add(p2);
+    peers.add(p3);
+
+    // Test simple round robin (all weight = 1)
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+
+    // Test weighted round robin (p1=2, p2=1, p3=1)
+    p1.setRating(2);
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+
+    // Test weighted round robin (p1=2, p2=2, p3=1)
+    p2.setRating(2);
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+
+    // Test equally weighted round robin (p1=2, p2=2, p3=2)
+    p3.setRating(2);
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+
+    // Add Peer-4 with weight 1 to list
+    peers.add(p4);
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    // expected glitch here: due to the sudden availibity of Peer-4, the algorithm is disturbed
+    assertEquals(p4.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p4.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+    assertEquals(p4.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p3.toString(), router.selectPeer(peers).toString());
+
+    // Next cycle would produce Peer-4, but reduce peer list now
+    peers = peers.subList(0, 2); // now: Peer-1, Peer-2
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+
+  }
 
   /*
    * WeightedRoundRobinResubmittingRouter should behave exactly as the WeightedRoundRobinRouter
@@ -363,7 +363,6 @@ public void testWeightedRoundRobinResubmittingPeersExhaused() throws Exception {
     logger.debug("*** Execution of testWeightedRoundRobinResubmittingPeersExhaused completed ***");
   }
 
-
   /*
    * Validates that, when a peer responds with a Busy or Unable to Deliver Answer, an alternative peer is
    * selected to resubmit the Request to, based on the existing Round Robin weighting algorithm, also after
@@ -424,299 +423,301 @@ private String getSessionId() {
     return sb.toString();
   }
 
-    @Test
-    public void testWeightedLeastConnections() throws Exception {
-
-        Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedleastconnections-config.xml");
-        WeightedLeastConnectionsRouter router = new WeightedLeastConnectionsRouter(new RealmTableTest(), config);
-
-        IStatisticManager manager = new StatisticManagerImpl(config);
-        PeerTest p1 = new PeerTest(1, 1, true, manager);
-        PeerTest p2 = new PeerTest(2, 1, true, manager);
-        PeerTest p3 = new PeerTest(3, 1, true, manager);
-
-        List peers = new ArrayList(2);
-        peers.add(p1);
-        peers.add(p2);
-
-        // Test simple round robin (all weight = 1)
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-
-        // increase p1 requests/s by 1
-        p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc();
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-
-        // increase p2 requests/s by 1
-        p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc();
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-
-        // decrease p1 requests/s by 1
-        p1.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenResponsePerSecond.name()+'.'+p1.getUri()).dec();
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
+  @Test
+  public void testWeightedLeastConnections() throws Exception {
+
+    Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedleastconnections-config.xml");
+    WeightedLeastConnectionsRouter router = new WeightedLeastConnectionsRouter(new RealmTableTest(), config);
+
+    IStatisticManager manager = new StatisticManagerImpl(config);
+    PeerTest p1 = new PeerTest(1, 1, true, manager);
+    PeerTest p2 = new PeerTest(2, 1, true, manager);
+    PeerTest p3 = new PeerTest(3, 1, true, manager);
 
-        // increase p1 requests/s by 3
-        p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc();
-        p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc();
-        p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc();
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    List peers = new ArrayList(2);
+    peers.add(p1);
+    peers.add(p2);
 
-        // increase p2 requests/s by 1
-        p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc();
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-
-        // increase weight of p1
-        p1.setRating(2);
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    // Test simple round robin (all weight = 1)
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
 
-        // decrease p1 requests/s by 1
-        p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).dec();
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    // increase p1 requests/s by 1
+    p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name() + '.' + p1.getUri()).inc();
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
 
-        // increase p1 requests/s by 2
-        p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc();
-        p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name()+'.'+p1.getUri()).inc();
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    // increase p2 requests/s by 1
+    p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name() + '.' + p2.getUri()).inc();
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+
+    // decrease p1 requests/s by 1
+    p1.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenResponsePerSecond.name() + '.' + p1.getUri()).dec();
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
 
-        // increase weight and requests/s of p2
-        p2.setRating(2);
-        p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc();
-        p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc();
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-        assertEquals(p2.toString(), router.selectPeer(peers).toString());
-
-        // increase p2 requests/s by 1
-        p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name()+'.'+p2.getUri()).inc();
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
-        assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    // increase p1 requests/s by 3
+    p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name() + '.' + p1.getUri()).inc();
+    p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name() + '.' + p1.getUri()).inc();
+    p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name() + '.' + p1.getUri()).inc();
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
 
-    }
-
-    private static class RealmTableTest implements IRealmTable {
+    // increase p2 requests/s by 1
+    p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name() + '.' + p2.getUri()).inc();
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+
+    // increase weight of p1
+    p1.setRating(2);
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+
+    // decrease p1 requests/s by 1
+    p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name() + '.' + p1.getUri()).dec();
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+
+    // increase p1 requests/s by 2
+    p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name() + '.' + p1.getUri()).inc();
+    p1.getStatistic().getRecordByName(IStatisticRecord.Counters.AppGenRequestPerSecond.name() + '.' + p1.getUri()).inc();
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+
+    // increase weight and requests/s of p2
+    p2.setRating(2);
+    p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name() + '.' + p2.getUri()).inc();
+    p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name() + '.' + p2.getUri()).inc();
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+    assertEquals(p2.toString(), router.selectPeer(peers).toString());
+
+    // increase p2 requests/s by 1
+    p2.getStatistic().getRecordByName(IStatisticRecord.Counters.NetGenRequestPerSecond.name() + '.' + p2.getUri()).inc();
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+    assertEquals(p1.toString(), router.selectPeer(peers).toString());
+
+  }
 
-        public Realm matchRealm(IRequest request) {
-            return null;
-        }
+  private static class RealmTableTest implements IRealmTable {
 
-        public Realm matchRealm(IAnswer message, String destRealm) {
-            return null;
-        }
+    public Realm matchRealm(IRequest request) {
+      return null;
+    }
 
-        public Realm getRealm(String realmName, ApplicationId applicationId) {
-            return null;
-        }
+    public Realm matchRealm(IAnswer message, String destRealm) {
+      return null;
+    }
 
-        public Realm removeRealmApplicationId(String realmName, ApplicationId appId) {
-            return null;
-        }
+    public Realm getRealm(String realmName, ApplicationId applicationId) {
+      return null;
+    }
 
-        public Collection removeRealm(String realmName) {
-            return null;
-        }
+    public Realm removeRealmApplicationId(String realmName, ApplicationId appId) {
+      return null;
+    }
 
-        public Collection getRealms(String realm) {
-            return null;
-        }
+    public Collection removeRealm(String realmName) {
+      return null;
+    }
 
-        public Collection getRealms() {
-            return null;
-        }
+    public Collection getRealms(String realm) {
+      return null;
+    }
 
-        public String getRealmForPeer(String fqdn) {
-            return null;
-        }
+    public Collection getRealms() {
+      return null;
+    }
 
-        public void addLocalApplicationId(ApplicationId ap) {
+    public String getRealmForPeer(String fqdn) {
+      return null;
+    }
 
-        }
+    public void addLocalApplicationId(ApplicationId ap) {
 
-        public void removeLocalApplicationId(ApplicationId a) {
+    }
 
-        }
+    public void removeLocalApplicationId(ApplicationId a) {
 
-        public void addLocalRealm(String localRealm, String fqdn) {
+    }
 
-        }
+    public void addLocalRealm(String localRealm, String fqdn) {
 
-        public Realm addRealm(String name, ApplicationId appId, LocalAction locAction, IAgentConfiguration agentConfImpl, boolean isDynamic, long expirationTime, String[] hosts) throws InternalException {
-            return null;
-        }
+    }
 
-        public Statistic getStatistic(String realmName) {
-            return null;
-        }
+    public Realm addRealm(String name, ApplicationId appId, LocalAction locAction, IAgentConfiguration agentConfImpl, boolean isDynamic, long expirationTime,
+        String[] hosts) throws InternalException {
+      return null;
+    }
 
-        public Realm addRealm(String realmName, ApplicationId applicationId, LocalAction action, String agentConfiguration, boolean dynamic, long expirationTime, String[] hosts) throws InternalException {
-            return null;
-        }
+    public Statistic getStatistic(String realmName) {
+      return null;
+    }
+
+    public Realm addRealm(String realmName, ApplicationId applicationId, LocalAction action, String agentConfiguration, boolean dynamic, long expirationTime,
+        String[] hosts) throws InternalException {
+      return null;
+    }
 
-        public boolean realmExists(String realmName) {
-            return false;
-        }
+    public boolean realmExists(String realmName) {
+      return false;
+    }
 
-        public boolean isWrapperFor(Class iface) throws InternalException {
-            return false;
-        }
+    public boolean isWrapperFor(Class iface) throws InternalException {
+      return false;
+    }
 
-        public  T unwrap(Class iface) throws InternalException {
-            return null;
-        }
+    public  T unwrap(Class iface) throws InternalException {
+      return null;
+    }
 
-        public List getAllRealmSet(){
-          return null;
-        }
+    public List getAllRealmSet() {
+      return null;
     }
+  }
 
-    private static class PeerTest extends AbstractPeer implements IPeer {
+  private static class PeerTest extends AbstractPeer implements IPeer {
 
-        private int id;
-        private int rating;
-        private boolean connected;
+    private int id;
+    private int rating;
+    private boolean connected;
 
-        public PeerTest(int id, int rating, boolean connected, IStatisticManager manager) throws URISyntaxException, UnknownServiceException {
-            super(new URI("aaa://"+id), manager);
-            this.id = id;
-            this.rating = rating;
-            this.connected = connected;
-            createPeerStatistics();
-        }
+    public PeerTest(int id, int rating, boolean connected, IStatisticManager manager) throws URISyntaxException, UnknownServiceException {
+      super(new URI("aaa://" + id), manager);
+      this.id = id;
+      this.rating = rating;
+      this.connected = connected;
+      createPeerStatistics();
+    }
 
-        public void setRating(int rating) {
-            this.rating = rating;
-        }
+    public void setRating(int rating) {
+      this.rating = rating;
+    }
 
-        public int getRating() {
-            return rating;
-        }
+    public int getRating() {
+      return rating;
+    }
 
-        public long getHopByHopIdentifier() {
-            return 0;
-        }
+    public long getHopByHopIdentifier() {
+      return 0;
+    }
 
-        public void addMessage(IMessage message) {
+    public void addMessage(IMessage message) {
 
-        }
+    }
 
-        public void remMessage(IMessage message) {
+    public void remMessage(IMessage message) {
 
-        }
+    }
 
-        public IMessage[] remAllMessage() {
-            return new IMessage[0];
-        }
+    public IMessage[] remAllMessage() {
+      return new IMessage[0];
+    }
 
-        public boolean handleMessage(EventTypes type, IMessage message, String key) throws TransportException, OverloadException, InternalException {
-            return false;
-        }
+    public boolean handleMessage(EventTypes type, IMessage message, String key) throws TransportException, OverloadException, InternalException {
+      return false;
+    }
 
-        public boolean sendMessage(IMessage message) throws TransportException, OverloadException, InternalException {
-            return false;
-        }
+    public boolean sendMessage(IMessage message) throws TransportException, OverloadException, InternalException {
+      return false;
+    }
 
-        public boolean hasValidConnection() {
-            return connected;
-        }
+    public boolean hasValidConnection() {
+      return connected;
+    }
 
-        public void setRealm(String realm) {
+    public void setRealm(String realm) {
 
-        }
+    }
 
-        public void addStateChangeListener(StateChangeListener listener) {
+    public void addStateChangeListener(StateChangeListener listener) {
 
-        }
+    }
 
-        public void remStateChangeListener(StateChangeListener listener) {
+    public void remStateChangeListener(StateChangeListener listener) {
 
-        }
+    }
 
-        public void addConnectionListener(IConnectionListener listener) {
+    public void addConnectionListener(IConnectionListener listener) {
 
-        }
+    }
 
-        public void remConnectionListener(IConnectionListener listener) {
+    public void remConnectionListener(IConnectionListener listener) {
 
-        }
+    }
 
-        public IStatistic getStatistic() {
-            return statistic;
-        }
+    public IStatistic getStatistic() {
+      return statistic;
+    }
 
-        public boolean isConnected() {
-            return connected;
-        }
+    public boolean isConnected() {
+      return connected;
+    }
 
-        public void connect() throws InternalException, IOException, IllegalDiameterStateException {
+    public void connect() throws InternalException, IOException, IllegalDiameterStateException {
 
-        }
+    }
 
-        @Override
-        public void disconnect(int disconnectCause) throws InternalException, IllegalDiameterStateException {
+    @Override
+    public void disconnect(int disconnectCause) throws InternalException, IllegalDiameterStateException {
 
-        }
+    }
 
-        public  E getState(Class enumc) {
-            return null;
-        }
+    public  E getState(Class enumc) {
+      return null;
+    }
 
-        public URI getUri() {
-            return uri;
-        }
+    public URI getUri() {
+      return uri;
+    }
 
-        public InetAddress[] getIPAddresses() {
-            return new InetAddress[0];
-        }
+    public InetAddress[] getIPAddresses() {
+      return new InetAddress[0];
+    }
 
-        public String getRealmName() {
-            return null;
-        }
+    public String getRealmName() {
+      return null;
+    }
 
-        public long getVendorId() {
-            return 0;
-        }
+    public long getVendorId() {
+      return 0;
+    }
 
-        public String getProductName() {
-            return null;
-        }
+    public String getProductName() {
+      return null;
+    }
 
-        public long getFirmware() {
-            return 0;
-        }
+    public long getFirmware() {
+      return 0;
+    }
 
-        public Set getCommonApplications() {
-            return null;
-        }
+    public Set getCommonApplications() {
+      return null;
+    }
 
-        public void addPeerStateListener(PeerStateListener listener) {
+    public void addPeerStateListener(PeerStateListener listener) {
 
-        }
+    }
 
-        public void removePeerStateListener(PeerStateListener listener) {
+    public void removePeerStateListener(PeerStateListener listener) {
 
-        }
+    }
 
-        @Override
-        public String toString() {
-            return "Peer-"+id;
-        }
+    @Override
+    public String toString() {
+      return "Peer-" + id;
     }
+  }
 
 }
\ No newline at end of file

From b9e42a4179ef6b820d1c4a2edc6ea3c129abc221 Mon Sep 17 00:00:00 2001
From: Steve Dwyer 
Date: Thu, 9 Apr 2020 17:59:50 +0100
Subject: [PATCH 5/7] Added check to ensure that the Router implementation can
 handle submitting requests which have received a Busy or Unable to Deliver
 Answer from one peer, to an alternative peer, and to avoid perpetual
 re-submission of such requests

---
 .../jdiameter/client/api/router/IRouter.java  | 18 ++++++++++--
 .../client/impl/controller/PeerImpl.java      | 23 ++++++++-------
 .../client/impl/router/RouterImpl.java        | 12 ++++++++
 .../WeightedRoundRobinResubmittingRouter.java | 29 +++++++++++++++----
 .../client/impl/router/TestRouter.java        | 12 ++++++++
 5 files changed, 76 insertions(+), 18 deletions(-)

diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java
index 19f547e1a..d5b29a9aa 100644
--- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java
+++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/router/IRouter.java
@@ -123,8 +123,22 @@ public interface IRouter  {
 
 
   /**
-   * Called when a 3002 or 3004 is received for a request. This method updates cc-request-number and attempts to resubmit the request
-   * to an alternative peer.
+   * Indicates whether this router implementation is able to resubmit requests to an alternative peer,
+   * for which a Busy or Unable to Deliver Answer has already been received from one peer.

+ * + * Note: Returning true from this method when the router implementation has not been designed to + * handle resubmitting such requests can result in a request being resubmitted perpetually. + * + * @return false by default. true when and only when the router implementation has specific logic to handle + * submitting requests which have received a Busy or Unable to Deliver Answer from one peer, to an alternative peer, and to avoid + * perpetual re-submission of such requests. + */ + boolean canProcessBusyOrUnableToDeliverAnswer(); + + + /** + * Called when a 3002 or 3004 is received for a request. This method attempts to resubmit the request to an alternative peer. + * * @param request * @param table */ diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java index e04c296e3..0a3506019 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java @@ -492,15 +492,18 @@ private IMessage processRedirectAnswer(IMessage request, IMessage answer) { } private IMessage processBusyOrUnableToDeliverAnswer(IMessage request, IMessage answer) { - try { - router.processBusyOrUnableToDeliverAnswer(request, table); - return null; - } - catch (Throwable exc) { - // Any error when attempting a resubmit to an alternative peer simply results in the original - // Busy or Unable to Deliver Answer being returned - if (logger.isErrorEnabled()) { - logger.error("Failed to reprocess busy or unable to deliver response!", exc); + if (router.canProcessBusyOrUnableToDeliverAnswer()) { + try { + logger.debug("Message with [sessionId={}] received a Busy or Unable to Deliver Answer and will be resubmitted.", request.getSessionId()); + router.processBusyOrUnableToDeliverAnswer(request, table); + return null; + } + catch (Throwable exc) { + // Any error when attempting a resubmit to an alternative peer simply results in the original + // Busy or Unable to Deliver Answer being returned + if (logger.isErrorEnabled()) { + logger.error("Failed to reprocess busy or unable to deliver response - all peers exhausted?", exc); + } } } return answer; @@ -1062,8 +1065,6 @@ public boolean receiveMessage(IMessage message) { } avpResCode = message.getAvps().getAvp(RESULT_CODE); if (isBusyOrUnableToDeliverAnswer(avpResCode, message)) { - logger.debug("Message with [sessionId={}] received a Busy or Unable to Deliver Answer and will be resubmitted.", message.getSessionId()); - message.setListener(request.getEventListener()); message = processBusyOrUnableToDeliverAnswer(request, message); // if return value is not null, there was some error, lets try to invoke listener if it exists... isProcessed = message == null; diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java index bdb455b22..d526dfd33 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/RouterImpl.java @@ -639,6 +639,18 @@ public void processRedirectAnswer(IRequest request, IAnswer answer, IPeerTable t } } + /** + * This method should always return false unless specifically designed to handle + * submitting requests which have received a Busy or Unable to Deliver Answer from one peer, to an alternative peer, and to avoid + * perpetual re-submission of such requests. + * + * @return false + */ + @Override + public boolean canProcessBusyOrUnableToDeliverAnswer() { + return false; + } + public void processBusyOrUnableToDeliverAnswer(IRequest request, IPeerTable table) throws InternalException, RouteException { try { table.sendMessage((IMessage) request); diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java index 368e8d8ad..52d2a07c3 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/WeightedRoundRobinResubmittingRouter.java @@ -43,6 +43,7 @@ * Weighted round-robin router implementation * * @author Nils Sowen + * @author Steve Dwyer * @see * http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling @@ -66,6 +67,18 @@ public WeightedRoundRobinResubmittingRouter(IContainer container, IConcurrentFac super(container, concurrentFactory, realmTable, config, aMetaData); } + /** + * The WeightedRoundRobinResubmittingRouter is specifically designed to handle submitting requests + * which have received a Busy or Unable to Deliver Answer from one peer, to an alternative peer, + * and to avoid perpetual re-submission of such requests. + * + * @return true + */ + @Override + public boolean canProcessBusyOrUnableToDeliverAnswer() { + return true; + } + /** * Select peer by weighted round-robin scheduling * As documented in http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling @@ -234,7 +247,7 @@ public IPeer selectPeer(IMessage message, List availablePeers) { } private MessageKey getMessageKey(final IMessage message) { - return new MessageKey(message.getSessionId(), message.getEndToEndIdentifier()); + return new MessageKey(message); } private boolean isPeerPreviouslyAttempted(int selectedPeerIndex, List availablePeers, IMessage message) { @@ -316,16 +329,18 @@ protected int gcd(int a, int b) { private class MessageKey { private String sessionId; private long endToEndId; + private int flags; - MessageKey(String sessionId, long endToEndId) { + MessageKey(IMessage message) { super(); - this.sessionId = sessionId; - this.endToEndId = endToEndId; + this.sessionId = message.getSessionId(); + this.endToEndId = message.getEndToEndIdentifier(); + this.flags = message.getFlags(); } @Override public String toString() { - return "MessageKey [sessionId=" + sessionId + ", endToEndId=" + endToEndId + "]"; + return "MessageKey [sessionId=" + sessionId + ", endToEndId=" + endToEndId + ", flags=" + flags + "]"; } @Override @@ -334,6 +349,7 @@ public int hashCode() { int result = 1; result = prime * result + getOuterType().hashCode(); result = prime * result + (int) (endToEndId ^ (endToEndId >>> 32)); + result = prime * result + flags; result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode()); return result; } @@ -356,6 +372,9 @@ public boolean equals(Object obj) { if (endToEndId != other.endToEndId) { return false; } + if (flags != other.flags) { + return false; + } if (sessionId == null) { if (other.sessionId != null) { return false; diff --git a/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java b/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java index 89d13f689..e325e210f 100644 --- a/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java +++ b/core/jdiameter/impl/src/test/java/org/jdiameter/client/impl/router/TestRouter.java @@ -78,6 +78,8 @@ public void testWeightedRoundRobin() throws Exception { Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobin-config.xml"); WeightedRoundRobinRouter router = new WeightedRoundRobinRouter(new RealmTableTest(), config); + assertFalse(router.canProcessBusyOrUnableToDeliverAnswer()); + IStatisticManager manager = new StatisticManagerImpl(config); PeerTest p1 = new PeerTest(1, 1, true, manager); PeerTest p2 = new PeerTest(2, 1, true, manager); @@ -184,6 +186,8 @@ public void testWeightedRoundRobinResubmittingNoResubmissions() throws Exception Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml"); WeightedRoundRobinResubmittingRouter router = new WeightedRoundRobinResubmittingRouter(new RealmTableTest(), config); + assertTrue(router.canProcessBusyOrUnableToDeliverAnswer()); + IStatisticManager manager = new StatisticManagerImpl(config); PeerTest p1 = new PeerTest(1, 1, true, manager); PeerTest p2 = new PeerTest(2, 1, true, manager); @@ -290,6 +294,8 @@ public void testWeightedRoundRobinResubmittingOnBusyOrUnableToDeliverAnswer() th Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml"); WeightedRoundRobinResubmittingRouter router = new WeightedRoundRobinResubmittingRouter(new RealmTableTest(), config); + assertTrue(router.canProcessBusyOrUnableToDeliverAnswer()); + IStatisticManager manager = new StatisticManagerImpl(config); PeerTest p1 = new PeerTest(1, 1, true, manager); PeerTest p2 = new PeerTest(2, 2, true, manager); @@ -331,6 +337,8 @@ public void testWeightedRoundRobinResubmittingPeersExhaused() throws Exception { Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml"); WeightedRoundRobinResubmittingRouter router = new WeightedRoundRobinResubmittingRouter(new RealmTableTest(), config); + assertTrue(router.canProcessBusyOrUnableToDeliverAnswer()); + IStatisticManager manager = new StatisticManagerImpl(config); PeerTest p1 = new PeerTest(1, 1, true, manager); PeerTest p2 = new PeerTest(2, 2, true, manager); @@ -376,6 +384,8 @@ public void testWeightedRoundRobinResubmittingOnBusyOrUnableToDeliverAnswerAfter Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedroundrobinresubmitting-config.xml"); WeightedRoundRobinResubmittingRouter router = new WeightedRoundRobinResubmittingRouter(new RealmTableTest(), config); + assertTrue(router.canProcessBusyOrUnableToDeliverAnswer()); + IStatisticManager manager = new StatisticManagerImpl(config); PeerTest p1 = new PeerTest(1, 1, true, manager); PeerTest p2 = new PeerTest(2, 2, true, manager); @@ -429,6 +439,8 @@ public void testWeightedLeastConnections() throws Exception { Configuration config = new XMLConfiguration("src/test/resources/jdiameter-weightedleastconnections-config.xml"); WeightedLeastConnectionsRouter router = new WeightedLeastConnectionsRouter(new RealmTableTest(), config); + assertFalse(router.canProcessBusyOrUnableToDeliverAnswer()); + IStatisticManager manager = new StatisticManagerImpl(config); PeerTest p1 = new PeerTest(1, 1, true, manager); PeerTest p2 = new PeerTest(2, 1, true, manager); From 2575d51907c480d51b7678134aa2c37abf9d1ece Mon Sep 17 00:00:00 2001 From: Steve Dwyer Date: Mon, 20 Apr 2020 10:16:42 +0100 Subject: [PATCH 6/7] Removed duplicate variable assignment of avpResCode --- .../java/org/jdiameter/client/impl/controller/PeerImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java index 0a3506019..cfe652eec 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java @@ -1060,10 +1060,9 @@ public boolean receiveMessage(IMessage message) { if (isRedirectAnswer(avpResCode, message)) { message.setListener(request.getEventListener()); message = processRedirectAnswer(request, message); - //if return value is not null, there was some error, lets try to invoke listener if it exists... + // if return value is not null, there was some error, lets try to invoke listener if it exists... isProcessed = message == null; } - avpResCode = message.getAvps().getAvp(RESULT_CODE); if (isBusyOrUnableToDeliverAnswer(avpResCode, message)) { message = processBusyOrUnableToDeliverAnswer(request, message); // if return value is not null, there was some error, lets try to invoke listener if it exists... From 3f9fafc84983e2901c71aed0ccf9836e6f446042 Mon Sep 17 00:00:00 2001 From: Steve Dwyer Date: Wed, 20 May 2020 11:50:32 +0100 Subject: [PATCH 7/7] Revert "Removed duplicate variable assignment of avpResCode" This reverts commit 2575d51907c480d51b7678134aa2c37abf9d1ece. --- .../java/org/jdiameter/client/impl/controller/PeerImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java index cfe652eec..0a3506019 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/controller/PeerImpl.java @@ -1060,9 +1060,10 @@ public boolean receiveMessage(IMessage message) { if (isRedirectAnswer(avpResCode, message)) { message.setListener(request.getEventListener()); message = processRedirectAnswer(request, message); - // if return value is not null, there was some error, lets try to invoke listener if it exists... + //if return value is not null, there was some error, lets try to invoke listener if it exists... isProcessed = message == null; } + avpResCode = message.getAvps().getAvp(RESULT_CODE); if (isBusyOrUnableToDeliverAnswer(avpResCode, message)) { message = processBusyOrUnableToDeliverAnswer(request, message); // if return value is not null, there was some error, lets try to invoke listener if it exists...