diff --git a/.gitignore b/.gitignore index cdfed9175..c57e92307 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,47 @@ -# Java compiled # -################# +######## Java compiled ######### +################################ *.class -# Eclipse # -########### +########### Eclipse ############ +################################ .classpath .project .settings -# IntelliJ IDEA # -################# -.idea/ +######## IntelliJ IDEA ######### +################################ +.idea *.iml *.iws -# Maven # -######### +########## NetBeans ############ +################################ +nbactions.xml + +# Mobile Tools for Java (J2ME) # +################################ +.mtj.tmp + +############ Maven ############# +################################ target +*.jar +*.war +*.ear -# OS generated files # -###################### +###### OS generated files ###### +################################ +.directory +.Trashes +._* +*~ .DS_Store .DS_Store? -._* .Spotlight-V100 -.Trashes Icon? ehthumbs.db Thumbs.db + +######## VM crash logs ######### +################################ +hs_err_pid* diff --git a/core/jdiameter-ha/impl/src/main/java/org/mobicents/diameter/impl/ha/client/ro/ClientRoSessionDataReplicatedImpl.java b/core/jdiameter-ha/impl/src/main/java/org/mobicents/diameter/impl/ha/client/ro/ClientRoSessionDataReplicatedImpl.java index f1a150ac0..91314f0de 100644 --- a/core/jdiameter-ha/impl/src/main/java/org/mobicents/diameter/impl/ha/client/ro/ClientRoSessionDataReplicatedImpl.java +++ b/core/jdiameter-ha/impl/src/main/java/org/mobicents/diameter/impl/ha/client/ro/ClientRoSessionDataReplicatedImpl.java @@ -62,9 +62,9 @@ import org.slf4j.LoggerFactory; /** - * * @author Bartosz Baranowski * @author Alexandre Mendonca + * @author Grzegorz Figiel (ProIDS sp. z o.o.) */ public class ClientRoSessionDataReplicatedImpl extends AppSessionDataReplicatedImpl implements IClientRoSessionData { @@ -74,18 +74,20 @@ public class ClientRoSessionDataReplicatedImpl extends AppSessionDataReplicatedI private static final String REQUEST_TYPE = "REQUEST_TYPE"; private static final String STATE = "STATE"; private static final String TXTIMER_ID = "TXTIMER_ID"; + private static final String RETRANSMISSION_TIMER_ID = "RETRANSMISSION_TIMER_ID"; private static final String TXTIMER_REQUEST = "TXTIMER_REQUEST"; private static final String BUFFER = "BUFFER"; private static final String GRA = "GRA"; private static final String GDDFH = "GDDFH"; private static final String GCCFH = "GCCFH"; + private static final String GCCSF = "GCCSF"; private IMessageParser messageParser; /** * @param nodeFqn * @param mobicentsCluster - * @param iface + * @param container */ public ClientRoSessionDataReplicatedImpl(Fqn nodeFqn, MobicentsCluster mobicentsCluster, IContainer container) { super(nodeFqn, mobicentsCluster); @@ -101,7 +103,7 @@ public ClientRoSessionDataReplicatedImpl(Fqn nodeFqn, MobicentsCluster mobice /** * @param sessionId * @param mobicentsCluster - * @param iface + * @param container */ public ClientRoSessionDataReplicatedImpl(String sessionId, MobicentsCluster mobicentsCluster, IContainer container) { this(Fqn.fromRelativeElements(ReplicatedSessionDatasource.SESSIONS_FQN, sessionId), mobicentsCluster, container); @@ -187,6 +189,26 @@ public void setTxTimerId(Serializable txTimerId) { } } + @Override + public Serializable getRetransmissionTimerId() { + if (exists()) { + return (Serializable) getNode().get(RETRANSMISSION_TIMER_ID); + } + else { + throw new IllegalStateException(); + } + } + + @Override + public void setRetransmissionTimerId(Serializable txTimerId) { + if (exists()) { + getNode().put(RETRANSMISSION_TIMER_ID, txTimerId); + } + else { + throw new IllegalStateException(); + } + } + @Override public Request getTxTimerRequest() { if (exists()) { @@ -323,4 +345,24 @@ public void setGatheredDDFH(int gatheredDDFH) { } } + @Override + public int getGatheredCCSF() { + if (exists()) { + return toPrimitive((Integer) getNode().get(GCCSF)); + } + else { + throw new IllegalStateException(); + } + } + + @Override + public void setGatheredCCSF(int gatheredCCSF) { + if (exists()) { + getNode().put(GCCSF, gatheredCCSF); + } + else { + throw new IllegalStateException(); + } + } + } diff --git a/core/jdiameter-ha/impl/src/main/java/org/mobicents/diameter/impl/ha/timer/ReplicatedTimerFacilityImpl.java b/core/jdiameter-ha/impl/src/main/java/org/mobicents/diameter/impl/ha/timer/ReplicatedTimerFacilityImpl.java index 9f4a92242..50972b201 100644 --- a/core/jdiameter-ha/impl/src/main/java/org/mobicents/diameter/impl/ha/timer/ReplicatedTimerFacilityImpl.java +++ b/core/jdiameter-ha/impl/src/main/java/org/mobicents/diameter/impl/ha/timer/ReplicatedTimerFacilityImpl.java @@ -46,6 +46,7 @@ import org.jdiameter.api.BaseSession; import org.jdiameter.client.api.IContainer; +import org.jdiameter.client.impl.BaseSessionImpl; import org.jdiameter.common.api.data.ISessionDatasource; import org.jdiameter.common.api.timer.ITimerFacility; import org.jdiameter.common.impl.app.AppSessionImpl; @@ -129,13 +130,25 @@ public void runTask() { try { DiameterTimerTaskData data = (DiameterTimerTaskData) getData(); BaseSession bSession = sessionDataSource.getSession(data.getSessionId()); - if (bSession == null || !bSession.isAppSession()) { + if (bSession == null) { // FIXME: error ? + logger.error("Base Session is null for sessionId: {}", data.getSessionId()); return; } else { - AppSessionImpl impl = (AppSessionImpl) bSession; - impl.onTimer(data.getTimerName()); + try { + if (!bSession.isAppSession()) { + BaseSessionImpl impl = (BaseSessionImpl) bSession; + impl.onTimer(data.getTimerName()); + } + else { + AppSessionImpl impl = (AppSessionImpl) bSession; + impl.onTimer(data.getTimerName()); + } + } + catch (Exception e) { + logger.error("Caught exception from session object!", e); + } } } catch (Exception e) { diff --git a/core/jdiameter/api/src/main/java/org/jdiameter/api/AvpSet.java b/core/jdiameter/api/src/main/java/org/jdiameter/api/AvpSet.java index a2af490a8..d1eef3c31 100644 --- a/core/jdiameter/api/src/main/java/org/jdiameter/api/AvpSet.java +++ b/core/jdiameter/api/src/main/java/org/jdiameter/api/AvpSet.java @@ -94,6 +94,21 @@ public interface AvpSet extends Iterable, Serializable, Wrapper { */ AvpSet getAvps(int avpCode, long vendorId); + /** + * Get position of the first instance of the AVP + * @param avpCode code of the Avp + * @return index (position) of the first occurrence of the Avp. -1 in case Avp is not found + */ + int getAvpIndex(int avpCode); + + /** + * Get position of the first instance of the AVP + * @param avpCode code of the Avp + * @param vendorId vendorId of the Avp + * @return index (position) of the first occurrence of the Avp. -1 in case Avp is not found + */ + int getAvpIndex(int avpCode, long vendorId); + /** * Remove AVPs with avpCode * @param avpCode code of Avp diff --git a/core/jdiameter/api/src/main/java/org/jdiameter/api/BaseSession.java b/core/jdiameter/api/src/main/java/org/jdiameter/api/BaseSession.java index e724e7b07..572222746 100644 --- a/core/jdiameter/api/src/main/java/org/jdiameter/api/BaseSession.java +++ b/core/jdiameter/api/src/main/java/org/jdiameter/api/BaseSession.java @@ -104,4 +104,6 @@ public interface BaseSession { * @return session-id as String (Session-Id AVP) */ String getSessionId(); + + String IDLE_SESSION_TIMER_NAME = "IDLE_SESSION_TIMER"; } diff --git a/core/jdiameter/api/src/main/java/org/jdiameter/api/Configuration.java b/core/jdiameter/api/src/main/java/org/jdiameter/api/Configuration.java index 14c99d5de..d0311bd66 100644 --- a/core/jdiameter/api/src/main/java/org/jdiameter/api/Configuration.java +++ b/core/jdiameter/api/src/main/java/org/jdiameter/api/Configuration.java @@ -105,6 +105,16 @@ public interface Configuration { */ byte[] getByteArrayValue(int key, byte[] defaultValue); + /** + * Returns the int[] point value of the given key. + * + * @param key the key + * @param defaultValue the Default Value + * @return the value, or defaultValue if the key was not found or was found + * but was not a int[] point number + */ + int[] getIntArrayValue(int key, int[] defaultValue); + /** * Returns the boolean point value of the given key. * diff --git a/core/jdiameter/api/src/main/java/org/jdiameter/api/MutableConfiguration.java b/core/jdiameter/api/src/main/java/org/jdiameter/api/MutableConfiguration.java index 0a41f0419..966c8b678 100644 --- a/core/jdiameter/api/src/main/java/org/jdiameter/api/MutableConfiguration.java +++ b/core/jdiameter/api/src/main/java/org/jdiameter/api/MutableConfiguration.java @@ -86,6 +86,13 @@ public interface MutableConfiguration extends Configuration { */ void setByteArrayValue(int key, byte[] value); + /** + * Set int array value to configuration + * @param key key of value + * @param value int array value + */ + void setIntArrayValue(int key, int[] value); + /** * Set boolean value to configuration * @param key key of value diff --git a/core/jdiameter/api/src/main/java/org/jdiameter/api/NoMorePeersAvailableException.java b/core/jdiameter/api/src/main/java/org/jdiameter/api/NoMorePeersAvailableException.java new file mode 100644 index 000000000..f2f5a2e5b --- /dev/null +++ b/core/jdiameter/api/src/main/java/org/jdiameter/api/NoMorePeersAvailableException.java @@ -0,0 +1,103 @@ +/* + * 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.api; + +/** + * Signals that no peer is available for routing. + * + * @author ProIDS sp. z o.o. + */ +public class NoMorePeersAvailableException extends RouteException { + + private static final long serialVersionUID = 1L; + + private boolean sessionPersistentRoutingEnabled = false; + private int lastSelectedPeerRating = -1; + private String roundRobinContextDescription = null; + + /** + * Constructor with reason string and routing details + * + * @param message reason string + */ + public NoMorePeersAvailableException(String message, boolean spre, String rrcd, int lspr) { + super(message); + this.setSessionPersistentRoutingEnabled(spre); + this.setRoundRobinContextDescription(rrcd); + this.setLastSelectedPeerRating(lspr); + } + + /** + * Constructor with reason string and parent exception + * + * @param message message reason string + * @param cause parent exception + */ + public NoMorePeersAvailableException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructor with reason string + * + * @param message reason string + */ + public NoMorePeersAvailableException(String message) { + super(message); + } + + public boolean isSessionPersistentRoutingEnabled() { + return sessionPersistentRoutingEnabled; + } + + public void setSessionPersistentRoutingEnabled(boolean sessionPersistentRoutingEnabled) { + this.sessionPersistentRoutingEnabled = sessionPersistentRoutingEnabled; + } + + public String getRoundRobinContextDescription() { + return roundRobinContextDescription; + } + + public void setRoundRobinContextDescription(String roundRobinContextDescription) { + this.roundRobinContextDescription = roundRobinContextDescription; + } + + public int getLastSelectedPeerRating() { + return lastSelectedPeerRating; + } + + public void setLastSelectedPeerRating(int lastSelectedPeerRating) { + this.lastSelectedPeerRating = lastSelectedPeerRating; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder + .append("NoMorePeersAvailableException [sessionPersistentRoutingEnabled=") + .append(sessionPersistentRoutingEnabled) + .append(", lastSelectedPeerRating=").append(lastSelectedPeerRating) + .append(", roundRobinContextDescription=") + .append(roundRobinContextDescription) + .append(", message=").append(getMessage()) + .append("]"); + return builder.toString(); + } +} diff --git a/core/jdiameter/api/src/main/java/org/jdiameter/api/SessionPersistenceStorage.java b/core/jdiameter/api/src/main/java/org/jdiameter/api/SessionPersistenceStorage.java new file mode 100644 index 000000000..0e9f78fae --- /dev/null +++ b/core/jdiameter/api/src/main/java/org/jdiameter/api/SessionPersistenceStorage.java @@ -0,0 +1,38 @@ +/* + * 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.api; + +import java.util.List; + +/** + * Enables to review and supervise the current state of session persistence map that is used + * for routing that preserves sticky sessions paradigm. Read only access is given for + * the sake of safety issues. + */ +public interface SessionPersistenceStorage { + + /** + * Returns a list of all session persistence records that are currently in operation. + * + * @param maxLimit maximum number of records to be listed (0 corresponds to no limit) + * @return list of active records + */ + List dumpStickySessions(int maxLimit); +} diff --git a/core/jdiameter/api/src/main/java/org/jdiameter/api/Stack.java b/core/jdiameter/api/src/main/java/org/jdiameter/api/Stack.java index c6d28ea88..6e5723118 100644 --- a/core/jdiameter/api/src/main/java/org/jdiameter/api/Stack.java +++ b/core/jdiameter/api/src/main/java/org/jdiameter/api/Stack.java @@ -134,6 +134,13 @@ public interface Stack extends Wrapper { */ SessionFactory getSessionFactory() throws IllegalDiameterStateException; + /** + * Return SessionPersistenceStorage instance + * @return SessionPersistenceStorage instance + * @throws IllegalDiameterStateException if stack is not configured + */ + SessionPersistenceStorage getSessionPersistenceStorage(); + /** * Return Dictionary instance * @return Dictionary instance diff --git a/core/jdiameter/api/src/main/java/org/jdiameter/api/cca/RequestType.java b/core/jdiameter/api/src/main/java/org/jdiameter/api/cca/RequestType.java new file mode 100644 index 000000000..2cdd3c244 --- /dev/null +++ b/core/jdiameter/api/src/main/java/org/jdiameter/api/cca/RequestType.java @@ -0,0 +1,56 @@ +/* + * 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.api.cca; + +/** + * This enumerated class defines CC-Request-Type AVP possible values as described in RFC 4006: + * + *
+ *     The CC-Request-Type AVP (AVP Code 416) is of type Enumerated and
+ *     contains the reason for sending the credit-control request message.
+ *     It MUST be present in all Credit-Control-Request messages.  The
+ *     following values are defined for the CC-Request-Type AVP:
+ *
+ *     INITIAL_REQUEST                 1
+ *     UPDATE_REQUEST                  2
+ *     TERMINATION_REQUEST             3
+ *     EVENT_REQUEST                   4
+ * 
+ */ +public enum RequestType { + INITIAL_REQUEST(1), + UPDATE_REQUEST(2), + TERMINATION_REQUEST(3), + EVENT_REQUEST(4); + + private final int value; + + RequestType(int value) { + this.value = value; + } + + /** + * Gets value of the corresponding constant as defined in RFC + * @return value of the AVP + */ + public int value() { + return this.value; + } +} diff --git a/core/jdiameter/api/src/main/java/org/jdiameter/api/ro/ClientRoSessionListener.java b/core/jdiameter/api/src/main/java/org/jdiameter/api/ro/ClientRoSessionListener.java index 59954f0a3..f4277bbe2 100644 --- a/core/jdiameter/api/src/main/java/org/jdiameter/api/ro/ClientRoSessionListener.java +++ b/core/jdiameter/api/src/main/java/org/jdiameter/api/ro/ClientRoSessionListener.java @@ -44,7 +44,9 @@ import org.jdiameter.api.IllegalDiameterStateException; import org.jdiameter.api.InternalException; +import org.jdiameter.api.Message; import org.jdiameter.api.OverloadException; +import org.jdiameter.api.Peer; import org.jdiameter.api.RouteException; import org.jdiameter.api.app.AppAnswerEvent; import org.jdiameter.api.app.AppRequestEvent; @@ -103,6 +105,49 @@ void doReAuthRequest(ClientRoSession session, ReAuthRequest request) void doOtherEvent(AppSession session, AppRequestEvent request, AppAnswerEvent answer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException; + /** + * Notifies this ClientRoSessionListener that message delivery timeout expired and there was no response from + * one of remote peers. + * + * @param session parent application session (FSM) + * @param msg request object + * @throws InternalException The InternalException signals that internal error has occurred. + * @throws IllegalDiameterStateException The IllegalStateException signals that session has incorrect state (invalid). + * @throws RouteException The NoRouteException signals that no route exist for a given realm. + * @throws OverloadException The OverloadException signals that destination host is overloaded. + */ + void doRequestTxTimeout(ClientRoSession session, Message msg, Peer peer) + throws InternalException, IllegalDiameterStateException, RouteException, OverloadException; + + /** + * Notifies this ClientRoSessionListener that message delivery timeout expired and there was no response from + * any of remote peers in spite of numerous retransmissions and performing failover algorithm if enabled. + * + * @param session parent application session (FSM) + * @param msg request object + * @throws InternalException The InternalException signals that internal error has occurred. + * @throws IllegalDiameterStateException The IllegalStateException signals that session has incorrect state (invalid). + * @throws RouteException The NoRouteException signals that no route exist for a given realm. + * @throws OverloadException The OverloadException signals that destination host is overloaded. + */ + void doRequestTimeout(ClientRoSession session, Message msg, Peer peer) + throws InternalException, IllegalDiameterStateException, RouteException, OverloadException; + + /** + * Notifies this ClientRoSessionListener that message cannot be delivered due to lack of remote peers being available at the moment. + * + * @param cause root cause containing detailed description + * @param session parent application session (FSM) + * @param msg request object + * @param peer last remote peer that has been selected for routing + * @throws InternalException The InternalException signals that internal error has occurred. + * @throws IllegalDiameterStateException The IllegalStateException signals that session has incorrect state (invalid). + * @throws RouteException The NoRouteException signals that no route exist for a given realm. + * @throws OverloadException The OverloadException signals that destination host is overloaded. + */ + void doPeerUnavailability(ClientRoSession session, Message msg, Peer peer, RouteException cause) + throws InternalException, IllegalDiameterStateException, RouteException, OverloadException; + /** * Provides with default value of DDFH AVP - this is used when AVP is not present or send * operation fails for some reason.
diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/IContainer.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/IContainer.java index 4f881ee13..8b623a3c0 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/IContainer.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/IContainer.java @@ -49,6 +49,7 @@ import org.jdiameter.api.Configuration; import org.jdiameter.api.IllegalDiameterStateException; import org.jdiameter.api.NetworkReqListener; +import org.jdiameter.api.NoMorePeersAvailableException; import org.jdiameter.api.RouteException; import org.jdiameter.api.Stack; import org.jdiameter.common.api.concurrent.IConcurrentFactory; @@ -102,7 +103,7 @@ public interface IContainer extends Stack { * @throws IllegalDiameterStateException * @throws IOException */ - void sendMessage(IMessage session) throws RouteException, AvpDataException, IllegalDiameterStateException, IOException; + void sendMessage(IMessage session) throws RouteException, NoMorePeersAvailableException, AvpDataException, IllegalDiameterStateException, IOException; /** diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/IMessage.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/IMessage.java index ecdb94c7a..5bbee0696 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/IMessage.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/IMessage.java @@ -57,6 +57,8 @@ * @author erick.svenson@yahoo.com * @author Alexandre Mendonca * @author Bartosz Baranowski + * @author Grzegorz Figiel (ProIDS sp. z o.o.) + * @version 1.5.0.1 */ public interface IMessage extends IRequest, IAnswer { @@ -82,36 +84,42 @@ public interface IMessage extends IRequest, IAnswer { /** * Return state of message + * * @return state of message */ int getState(); /** * Set new state + * * @param newState new state value */ void setState(int newState); /** * Return header applicationId + * * @return header applicationId */ long getHeaderApplicationId(); /** * Set header message application id + * * @param applicationId header message application id */ void setHeaderApplicationId(long applicationId); /** * Return flags as inteher + * * @return flags as inteher */ int getFlags(); /** * Create timer for request timout procedure + * * @param scheduledFacility timer facility * @param timeOut value of timeout * @param timeUnit time unit @@ -142,48 +150,56 @@ public interface IMessage extends IRequest, IAnswer { /** * Return attached peer + * * @return attached peer */ IPeer getPeer(); /** * Attach message to peer + * * @param peer attached peer */ void setPeer(IPeer peer); /** * Return application id + * * @return application id */ ApplicationId getSingleApplicationId(); /** * Return application id + * * @return application id */ ApplicationId getSingleApplicationId(long id); /** * Check timeout + * * @return true if request has timeout */ boolean isTimeOut(); /** * Set event listener + * * @param listener event listener */ void setListener(IEventListener listener); /** * Return event listener + * * @return event listener */ IEventListener getEventListener(); /** * Return duplication key of message + * * @return duplication key of message */ String getDuplicationKey(); @@ -198,7 +214,43 @@ public interface IMessage extends IRequest, IAnswer { /** * Create clone object + * * @return clone */ Object clone(); + + /** + * Tells if there are any timers set to monitor potential retransmissions + * + * @return true if potential retransmissions will be handled + */ + boolean isRetransmissionSupervised(); + + /** + * Marks that message to be under supervision timers guarding retransmissions + * + * @param arg true if supervision is active + */ + void setRetransmissionSupervised(boolean arg); + + /** + * Tells if the number of allowed retransmissions for this message is + * already exceeded or not. + * + * @return false if no more retransmissions are allowed + */ + boolean isRetransmissionAllowed(); + + /** + * Sets the number of allowed retransmissions for this message that can be performed + * in case of failure detection. + * + * @param arg number of allowed retransmissions + */ + void setNumberOfRetransAllowed(int arg); + + /** + * Decrements the number of allowed retransmissions for this message. + */ + void decrementNumberOfRetransAllowed(); } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/ISessionFactory.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/ISessionFactory.java index 5be96d93d..a559771da 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/ISessionFactory.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/ISessionFactory.java @@ -103,4 +103,11 @@ T getNewAppSession(String sessionId, ApplicationId applic */ IContainer getContainer(); + /** + * Tells whether session persistent routing is enabled for session created by this factory. + * + * @return true if enabled + */ + boolean isSessionPersistenceEnabled(); + } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/controller/IPeerTable.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/controller/IPeerTable.java index c4998c36a..38aff969c 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/controller/IPeerTable.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/controller/IPeerTable.java @@ -48,6 +48,7 @@ import org.jdiameter.api.AvpDataException; import org.jdiameter.api.IllegalDiameterStateException; import org.jdiameter.api.NetworkReqListener; +import org.jdiameter.api.NoMorePeersAvailableException; import org.jdiameter.api.PeerTable; import org.jdiameter.api.RouteException; import org.jdiameter.client.api.IAssembler; @@ -94,7 +95,7 @@ public interface IPeerTable extends PeerTable { * @throws RouteException * @throws AvpDataException */ - void sendMessage(IMessage message) throws IllegalDiameterStateException, IOException, RouteException, AvpDataException; + void sendMessage(IMessage message) throws IllegalDiameterStateException, IOException, RouteException, NoMorePeersAvailableException, AvpDataException; /** * Register session lister diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/parser/IMessageParser.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/parser/IMessageParser.java index a8254d19c..c319789cb 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/parser/IMessageParser.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/api/parser/IMessageParser.java @@ -47,7 +47,7 @@ import org.jdiameter.api.AvpDataException; import org.jdiameter.client.api.IMessage; -/** + /** * Basic interface for diameter message parsers. * * @author erick.svenson@yahoo.com @@ -66,7 +66,6 @@ public interface IMessageParser { /** * Create message from byte array - * @param data message byte array * @return instance of message * @throws AvpDataException */ 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..b8746da71 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 @@ -45,6 +45,7 @@ import org.jdiameter.api.AvpDataException; import org.jdiameter.api.InternalException; import org.jdiameter.api.RouteException; +import org.jdiameter.api.Wrapper; import org.jdiameter.client.api.IAnswer; import org.jdiameter.client.api.IMessage; import org.jdiameter.client.api.IRequest; @@ -54,12 +55,13 @@ /** * This class describe Router functionality + * This class describes Router functionality * * @author erick.svenson@yahoo.com * @author Alexandre Mendonca * @author Bartosz Baranowski */ -public interface IRouter { +public interface IRouter extends Wrapper { /** * Return peer from inner peer table by predefined parameters. Fetches peer based on message content, that is HBH or realm/host avp contents. @@ -130,4 +132,9 @@ public interface IRouter { */ boolean updateRoute(IRequest message) throws RouteException, AvpDataException; + /** + * Tells whether session persistence is preserved while making routing decisions among peers. + * @return true if sticky sessions are applied + */ + boolean isSessionAware(); } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/BaseSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/BaseSessionImpl.java index cf81b8fb3..da588a943 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/BaseSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/BaseSessionImpl.java @@ -43,7 +43,9 @@ package org.jdiameter.client.impl; import static org.jdiameter.client.impl.helpers.Parameters.MessageTimeOut; +import static org.jdiameter.client.impl.helpers.Parameters.SessionTimeOut; +import java.io.Serializable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -65,10 +67,14 @@ import org.jdiameter.api.OverloadException; import org.jdiameter.api.Request; import org.jdiameter.api.RouteException; +import org.jdiameter.client.api.IAssembler; import org.jdiameter.client.api.IContainer; import org.jdiameter.client.api.IEventListener; import org.jdiameter.client.api.IMessage; import org.jdiameter.client.api.parser.IMessageParser; +import org.jdiameter.common.api.timer.ITimerFacility; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Implementation for {@link BaseSession}. @@ -79,15 +85,21 @@ */ public abstract class BaseSessionImpl implements BaseSession { + private static final Logger logger = LoggerFactory.getLogger(BaseSessionImpl.class); + protected final long creationTime = System.currentTimeMillis(); protected long lastAccessedTime = creationTime; protected boolean isValid = true; protected String sessionId; + protected long maxIdleTime = 0; + protected transient IContainer container; protected transient IMessageParser parser; protected NetworkReqListener reqListener; + protected Serializable istTimerId; + @Override public long getCreationTime() { return creationTime; @@ -98,6 +110,31 @@ public long getLastAccessedTime() { return lastAccessedTime; } + protected long setLastAccessTime() { + lastAccessedTime = System.currentTimeMillis(); + if (sessionId != null) { + maxIdleTime = container.getConfiguration().getLongValue(SessionTimeOut.ordinal(), (Long) SessionTimeOut.defValue()); + if (maxIdleTime > 0) { + IAssembler assembler = container.getAssemblerFacility(); + ITimerFacility timerFacility = assembler.getComponentInstance(ITimerFacility.class); + if (istTimerId != null) { + timerFacility.cancel(istTimerId); + } + istTimerId = timerFacility.schedule(this.getSessionId(), IDLE_SESSION_TIMER_NAME, maxIdleTime); + } + } + return lastAccessedTime; + } + + public void onTimer(String timerName) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + if (!isValid() || (maxIdleTime > 0 && System.currentTimeMillis() - getLastAccessedTime() >= maxIdleTime)) { + logger.debug("Terminating idle/invalid application session [{}] with SID[{}]", this, getSessionId()); + this.release(); + } + } + } + @Override public boolean isValid() { return isValid; @@ -138,7 +175,7 @@ protected void genericSend(Message message, EventListener listener) protected void genericSend(Message aMessage, EventListener listener, long timeout, TimeUnit timeUnit) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { if (isValid) { - lastAccessedTime = System.currentTimeMillis(); + setLastAccessTime(); IMessage message = (IMessage) aMessage; IEventListener localListener = createListenerWrapper(listener); @@ -402,7 +439,7 @@ public boolean isValid() { @SuppressWarnings("unchecked") public void receivedSuccessMessage(Request request, Answer answer) { if (isValid) { - session.lastAccessedTime = System.currentTimeMillis(); + session.setLastAccessTime(); listener.receivedSuccessMessage(request, answer); } } @@ -411,7 +448,7 @@ public void receivedSuccessMessage(Request request, Answer answer) { @SuppressWarnings("unchecked") public void timeoutExpired(Request message) { if (isValid) { - session.lastAccessedTime = System.currentTimeMillis(); + session.setLastAccessTime(); listener.timeoutExpired(message); } } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/RawSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/RawSessionImpl.java index 6c0c272c2..1b9d34cf9 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/RawSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/RawSessionImpl.java @@ -75,7 +75,7 @@ public class RawSessionImpl extends BaseSessionImpl implements RawSession { @Override public Message createMessage(int commandCode, ApplicationId appId, Avp... avps) { if ( isValid ) { - lastAccessedTime = System.currentTimeMillis(); + setLastAccessTime(); IMessage m = parser.createEmptyMessage(commandCode, getAppId(appId)); m.getAvps().addAvp(avps); appendAppId(appId, m); @@ -88,7 +88,7 @@ public Message createMessage(int commandCode, ApplicationId appId, Avp... avps) @Override public Message createMessage(int commandCode, ApplicationId appId, long hopByHopIdentifier, long endToEndIdentifier, Avp... avps) { if ( isValid ) { - lastAccessedTime = System.currentTimeMillis(); + setLastAccessTime(); IMessage m = parser.createEmptyMessage(commandCode, getAppId(appId)); if (hopByHopIdentifier >= 0) { m.setHopByHopIdentifier(-hopByHopIdentifier); @@ -107,7 +107,7 @@ public Message createMessage(int commandCode, ApplicationId appId, long hopByHop @Override public Message createMessage(Message message, boolean copyAvps) { if ( isValid ) { - lastAccessedTime = System.currentTimeMillis(); + setLastAccessTime(); IMessage newMessage = null; IMessage inner = (IMessage) message; if (copyAvps) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/SessionFactoryImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/SessionFactoryImpl.java index 12b4e4c1a..6492f9d19 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/SessionFactoryImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/SessionFactoryImpl.java @@ -55,6 +55,7 @@ import org.jdiameter.client.api.StackState; import org.jdiameter.client.impl.helpers.UIDGenerator; import org.jdiameter.common.api.app.IAppSessionFactory; +import org.jdiameter.common.api.data.IRoutingAwareSessionDatasource; import org.jdiameter.common.api.data.ISessionDatasource; /** @@ -68,6 +69,8 @@ public class SessionFactoryImpl implements ISessionFactory { private IContainer stack; + private boolean isSessionPersistenceEnabled = false; + @SuppressWarnings("rawtypes") private Map appFactories = new ConcurrentHashMap(); private ISessionDatasource dataSource; @@ -77,6 +80,10 @@ public class SessionFactoryImpl implements ISessionFactory { public SessionFactoryImpl(IContainer stack) { this.stack = stack; this.dataSource = this.stack.getAssemblerFacility().getComponentInstance(ISessionDatasource.class); + + if(dataSource instanceof IRoutingAwareSessionDatasource) { + isSessionPersistenceEnabled = true; + } } @Override @@ -184,4 +191,9 @@ public IContainer getContainer() { return stack; } + @Override + public boolean isSessionPersistenceEnabled() { + return isSessionPersistenceEnabled; + } + } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/SessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/SessionImpl.java index 4d6dcbae0..8dbead2c0 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/SessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/SessionImpl.java @@ -62,6 +62,7 @@ import org.jdiameter.client.api.ISession; import org.jdiameter.client.api.parser.IMessageParser; import org.jdiameter.common.api.data.ISessionDatasource; +import org.jdiameter.common.api.timer.ITimerFacility; /** * Implementation for {@link ISession} @@ -115,7 +116,7 @@ public NetworkReqListener getReqListener() { @Override public Request createRequest(int commandCode, ApplicationId appId, String destRealm) { if (isValid) { - lastAccessedTime = System.currentTimeMillis(); + setLastAccessTime(); IRequest m = parser.createEmptyMessage(IRequest.class, commandCode, getAppId(appId)); m.setNetworkRequest(false); m.setRequest(true); @@ -135,7 +136,7 @@ public Request createRequest(int commandCode, ApplicationId appId, String destRe @Override public Request createRequest(int commandCode, ApplicationId appId, String destRealm, String destHost) { if (isValid) { - lastAccessedTime = System.currentTimeMillis(); + setLastAccessTime(); IRequest m = parser.createEmptyMessage(IRequest.class, commandCode, getAppId(appId)); m.setNetworkRequest(false); m.setRequest(true); @@ -158,7 +159,7 @@ public Request createRequest(int commandCode, ApplicationId appId, String destRe @Override public Request createRequest(Request prevRequest) { if (isValid) { - lastAccessedTime = System.currentTimeMillis(); + setLastAccessTime(); IRequest request = parser.createEmptyMessage(Request.class, (IMessage) prevRequest); request.setRequest(true); request.setNetworkRequest(false); @@ -174,6 +175,9 @@ public Request createRequest(Request prevRequest) { public void release() { isValid = false; if (container != null) { + if (istTimerId != null) { + container.getAssemblerFacility().getComponentInstance(ITimerFacility.class).cancel(istTimerId); + } container.removeSessionListener(sessionId); // FIXME container.getAssemblerFacility().getComponentInstance(ISessionDatasource.class).removeSession(sessionId); diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/StackImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/StackImpl.java index 82712402d..65e80b8b7 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/StackImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/StackImpl.java @@ -70,6 +70,7 @@ import org.jdiameter.api.PeerTable; import org.jdiameter.api.RouteException; import org.jdiameter.api.SessionFactory; +import org.jdiameter.api.SessionPersistenceStorage; import org.jdiameter.api.app.StateChangeListener; import org.jdiameter.api.validation.Dictionary; import org.jdiameter.api.validation.ValidatorLevel; @@ -404,6 +405,15 @@ public MetaData getMetaData() { return assembler.getComponentInstance(IMetaData.class); } + @Override + public SessionPersistenceStorage getSessionPersistenceStorage() { + if (state == StackState.IDLE) { + throw new IllegalStateException("Session storage not defined"); + } + ISessionDatasource sds = assembler.getComponentInstance(ISessionDatasource.class); + return sds instanceof SessionPersistenceStorage ? (SessionPersistenceStorage) sds : null; + } + @Override @SuppressWarnings("unchecked") public T getSession(String sessionId, Class clazz) throws InternalException { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/acc/ClientAccSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/acc/ClientAccSessionImpl.java index 19f1b9c7e..5708ce78a 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/acc/ClientAccSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/acc/ClientAccSessionImpl.java @@ -622,7 +622,10 @@ protected void processInterimIntervalAvp(StateEvent event) throws InternalExcept */ @Override public void onTimer(String timerName) { - if (timerName.equals(TIMER_NAME_INTERIM)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TIMER_NAME_INTERIM)) { if (context != null) { try { Request interimRecord = createInterimRecord(); @@ -641,7 +644,7 @@ public void onTimer(String timerName) { } } else { - //....? + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); } } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/auth/ClientAuthSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/auth/ClientAuthSessionImpl.java index 9c118bbc4..3564e753b 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/auth/ClientAuthSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/auth/ClientAuthSessionImpl.java @@ -594,7 +594,10 @@ protected void cancelTsTimer() { */ @Override public void onTimer(String timerName) { - if (timerName.equals(TIMER_NAME_TS)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TIMER_NAME_TS)) { try { sendAndStateLock.lock(); sessionData.setTsTimerId(null); @@ -611,6 +614,9 @@ public void onTimer(String timerName) { sendAndStateLock.unlock(); } } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/cca/ClientCCASessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/cca/ClientCCASessionImpl.java index 955f0eeba..3185312f1 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/cca/ClientCCASessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/cca/ClientCCASessionImpl.java @@ -77,6 +77,7 @@ import org.jdiameter.common.api.app.cca.ClientCCASessionState; import org.jdiameter.common.api.app.cca.ICCAMessageFactory; import org.jdiameter.common.api.app.cca.IClientCCASessionContext; +import org.jdiameter.common.api.data.ISessionDatasource; import org.jdiameter.common.impl.app.AppAnswerEventImpl; import org.jdiameter.common.impl.app.AppEventImpl; import org.jdiameter.common.impl.app.AppRequestEventImpl; @@ -121,11 +122,13 @@ public class ClientCCASessionImpl extends AppCCASessionImpl implements ClientCCA private static final int DDFH_TERMINATE_OR_BUFFER = 0; private static final int DDFH_CONTINUE = 1; - // CC-Request-Type Values --------------------------------------------------- + // Requested-Action Values -------------------------------------------------- private static final int DIRECT_DEBITING = 0; private static final int REFUND_ACCOUNT = 1; private static final int CHECK_BALANCE = 2; private static final int PRICE_ENQUIRY = 3; + + // CC-Request-Type --------------------------------------------------------- private static final int EVENT_REQUEST = 4; // Error Codes -------------------------------------------------------------- @@ -149,7 +152,30 @@ public class ClientCCASessionImpl extends AppCCASessionImpl implements ClientCCA public ClientCCASessionImpl(IClientCCASessionData data, ICCAMessageFactory fct, ISessionFactory sf, ClientCCASessionListener lst, IClientCCASessionContext ctx, StateChangeListener stLst) { - super(sf, data); + super(null, sf, data); + if (lst == null) { + throw new IllegalArgumentException("Listener can not be null"); + } + if (data == null) { + throw new IllegalArgumentException("SessionData can not be null"); + } + if (fct.getApplicationIds() == null) { + throw new IllegalArgumentException("ApplicationId can not be less than zero"); + } + + this.sessionData = data; + this.context = ctx; + + this.authAppIds = fct.getApplicationIds(); + this.listener = lst; + this.factory = fct; + super.addStateChangeNotification(stLst); + + } + + public ClientCCASessionImpl(IClientCCASessionData data, ICCAMessageFactory fct, ISessionDatasource sds, ISessionFactory sf, ClientCCASessionListener lst, + IClientCCASessionContext ctx, StateChangeListener stLst) { + super(sds, sf, data); if (lst == null) { throw new IllegalArgumentException("Listener can not be null"); } @@ -351,6 +377,11 @@ protected boolean handleEventForSessionBased(StateEvent event) throws InternalEx // New State: OPEN stopTx(); setState(ClientCCASessionState.OPEN); + //Session persistence record shall be created after a peer had answered the + //first (initial) request for that session + if (sf.isSessionPersistenceEnabled()) { + initSessionPersistenceContext(localEvent.getRequest(), localEvent.getAnswer()); + } } else if (isProvisional(resultCode) || isFailure(resultCode)) { handleFailureMessage((JCreditControlAnswer) answer, (JCreditControlRequest) localEvent.getRequest(), eventType); @@ -626,6 +657,18 @@ public void onTimer(String timerName) { if (timerName.equals(TX_TIMER_NAME)) { new TxTimerTask(this, this.sessionData.getTxTimerRequest()).run(); } + else { + try { + sendAndStateLock.lock(); + super.onTimer(timerName); + } + catch (Exception ex) { + logger.error("Cannot properly handle timer expiry", ex); + } + finally { + sendAndStateLock.unlock(); + } + } } protected void setState(ClientCCASessionState newState) { @@ -647,6 +690,13 @@ protected void setState(ClientCCASessionState newState, boolean release) { this.release(); } stopTx(); + + if (sf.isSessionPersistenceEnabled()) { + if (!release) { + String oldPeer = flushSessionPersistenceContext(); + logger.debug("Session state reset, routing context for peer [{}] was removed from session [{}]", oldPeer, this.getSessionId()); + } + } } } catch (Exception e) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/cxdx/CxDxClientSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/cxdx/CxDxClientSessionImpl.java index b7226ee1f..a9b2ea28e 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/cxdx/CxDxClientSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/cxdx/CxDxClientSessionImpl.java @@ -369,7 +369,10 @@ protected void setState(CxDxSessionState newState) { */ @Override public void onTimer(String timerName) { - if (timerName.equals(CxDxSession.TIMER_NAME_MSG_TIMEOUT)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(CxDxSession.TIMER_NAME_MSG_TIMEOUT)) { try { sendAndStateLock.lock(); try { @@ -385,6 +388,9 @@ public void onTimer(String timerName) { sendAndStateLock.unlock(); } } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/gq/GqClientSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/gq/GqClientSessionImpl.java index 02f365002..04443f1c3 100755 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/gq/GqClientSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/gq/GqClientSessionImpl.java @@ -562,7 +562,10 @@ protected void cancelTsTimer() { */ @Override public void onTimer(String timerName) { - if (timerName.equals(TIMER_NAME_TS)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TIMER_NAME_TS)) { try { sendAndStateLock.lock(); sessionData.setTsTimerId(null); @@ -579,6 +582,9 @@ public void onTimer(String timerName) { sendAndStateLock.unlock(); } } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } protected AbortSessionAnswer createAbortSessionAnswer(Answer answer) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/gx/ClientGxSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/gx/ClientGxSessionImpl.java index c492e970c..028a7fb38 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/gx/ClientGxSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/gx/ClientGxSessionImpl.java @@ -611,9 +611,15 @@ protected void stopTx() { */ @Override public void onTimer(String timerName) { - if (timerName.equals(TX_TIMER_NAME)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TX_TIMER_NAME)) { new TxTimerTask(this, this.sessionData.getTxTimerRequest()).run(); } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } protected void setState(ClientGxSessionState newState) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/rf/ClientRfSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/rf/ClientRfSessionImpl.java index 04aec79b0..c156e8dd9 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/rf/ClientRfSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/rf/ClientRfSessionImpl.java @@ -608,7 +608,10 @@ protected void processInterimIntervalAvp(StateEvent event) throws InternalExcept */ @Override public void onTimer(String timerName) { - if (timerName.equals(TIMER_NAME_INTERIM)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TIMER_NAME_INTERIM)) { if (context != null) { try { Request interimRecord = createInterimRecord(); @@ -626,6 +629,9 @@ public void onTimer(String timerName) { } } } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } private void startInterimTimer(long v) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/ClientRoSessionDataLocalImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/ClientRoSessionDataLocalImpl.java index 8960e7d1c..fd222817c 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/ClientRoSessionDataLocalImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/ClientRoSessionDataLocalImpl.java @@ -49,18 +49,18 @@ import org.jdiameter.common.api.app.ro.ClientRoSessionState; /** - * * @author Bartosz Baranowski * @author Alexandre Mendonca + * @author Grzegorz Figiel (ProIDS sp. z o.o.) */ public class ClientRoSessionDataLocalImpl extends AppSessionDataLocalImpl implements IClientRoSessionData { protected boolean isEventBased = true; protected boolean requestTypeSet = false; protected ClientRoSessionState state = ClientRoSessionState.IDLE; - protected Serializable txTimerId; - //protected JCreditControlRequest txTimerRequest; - protected Request txTimerRequest; + protected Serializable txTimerId = null; + protected Serializable retransmissionTimerId = null; + protected Request txTimerRequest = null; // Event Based Buffer //protected Message buffer = null; @@ -70,6 +70,7 @@ public class ClientRoSessionDataLocalImpl extends AppSessionDataLocalImpl implem protected int gatheredCCFH = NON_INITIALIZED; protected int gatheredDDFH = NON_INITIALIZED; + protected int gatheredCCSF = NON_INITIALIZED; /** * @@ -127,6 +128,16 @@ public void setTxTimerRequest(Request txTimerRequest) { this.txTimerRequest = txTimerRequest; } + @Override + public Serializable getRetransmissionTimerId() { + return retransmissionTimerId; + } + + @Override + public void setRetransmissionTimerId(Serializable retransmissionTimerId) { + this.retransmissionTimerId = retransmissionTimerId; + } + @Override public Request getBuffer() { return buffer; @@ -167,4 +178,14 @@ public void setGatheredDDFH(int gatheredDDFH) { this.gatheredDDFH = gatheredDDFH; } + @Override + public int getGatheredCCSF() { + return this.gatheredCCSF; + } + + @Override + public void setGatheredCCSF(int gatheredCCSF) { + this.gatheredCCSF = gatheredCCSF; + } + } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/ClientRoSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/ClientRoSessionImpl.java index ca41deedb..b8139f9e2 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/ClientRoSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/ClientRoSessionImpl.java @@ -52,12 +52,14 @@ import java.util.concurrent.locks.ReentrantLock; import org.jdiameter.api.Answer; +import org.jdiameter.api.Avp; import org.jdiameter.api.AvpDataException; import org.jdiameter.api.EventListener; import org.jdiameter.api.IllegalDiameterStateException; import org.jdiameter.api.InternalException; import org.jdiameter.api.Message; import org.jdiameter.api.NetworkReqListener; +import org.jdiameter.api.NoMorePeersAvailableException; import org.jdiameter.api.OverloadException; import org.jdiameter.api.Request; import org.jdiameter.api.RouteException; @@ -77,22 +79,30 @@ import org.jdiameter.client.api.ISessionFactory; import org.jdiameter.client.api.parser.IMessageParser; import org.jdiameter.client.api.parser.ParseException; +import org.jdiameter.client.api.router.IRouter; import org.jdiameter.client.impl.app.ro.Event.Type; import org.jdiameter.common.api.app.IAppSessionState; import org.jdiameter.common.api.app.ro.ClientRoSessionState; import org.jdiameter.common.api.app.ro.IClientRoSessionContext; import org.jdiameter.common.api.app.ro.IRoMessageFactory; +import org.jdiameter.common.api.data.ISessionDatasource; import org.jdiameter.common.impl.app.AppAnswerEventImpl; import org.jdiameter.common.impl.app.AppRequestEventImpl; +import org.jdiameter.common.impl.app.auth.ReAuthAnswerImpl; import org.jdiameter.common.impl.app.ro.AppRoSessionImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.jdiameter.client.impl.helpers.Parameters.RetransmissionRequiredResCodes; +import static org.jdiameter.client.impl.helpers.Parameters.MessageTimeOut; +import static org.jdiameter.client.impl.helpers.Parameters.TxTimeOut; + /** * Client Credit-Control Application session implementation * * @author Alexandre Mendonca * @author Bartosz Baranowski + * @author Grzegorz Figiel (ProIDS sp. z o.o.) */ public class ClientRoSessionImpl extends AppRoSessionImpl implements ClientRoSession, NetworkReqListener, EventListener { @@ -107,13 +117,18 @@ public class ClientRoSessionImpl extends AppRoSessionImpl implements ClientRoSes protected transient ClientRoSessionListener listener; protected transient IClientRoSessionContext context; protected transient IMessageParser parser; + protected transient IRouter router; // Tx Timer ----------------------------------------------------------------- - protected static final String TX_TIMER_NAME = "Ro_CLIENT_TX_TIMER"; - protected static final long TX_TIMER_DEFAULT_VALUE = 30 * 60 * 1000; // miliseconds + protected final long txTimerVal; - protected long[] authAppIds = new long[] { 4 }; + // Response Timer ----------------------------------------------------------- + protected static final String RETRANSMISSION_TIMER_NAME = "Ro_CLIENT_RETRANSMISSION_TIMER"; + protected final long retransmissionTimerVal; + + protected long[] authAppIds = new long[]{4}; + protected final Set retrRequiredErrorCodes; // Requested Action + Credit-Control and Direct-Debiting Failure-Handling --- protected static final int CCFH_TERMINATE = 0; @@ -123,11 +138,13 @@ public class ClientRoSessionImpl extends AppRoSessionImpl implements ClientRoSes private static final int DDFH_TERMINATE_OR_BUFFER = 0; private static final int DDFH_CONTINUE = 1; - // CC-Request-Type Values --------------------------------------------------- + // Requested-Action Values -------------------------------------------------- private static final int DIRECT_DEBITING = 0; private static final int REFUND_ACCOUNT = 1; private static final int CHECK_BALANCE = 2; private static final int PRICE_ENQUIRY = 3; + + // CC-Request-Type --------------------------------------------------------- private static final int EVENT_REQUEST = 4; // Error Codes -------------------------------------------------------------- @@ -152,9 +169,9 @@ public class ClientRoSessionImpl extends AppRoSessionImpl implements ClientRoSes // Session Based Queue protected ArrayList eventQueue = new ArrayList(); - public ClientRoSessionImpl(IClientRoSessionData sessionData, IRoMessageFactory fct, ISessionFactory sf, ClientRoSessionListener lst, - IClientRoSessionContext ctx, StateChangeListener stLst) { - super(sf, sessionData); + public ClientRoSessionImpl(IClientRoSessionData sessionData, IRoMessageFactory fct, ISessionDatasource sds, ISessionFactory sf, ClientRoSessionListener + lst, IClientRoSessionContext ctx, StateChangeListener stLst) { + super(sds, sf, sessionData); if (lst == null) { throw new IllegalArgumentException("Listener can not be null"); } @@ -171,10 +188,19 @@ public ClientRoSessionImpl(IClientRoSessionData sessionData, IRoMessageFactory f this.factory = fct; IContainer icontainer = sf.getContainer(); + this.parser = icontainer.getAssemblerFacility().getComponentInstance(IMessageParser.class); + this.router = icontainer.getAssemblerFacility().getComponentInstance(IRouter.class); + this.txTimerVal = icontainer.getConfiguration().getLongValue(TxTimeOut.ordinal(), (Long) TxTimeOut.defValue()); + this.retransmissionTimerVal = icontainer.getConfiguration().getLongValue(MessageTimeOut.ordinal(), (Long) MessageTimeOut.defValue()); - super.addStateChangeNotification(stLst); + Set tmpErrCodes = new HashSet<>(); + for (int val : icontainer.getConfiguration().getIntArrayValue(RetransmissionRequiredResCodes.ordinal(), new int[0])) { + tmpErrCodes.add(new Long(val)); + } + this.retrRequiredErrorCodes = Collections.unmodifiableSet(tmpErrCodes); + super.addStateChangeNotification(stLst); } protected int getLocalCCFH() { @@ -185,9 +211,13 @@ protected int getLocalDDFH() { return sessionData.getGatheredDDFH() >= 0 ? sessionData.getGatheredDDFH() : context.getDefaultDDFHValue(); } - @Override - public void sendCreditControlRequest(RoCreditControlRequest request) - throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { + protected boolean isSessionFailoverSupported() { + return sessionData.getGatheredCCSF() > 0; + } + + + public void sendCreditControlRequest(RoCreditControlRequest request) throws InternalException, IllegalDiameterStateException, RouteException, + OverloadException { try { extractFHAVPs(request, null); this.handleEvent(new Event(true, request, null)); @@ -237,7 +267,7 @@ protected boolean handleEventForEventBased(StateEvent event) throws InternalExce // Event: Client or device requests a one-time service // Action: Send CC event request, start Tx // New State: PENDING_E - startTx((RoCreditControlRequest) localEvent.getRequest()); + startTx(localEvent.getRequest().getMessage()); setState(ClientRoSessionState.PENDING_EVENT); try { dispatchEvent(localEvent.getRequest()); @@ -279,6 +309,7 @@ protected boolean handleEventForEventBased(StateEvent event) throws InternalExce } break; case Tx_TIMER_FIRED: + deliverRequestTxTimeout(localEvent.getRequest().getMessage()); handleTxExpires(localEvent.getRequest().getMessage()); break; default: @@ -335,11 +366,17 @@ protected boolean handleEventForSessionBased(StateEvent event) throws InternalEx // Event: Client or device requests access/service // Action: Send CC initial request, start Tx // New State: PENDING_I - startTx((RoCreditControlRequest) localEvent.getRequest()); + startTx(localEvent.getRequest().getMessage()); setState(ClientRoSessionState.PENDING_INITIAL); + // RFC 4006: For new credit-control sessions, failover to an alternative + // credit-control server SHOULD be performed if possible. + sessionData.setGatheredCCSF(IRoMessageFactory.SESSION_FAILOVER_SUPPORTED_VALUE); try { dispatchEvent(localEvent.getRequest()); } + catch (NoMorePeersAvailableException nmpae) { + handlePeerUnavailability(localEvent.getRequest().getMessage(), nmpae); + } catch (Exception e) { // This handles failure to send in PendingI state in FSM table handleSendFailure(e, eventType, localEvent.getRequest().getMessage()); @@ -355,6 +392,19 @@ protected boolean handleEventForSessionBased(StateEvent event) throws InternalEx AppAnswerEvent answer = (AppAnswerEvent) localEvent.getAnswer(); switch (eventType) { case RECEIVED_INITIAL_ANSWER: + + Message message = localEvent.getAnswer().getMessage(); + int ccSessionFailover = IRoMessageFactory.SESSION_FAILOVER_NOT_SUPPORTED_VALUE; + + Avp avpCcSessionFailover = message.getAvps().getAvp(Avp.CC_SESSION_FAILOVER); + if (avpCcSessionFailover != null) { + ccSessionFailover = avpCcSessionFailover.getInteger32(); + } + else { + logger.warn("Failed to fetch CC-Session-Failover"); + } + + sessionData.setGatheredCCSF(ccSessionFailover); long resultCode = answer.getResultCodeAvp().getUnsigned32(); if (isSuccess(resultCode)) { // Current State: PENDING_I @@ -363,14 +413,33 @@ protected boolean handleEventForSessionBased(StateEvent event) throws InternalEx // New State: OPEN stopTx(); setState(ClientRoSessionState.OPEN); + //Session persistence record shall be created after a peer had answered + //the first (initial) request for that session + if (sf.isSessionPersistenceEnabled()) { + initSessionPersistenceContext(localEvent.getRequest(), localEvent.getAnswer()); + } + } + else if (retrRequiredErrorCodes.contains(resultCode)) { + handleRetransmissionDueToError(eventType, (IMessage) localEvent.getRequest().getMessage()); + break; } else if (isProvisional(resultCode) || isFailure(resultCode)) { handleFailureMessage((RoCreditControlAnswer) answer, (RoCreditControlRequest) localEvent.getRequest(), eventType); } + deliverRoAnswer((RoCreditControlRequest) localEvent.getRequest(), (RoCreditControlAnswer) localEvent.getAnswer()); break; case Tx_TIMER_FIRED: - handleTxExpires(localEvent.getRequest().getMessage()); + deliverRequestTxTimeout(localEvent.getRequest().getMessage()); + if (isRetransmissionRequired()) { + handleRetransmissionDueToTimeout(eventType, localEvent.getRequest()); + } + else { + handleRetransmissionFailure((RoCreditControlRequest) localEvent.getRequest()); + } + break; + case RETRANSMISSION_TIMER_FIRED: + handleRetransmissionFailure((RoCreditControlRequest) localEvent.getRequest()); break; case SEND_UPDATE_REQUEST: case SEND_TERMINATE_REQUEST: @@ -413,11 +482,14 @@ else if (isProvisional(resultCode) || isFailure(resultCode)) { // Event: RAR received // Action: Send RAA followed by CC update request, start Tx // New State: PENDING_U - startTx((RoCreditControlRequest) localEvent.getRequest()); + startTx(localEvent.getRequest().getMessage()); setState(ClientRoSessionState.PENDING_UPDATE); try { dispatchEvent(localEvent.getRequest()); } + catch (NoMorePeersAvailableException nmpae) { + handlePeerUnavailability(localEvent.getRequest().getMessage(), nmpae); + } catch (Exception e) { // This handles failure to send in PendingI state in FSM table handleSendFailure(e, eventType, localEvent.getRequest().getMessage()); @@ -438,10 +510,16 @@ else if (isProvisional(resultCode) || isFailure(resultCode)) { // Event: User service terminated // Action: Send CC termination request // New State: PENDING_T + + // In all cases start Tx in order to assure failover + startTx(localEvent.getRequest().getMessage()); setState(ClientRoSessionState.PENDING_TERMINATION); try { dispatchEvent(localEvent.getRequest()); } + catch (NoMorePeersAvailableException nmpae) { + handlePeerUnavailability(localEvent.getRequest().getMessage(), nmpae); + } catch (Exception e) { handleSendFailure(e, eventType, localEvent.getRequest().getMessage()); } @@ -468,6 +546,19 @@ else if (isProvisional(resultCode) || isFailure(resultCode)) { answer = (AppAnswerEvent) localEvent.getAnswer(); switch (eventType) { case RECEIVED_UPDATE_ANSWER: + + Message message = localEvent.getAnswer().getMessage(); + int ccSessionFailover = IRoMessageFactory.SESSION_FAILOVER_NOT_SUPPORTED_VALUE; + + Avp avpCcSessionFailover = message.getAvps().getAvp(Avp.CC_SESSION_FAILOVER); + if (avpCcSessionFailover != null) { + ccSessionFailover = avpCcSessionFailover.getInteger32(); + } + else { + logger.warn("Failed to fetch CC-Session-Failover"); + } + + sessionData.setGatheredCCSF(ccSessionFailover); long resultCode = answer.getResultCodeAvp().getUnsigned32(); if (isSuccess(resultCode)) { // Current State: PENDING_U @@ -477,15 +568,28 @@ else if (isProvisional(resultCode) || isFailure(resultCode)) { stopTx(); setState(ClientRoSessionState.OPEN); } + else if (retrRequiredErrorCodes.contains(resultCode)) { + handleRetransmissionDueToError(eventType, (IMessage) localEvent.getRequest().getMessage()); + break; + } else if (isProvisional(resultCode) || isFailure(resultCode)) { handleFailureMessage((RoCreditControlAnswer) answer, (RoCreditControlRequest) localEvent.getRequest(), eventType); } + deliverRoAnswer((RoCreditControlRequest) localEvent.getRequest(), (RoCreditControlAnswer) localEvent.getAnswer()); break; case Tx_TIMER_FIRED: - handleTxExpires(localEvent.getRequest().getMessage()); + deliverRequestTxTimeout(localEvent.getRequest().getMessage()); + if (isRetransmissionRequired()) { + handleRetransmissionDueToTimeout(eventType, localEvent.getRequest()); + } + else { + handleRetransmissionFailure((RoCreditControlRequest) localEvent.getRequest()); + } + break; + case RETRANSMISSION_TIMER_FIRED: + handleRetransmissionFailure((RoCreditControlRequest) localEvent.getRequest()); break; - case SEND_UPDATE_REQUEST: case SEND_TERMINATE_REQUEST: // Current State: PENDING_U @@ -547,9 +651,39 @@ else if (isProvisional(resultCode) || isFailure(resultCode)) { //FIXME: Alex broke this, setting back "true" ? //setState(ClientRoSessionState.IDLE, false); + Message message = localEvent.getAnswer().getMessage(); + int ccSessionFailover = IRoMessageFactory.SESSION_FAILOVER_NOT_SUPPORTED_VALUE; + + Avp avpCcSessionFailover = message.getAvps().getAvp(Avp.CC_SESSION_FAILOVER); + if (avpCcSessionFailover != null) { + ccSessionFailover = avpCcSessionFailover.getInteger32(); + } + else { + logger.warn("Failed to fetch CC-Session-Failover"); + } + + sessionData.setGatheredCCSF(ccSessionFailover); + long resultCode = ((AppAnswerEvent) localEvent.getAnswer()).getResultCodeAvp().getUnsigned32(); + if (retrRequiredErrorCodes.contains(resultCode)) { + handleRetransmissionDueToError(eventType, (IMessage) localEvent.getRequest().getMessage()); + break; + } + deliverRoAnswer((RoCreditControlRequest) localEvent.getRequest(), (RoCreditControlAnswer) localEvent.getAnswer()); setState(ClientRoSessionState.IDLE, true); break; + case Tx_TIMER_FIRED: + deliverRequestTxTimeout(localEvent.getRequest().getMessage()); + if (isRetransmissionRequired()) { + handleRetransmissionDueToTimeout(eventType, localEvent.getRequest()); + } + else { + handleRetransmissionFailure((RoCreditControlRequest) localEvent.getRequest()); + } + break; + case RETRANSMISSION_TIMER_FIRED: + handleRetransmissionFailure((RoCreditControlRequest) localEvent.getRequest()); + break; default: logger.warn("Wrong event type ({}) on state {}", eventType, state); break; @@ -596,6 +730,7 @@ public void timeoutExpired(Request request) { if (request.getCommandCode() == RoCreditControlAnswer.code) { try { sendAndStateLock.lock(); + stopTx(); handleSendFailure(null, null, request); } catch (Exception e) { @@ -607,33 +742,55 @@ public void timeoutExpired(Request request) { } } - protected void startTx(RoCreditControlRequest request) { - long txTimerValue = context.getDefaultTxTimerValue(); - if (txTimerValue < 0) { - txTimerValue = TX_TIMER_DEFAULT_VALUE; - } - stopTx(); - - logger.debug("Scheduling TX Timer {}", txTimerValue); - //this.txFuture = scheduler.schedule(new TxTimerTask(this, request), txTimerValue, TimeUnit.SECONDS); + protected void startTx(Message message) { + stopTx(false); + logger.debug("Scheduling Tx timer in [{}] ms", this.txTimerVal); try { - sessionData.setTxTimerRequest((Request) request.getMessage()); - sessionData.setTxTimerId(this.timerFacility.schedule(this.sessionData.getSessionId(), TX_TIMER_NAME, TX_TIMER_DEFAULT_VALUE)); + sessionData.setTxTimerRequest((Request) message); + sessionData.setTxTimerId(this.timerFacility.schedule(this.sessionData.getSessionId(), TX_TIMER_NAME, this.txTimerVal)); } catch (Exception e) { throw new IllegalArgumentException("Failed to store request.", e); } } - protected void stopTx() { + protected void stopTx(boolean stopDependant) { Serializable txTimerId = this.sessionData.getTxTimerId(); if (txTimerId != null) { + if (stopDependant) { + stopFailoverStopTimer(); + } + logger.debug("Stopping Tx timer [{}]", txTimerId); timerFacility.cancel(txTimerId); sessionData.setTxTimerId(null); sessionData.setTxTimerRequest(null); } } + protected void stopTx() { + stopTx(true); + } + + protected void startFailoverStopTimer() { + long timerVal = this.retransmissionTimerVal - this.txTimerVal; + if (timerVal <= 0) { + logger.warn("Value of Tx timer cannot exceed failover stop timer: [{}] vs. [{}] (taking default values)", this.txTimerVal, this.retransmissionTimerVal); + timerVal = this.txTimerVal + (((Long) MessageTimeOut.defValue()) - ((Long) TxTimeOut.defValue())); + } + logger.debug("Scheduling failover stop timer in [{}] ms", timerVal); + stopFailoverStopTimer(); + sessionData.setRetransmissionTimerId(this.timerFacility.schedule(this.sessionData.getSessionId(), RETRANSMISSION_TIMER_NAME, timerVal)); + } + + protected void stopFailoverStopTimer() { + Serializable failoverStopTimerId = this.sessionData.getRetransmissionTimerId(); + if (failoverStopTimerId != null) { + logger.debug("Stopping failover stop timer [{}]", failoverStopTimerId); + timerFacility.cancel(failoverStopTimerId); + sessionData.setRetransmissionTimerId(null); + } + } + /* (non-Javadoc) * @see org.jdiameter.common.impl.app.AppSessionImpl#onTimer(java.lang.String) */ @@ -642,6 +799,21 @@ public void onTimer(String timerName) { if (timerName.equals(TX_TIMER_NAME)) { new TxTimerTask(this, sessionData.getTxTimerRequest()).run(); } + else if (timerName.equals(RETRANSMISSION_TIMER_NAME)) { + new RetransmissionTimerTask(this, sessionData.getTxTimerRequest()).run(); + } + else { + try { + sendAndStateLock.lock(); + super.onTimer(timerName); + } + catch (Exception ex) { + logger.error("Cannot properly handle timer expiry", ex); + } + finally { + sendAndStateLock.unlock(); + } + } } protected void setState(ClientRoSessionState newState) { @@ -655,7 +827,7 @@ protected void setState(ClientRoSessionState newState, boolean release) { IAppSessionState oldState = state; sessionData.setClientRoSessionState(newState); for (StateChangeListener i : stateListeners) { - i.stateChanged(this,(Enum) oldState, (Enum) newState); + i.stateChanged(this, (Enum) oldState, newState); } if (newState == ClientRoSessionState.IDLE) { @@ -663,6 +835,13 @@ protected void setState(ClientRoSessionState newState, boolean release) { this.release(); } stopTx(); + + if (sf.isSessionPersistenceEnabled()) { + if (!release) { + String oldPeer = flushSessionPersistenceContext(); + logger.debug("Session state reset, routing context for peer [{}] was removed from session [{}]", oldPeer, this.getSessionId()); + } + } } } catch (Exception e) { @@ -693,7 +872,10 @@ public void release() { } protected void handleSendFailure(Exception e, Event.Type eventType, Message request) throws Exception { - logger.debug("Failed to send message, type: {} message: {}, failure: {}", new Object[]{eventType, request, e != null ? e.getLocalizedMessage() : ""}); + logger.warn("Failed to send message", e); + if (logger.isDebugEnabled()) { + logger.debug("Failed to send message, type: {} message: {}, failure: {}", new Object[]{eventType, request, e != null ? e.getLocalizedMessage() : ""}); + } try { ClientRoSessionState state = sessionData.getClientRoSessionState(); // Event Based ---------------------------------------------------------- @@ -1141,6 +1323,99 @@ else if (gatheredRequestedAction == REFUND_ACCOUNT) { break; } } + + this.release(); + } + + protected void handleRetransmissionFailure(RoCreditControlRequest req) { + try { + deliverRequestTimeout(req.getMessage()); + resetMessageStatus(req.getMessage()); + } + catch (InternalException e) { + logger.error("Cannot remove the expired message from either peer or rouoter tables for session [{}]", this.getSessionId()); + } + + setState(ClientRoSessionState.IDLE, true); + } + + protected void handlePeerUnavailability(Message msg, NoMorePeersAvailableException nmpae) { + logger.warn("No more peers available for sending diameter message: ", nmpae.getMessage()); + if (logger.isDebugEnabled()) { + logger.debug("nmpa exception: {}", nmpae); + } + deliverPeerUnavailabilityError(msg, nmpae); + resetMessageStatus(msg); + setState(ClientRoSessionState.IDLE, true); + } + + protected void handleRetransmission(Type eventType, IMessage msg, boolean tFlagSetting) { + msg.setReTransmitted(tFlagSetting); + if (this.sessionData.getRetransmissionTimerId() == null) { + startFailoverStopTimer(); + } + + if (sf.isSessionPersistenceEnabled()) { + String oldPeer = flushSessionPersistenceContext(); + logger.debug("Routing context for peer [{}] was removed from session [{}] due to retransmission", oldPeer, this.getSessionId()); + } + + resetMessageStatus(msg); + startTx(msg); + + try { + dispatchEvent(msg); + } + catch (NoMorePeersAvailableException nmpae) { + handlePeerUnavailability(msg, nmpae); + } + catch (Exception e1) { + logger.error("Cannot retransmit an old request", e1); + try { + handleSendFailure(e1, eventType, msg); + } + catch (Exception e2) { + logger.error("Failure handling error", e2); + } + } + } + + protected void handleRetransmissionDueToError(Type eventType, IMessage msg) { + logger.warn("Message will be retransmitted due to error response [{}] ", msg); + + try { + if (msg.isRetransmissionAllowed()) { + if (isSessionFailoverSupported()) { + handleRetransmission(eventType, msg, false); + msg.decrementNumberOfRetransAllowed(); + } + else { + handleSendFailure(new Exception("Failover unsupported for session ID: " + sessionData.getSessionId()), eventType, msg); + } + } + else { + NoMorePeersAvailableException cause = new NoMorePeersAvailableException("No more peers available for retransmission"); + cause.setSessionPersistentRoutingEnabled(router.isSessionAware()); + handlePeerUnavailability(msg, cause); + } + } + catch (Exception e) { + logger.error("Failure handling send failure error in handleRetransmissionDueToError", e); + } + } + + protected void handleRetransmissionDueToTimeout(Type eventType, AppEvent event) throws InternalException { + if (isSessionFailoverSupported()) { + handleRetransmission(eventType, (IMessage) event.getMessage(), true); + } + else { + logger.warn("Failed to send message. Failover unsupported for session ID: {}", sessionData.getSessionId()); + if (logger.isDebugEnabled()) { + logger.debug("Failed to send message, type: {} message: {}, failure: Failover unsupported for session ID: {}", new Object[]{eventType, event + .getMessage(), sessionData.getSessionId()}); + } + handleRetransmissionFailure((RoCreditControlRequest) event); + } } /** @@ -1184,6 +1459,7 @@ protected void dispatch() { } protected void deliverRoAnswer(RoCreditControlRequest request, RoCreditControlAnswer answer) { + logger.debug("Propagating answer event to listener [{}] on {} session", listener, isValid() ? "valid" : "invalid"); try { if (isValid()) { listener.doCreditControlAnswer(this, request, answer); @@ -1194,6 +1470,52 @@ protected void deliverRoAnswer(RoCreditControlRequest request, RoCreditControlAn } } + protected void deliverRAR(ReAuthRequest request) { + logger.debug("Propagating RAR event to listener [{}] on {} session", listener, isValid() ? "valid" : "invalid"); + try { + listener.doReAuthRequest(this, request); + } + catch (Exception e) { + logger.warn("Failure delivering RAR", e); + } + } + + protected void deliverRequestTxTimeout(Message msg) { + logger.debug("Propagating Tx timeout event to listener [{}] on {} session", listener, isValid() ? "valid" : "invalid"); + try { + if (isValid()) { + listener.doRequestTxTimeout(this, msg, ((IMessage) msg).getPeer()); + } + } + catch (Exception e) { + logger.warn("Failure delivering request tx timeout", e); + } + } + + protected void deliverRequestTimeout(Message msg) { + logger.debug("Propagating timeout event to listener [{}] on {} session", listener, isValid() ? "valid" : "invalid"); + try { + if (isValid()) { + listener.doRequestTimeout(this, msg, ((IMessage) msg).getPeer()); + } + } + catch (Exception e) { + logger.warn("Failure delivering request timeout", e); + } + } + + protected void deliverPeerUnavailabilityError(Message msg, NoMorePeersAvailableException cause) { + logger.debug("Propagating peer unavailability error event to listener [{}] on {} session", listener, isValid() ? "valid" : "invalid"); + try { + if (isValid()) { + listener.doPeerUnavailability(this, msg, ((IMessage) msg).getPeer(), cause); + } + } + catch (Exception e) { + logger.warn("Failure delivering peer unavailability error", e); + } + } + protected void extractFHAVPs(RoCreditControlRequest request, RoCreditControlAnswer answer) throws AvpDataException { if (answer != null) { try { @@ -1236,17 +1558,15 @@ else if (request != null) { } } - protected void deliverRAR(ReAuthRequest request) { - try { - listener.doReAuthRequest(this, request); - } - catch (Exception e) { - logger.debug("Failure delivering RAR", e); + protected void dispatchEvent(IMessage message) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { + if (message.isRequest()) { + message.setRetransmissionSupervised(true); } + session.send(message, this); } protected void dispatchEvent(AppEvent event) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { - session.send(event.getMessage(), this); + dispatchEvent((IMessage) event.getMessage()); } protected boolean isProvisional(long resultCode) { @@ -1258,7 +1578,12 @@ protected boolean isSuccess(long resultCode) { } protected boolean isFailure(long code) { - return (!isProvisional(code) && !isSuccess(code) && ((code >= 3000 && code < 6000)) && !temporaryErrorCodes.contains(code)); + return (!isProvisional(code) && !isSuccess(code) && ((code >= 3000 && /*code < 4000) || (code >= 5000 &&*/ code < 6000)) && !temporaryErrorCodes.contains + (code)); + } + + protected boolean isRetransmissionRequired() { + return (getLocalCCFH() == CCFH_CONTINUE || getLocalCCFH() == CCFH_RETRY_AND_TERMINATE); } /* (non-Javadoc) @@ -1269,7 +1594,6 @@ public boolean isReplicable() { return true; } - private class TxTimerTask implements Runnable { private ClientRoSession session = null; private Request request = null; @@ -1280,19 +1604,13 @@ private TxTimerTask(ClientRoSession session, Request request) { this.request = request; } - @Override public void run() { try { sendAndStateLock.lock(); - logger.debug("Fired TX Timer"); + logger.debug("Fired Tx timer"); sessionData.setTxTimerId(null); sessionData.setTxTimerRequest(null); - try { - context.txTimerExpired(session); - } - catch (Exception e) { - logger.debug("Failure handling TX Timer Expired", e); - } + RoCreditControlRequest req = factory.createCreditControlRequest(request); handleEvent(new Event(Event.Type.Tx_TIMER_FIRED, req, null)); } @@ -1311,6 +1629,37 @@ public void run() { } } + private class RetransmissionTimerTask implements Runnable { + private Request request = null; + + private RetransmissionTimerTask(ClientRoSession session, Request request) { + super(); + this.request = request; + } + + public void run() { + try { + sendAndStateLock.lock(); + logger.debug("Fired failover stop timer (Retransmission timout occured)"); + stopTx(false); + sessionData.setRetransmissionTimerId(null); + handleEvent(new Event(Event.Type.RETRANSMISSION_TIMER_FIRED, factory.createCreditControlRequest(request), null)); + } + catch (InternalException e) { + logger.error("Internal Exception", e); + } + catch (OverloadException e) { + logger.error("Overload Exception", e); + } + catch (Exception e) { + logger.error("Exception", e); + } + finally { + sendAndStateLock.unlock(); + } + } + } + private Message messageFromBuffer(ByteBuffer request) throws InternalException { if (request != null) { Message m; @@ -1334,15 +1683,24 @@ private ByteBuffer messageToBuffer(IMessage msg) throws InternalException { } } + private void resetMessageStatus(Message message) { + IMessage msg = (IMessage) message; + msg.clearTimer(); + msg.setState(IMessage.STATE_NOT_SENT); + if (msg.getPeer() != null) { + msg.getPeer().remMessage(msg); + } + router.garbageCollectRequestRouteInfo(msg); + } + private class RequestDelivery implements Runnable { ClientRoSession session; Request request; - @Override public void run() { try { switch (request.getCommandCode()) { - case ReAuthAnswer.code: + case ReAuthAnswerImpl.code: handleEvent(new Event(Event.Type.RECEIVED_RAR, factory.createReAuthRequest(request), null)); break; @@ -1362,14 +1720,13 @@ private class AnswerDelivery implements Runnable { Answer answer; Request request; - @Override public void run() { try { switch (request.getCommandCode()) { case RoCreditControlAnswer.code: RoCreditControlRequest _request = factory.createCreditControlRequest(request); RoCreditControlAnswer _answer = factory.createCreditControlAnswer(answer); - extractFHAVPs(null, _answer ); + extractFHAVPs(null, _answer); handleEvent(new Event(false, _request, _answer)); break; diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/Event.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/Event.java index 62ea2ea3a..0285b65e9 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/Event.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/Event.java @@ -61,7 +61,7 @@ public enum Type { SEND_INITIAL_REQUEST, RECEIVED_INITIAL_ANSWER, SEND_UPDATE_REQUEST, RECEIVED_UPDATE_ANSWER, SEND_TERMINATE_REQUEST, RECEIVED_TERMINATED_ANSWER, - RECEIVED_RAR, SEND_RAA, Tx_TIMER_FIRED, + RECEIVED_RAR,SEND_RAA, Tx_TIMER_FIRED, RETRANSMISSION_TIMER_FIRED, SEND_EVENT_REQUEST, RECEIVE_EVENT_ANSWER; } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/IClientRoSessionData.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/IClientRoSessionData.java index a9611a01a..bd8e96008 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/IClientRoSessionData.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/ro/IClientRoSessionData.java @@ -52,6 +52,7 @@ * * @author Bartosz Baranowski * @author Alexandre Mendonca + * @author Grzegorz Figiel (ProIDS sp. z o.o.) */ public interface IClientRoSessionData extends IRoSessionData { @@ -75,6 +76,10 @@ public interface IClientRoSessionData extends IRoSessionData { void setTxTimerRequest(Request txTimerRequest); + Serializable getRetransmissionTimerId(); + + void setRetransmissionTimerId(Serializable retransmissionTimerId); + Request getBuffer(); void setBuffer(Request buffer); @@ -90,4 +95,9 @@ public interface IClientRoSessionData extends IRoSessionData { int getGatheredDDFH(); void setGatheredDDFH(int gatheredDDFH); + + int getGatheredCCSF(); + + void setGatheredCCSF(int gatheredCCSF); + } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/rx/ClientRxSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/rx/ClientRxSessionImpl.java index b8c4335ab..97df5be28 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/rx/ClientRxSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/rx/ClientRxSessionImpl.java @@ -890,5 +890,11 @@ else if (!sessionData.getClientRxSessionState().equals(other.sessionData.getClie @Override public void onTimer(String timerName) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } } \ No newline at end of file diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/s13/S13ClientSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/s13/S13ClientSessionImpl.java index 5bbb6f85a..68a61a932 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/s13/S13ClientSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/s13/S13ClientSessionImpl.java @@ -209,7 +209,10 @@ protected void setState(S13SessionState newState) { @Override public void onTimer(String timerName) { - if (timerName.equals(S13Session.TIMER_NAME_MSG_TIMEOUT)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(S13Session.TIMER_NAME_MSG_TIMEOUT)) { try { sendAndStateLock.lock(); try { @@ -223,6 +226,9 @@ public void onTimer(String timerName) { sendAndStateLock.unlock(); } } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } @Override diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/s6a/S6aClientSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/s6a/S6aClientSessionImpl.java index 5e16dd246..a9aa8fc06 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/s6a/S6aClientSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/s6a/S6aClientSessionImpl.java @@ -371,7 +371,10 @@ protected void setState(S6aSessionState newState) { */ @Override public void onTimer(String timerName) { - if (timerName.equals(S6aSession.TIMER_NAME_MSG_TIMEOUT)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(S6aSession.TIMER_NAME_MSG_TIMEOUT)) { try { sendAndStateLock.lock(); try { @@ -387,6 +390,9 @@ public void onTimer(String timerName) { sendAndStateLock.unlock(); } } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/sh/ShClientSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/sh/ShClientSessionImpl.java index aa7d00c37..ca275c166 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/sh/ShClientSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/app/sh/ShClientSessionImpl.java @@ -291,7 +291,12 @@ public boolean isReplicable() { @Override public void onTimer(String timerName) { - // TODO ... + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } private class RequestDelivery implements Runnable { 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..083f92727 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 @@ -72,6 +72,7 @@ import static org.jdiameter.client.api.fsm.EventTypes.INTERNAL_ERROR; import static org.jdiameter.client.api.fsm.EventTypes.RECEIVE_MSG_EVENT; import static org.jdiameter.client.api.fsm.EventTypes.STOP_EVENT; +import static org.jdiameter.client.impl.helpers.Parameters.PeerRating; import static org.jdiameter.client.impl.helpers.Parameters.SecurityRef; import static org.jdiameter.client.impl.helpers.Parameters.UseUriAsFqdn; @@ -191,7 +192,7 @@ public void connectionOpened(String connKey) { public void connectionClosed(String connKey, List notSent) { logger.debug("Connection from {} is closed", uri); for (IMessage request : peerRequests.values()) { - if (request.getState() == IMessage.STATE_SENT) { + if (request.getState() == IMessage.STATE_SENT && !request.isRetransmissionSupervised()) { request.setReTransmitted(true); request.setState(IMessage.STATE_NOT_SENT); try { @@ -273,7 +274,12 @@ protected PeerImpl(final PeerTableImpl table, int rating, URI remotePeer, String IConnection connection, final ISessionDatasource sessionDataSource) throws InternalException, TransportException { super(remotePeer, statisticFactory); this.table = table; - this.rating = rating; + if (peerConfig != null) { + this.rating = peerConfig.getIntValue(PeerRating.ordinal(), 0); + } + else { + this.rating = rating; + } this.router = table.router; this.metaData = metaData; // XXX: FT/HA // this.slc = table.getSessionReqListeners(); @@ -632,7 +638,7 @@ public boolean isConnected() { @Override public String toString() { - return "CPeer{" + "Uri=" + uri + "; State=" + (fsm != null ? fsm.getState(PeerState.class) : "n/a") + "; Con=" + connection + "}"; + return "CPeer{" + "Uri=" + uri + "; State=" + (fsm != null ? fsm.getState(PeerState.class) : "n/a") + "; Rating=" + rating + "; Con=" + connection + "}"; } protected void fillIPAddressTable(IMessage message) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/EmptyConfiguration.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/EmptyConfiguration.java index 6b9019d6f..37757699d 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/EmptyConfiguration.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/EmptyConfiguration.java @@ -213,6 +213,13 @@ public byte[] getByteArrayValue(int i, byte[] bytes) { * @see org.jdiameter.api.Configuration class */ @Override + public int[] getIntArrayValue(int i, int[] ints) { + return (int[]) (isAttributeExist(i) ? elements.get(i) : ints); + } + + /** + * @see org.jdiameter.api.Configuration class + */ public boolean getBooleanValue(int i, boolean b) { return (Boolean) (isAttributeExist(i) ? elements.get(i) : b); } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/Parameters.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/Parameters.java index cb568947d..e46ebf0c4 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/Parameters.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/Parameters.java @@ -123,6 +123,10 @@ public class Parameters extends Ordinal { * Reconnect time out property */ public static final Parameters RecTimeOut = new Parameters("RecTimeOut", Long.class, 10000L); + /** + * Idle session time out property + */ + public static final Parameters SessionTimeOut = new Parameters("SessionTimeOut", Long.class, 0L); /** * Peer FSM Thread Count property @@ -398,6 +402,17 @@ public class Parameters extends Ordinal { */ public static final Parameters DictionaryReceiveLevel = new Parameters("DictionaryReceiveLevel", String.class, "OFF"); + /** + * Tx timer as described in chapter 13. of RFC 4006: + *
The Tx timer is introduced to control the waiting time in the client in the Pending state. The recommended value is 10 seconds.
+ */ + public static final Parameters TxTimeOut = new Parameters("TxTimeOut", Long.class, 10000L); + + /** + * Array of result codes which make an initial request to be retransmitted to another remote peer. + */ + public static final Parameters RetransmissionRequiredResCodes = new Parameters("RetransmissionRequiredResCodes", Object.class); + /** * Return all parameters as iterator * diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/XMLConfiguration.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/XMLConfiguration.java index 94286661f..3c760cf1b 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/XMLConfiguration.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/helpers/XMLConfiguration.java @@ -103,6 +103,7 @@ import static org.jdiameter.client.impl.helpers.Parameters.RealmEntry; import static org.jdiameter.client.impl.helpers.Parameters.RealmTable; import static org.jdiameter.client.impl.helpers.Parameters.RecTimeOut; +import static org.jdiameter.client.impl.helpers.Parameters.RetransmissionRequiredResCodes; import static org.jdiameter.client.impl.helpers.Parameters.SDEnableSessionCreation; import static org.jdiameter.client.impl.helpers.Parameters.SDName; import static org.jdiameter.client.impl.helpers.Parameters.SDProtocol; @@ -123,6 +124,7 @@ import static org.jdiameter.client.impl.helpers.Parameters.ThreadPoolPriority; import static org.jdiameter.client.impl.helpers.Parameters.ThreadPoolSize; import static org.jdiameter.client.impl.helpers.Parameters.TrustData; +import static org.jdiameter.client.impl.helpers.Parameters.TxTimeOut; import static org.jdiameter.client.impl.helpers.Parameters.UseUriAsFqdn; import static org.jdiameter.client.impl.helpers.Parameters.VendorId; import static org.jdiameter.server.impl.helpers.Parameters.RealmEntryExpTime; @@ -377,12 +379,29 @@ else if (nodeName.equals("Concurrent")) { else if (nodeName.equals("Dictionary")) { addDictionary(Dictionary, c.item(i)); } + else if (nodeName.equals("TxTimeOut")) { + add(TxTimeOut, getLongValue(c.item(i))); + } + else if (nodeName.equals("RetransmissionRequiredResCodes")) { + addRetransmissionRequiredResCodes(c.item(i)); + } else { appendOtherParameter(c.item(i)); } } } + protected void addRetransmissionRequiredResCodes(Node node) { + String[] codesArray = getValue(node).replaceAll(" ", "").split(","); + if (codesArray.length > 0) { + int[] parsedCodesArray = new int[codesArray.length]; + for (int i = 0; i < codesArray.length; i++) { + parsedCodesArray[i] = Integer.parseInt(codesArray[i]); + } + add(RetransmissionRequiredResCodes, parsedCodesArray); + } + } + protected void addConcurrent(org.jdiameter.client.impl.helpers.Parameters name, Node node) { NodeList c = node.getChildNodes(); List items = new ArrayList(); @@ -716,10 +735,10 @@ else if (nodeName.equals("AgentRedirect")) { addInternalExtension(InternalAgentRedirect, getValue(c.item(i))); } else if (nodeName.equals("AgentConfiguration")) { - add(InternalAgentConfiguration, getValue(c.item(i))) ; + add(InternalAgentConfiguration, getValue(c.item(i))); } else if (nodeName.equals("StatisticProcessor")) { - addInternalExtension(InternalStatisticProcessor, getValue(c.item(i))) ; + addInternalExtension(InternalStatisticProcessor, getValue(c.item(i))); } else { appendOtherExtension(c.item(i)); diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/parser/AvpSetImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/parser/AvpSetImpl.java index 800fe3f5c..0155138ed 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/parser/AvpSetImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/parser/AvpSetImpl.java @@ -119,8 +119,6 @@ public AvpSet getAvps(int avpCode) { return result; } - - @Override public AvpSet getAvps(int avpCode, long vendorId) { AvpSet result = new AvpSetImpl(); @@ -132,6 +130,24 @@ public AvpSet getAvps(int avpCode, long vendorId) { return result; } + public int getAvpIndex(int avpCode) { + for (Avp avp : this.avps) { + if (avp.getCode() == avpCode ) { + return this.avps.indexOf(avp); + } + } + return -1; + } + + public int getAvpIndex(int avpCode, long vendorId) { + for (Avp avp : this.avps) { + if (avp.getCode() == avpCode && avp.getVendorId() == vendorId) { + return this.avps.indexOf(avp); + } + } + return -1; + } + @Override public AvpSet removeAvp(int avpCode) { return removeAvp(avpCode, 0); diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/parser/MessageImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/parser/MessageImpl.java index da48e5617..90f9f12fc 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/parser/MessageImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/parser/MessageImpl.java @@ -62,6 +62,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + /** * Represents a Diameter message. * @@ -96,10 +97,12 @@ public class MessageImpl implements IMessage { // Potential place for dirt, but Application IDs don't change during message life time. transient List applicationIds; + boolean isRetransSupervisionActive = false; + int numberOfRetransAllowed = Integer.MIN_VALUE; + /** * Create empty message * - * @param parser * @param commandCode * @param appId */ @@ -114,7 +117,6 @@ public class MessageImpl implements IMessage { /** * Create empty message * - * @param parser * @param commandCode * @param applicationId * @param flags @@ -265,7 +267,6 @@ public void setReTransmitted(boolean b) { } } - @Override public int getCommandCode() { return this.commandCode; } @@ -613,6 +614,29 @@ public Object clone() { } } + @Override + public boolean isRetransmissionSupervised() { + return this.isRetransSupervisionActive; + } + + public void setRetransmissionSupervised(boolean arg) { + this.isRetransSupervisionActive = arg; + } + + public boolean isRetransmissionAllowed() { + return this.numberOfRetransAllowed > 0; + } + + public void setNumberOfRetransAllowed(int arg) { + if (this.numberOfRetransAllowed < 0) { + this.numberOfRetransAllowed = arg; + } + } + + public void decrementNumberOfRetransAllowed() { + this.numberOfRetransAllowed--; + } + protected static class TimerTask implements Runnable { ScheduledFuture timerHandler; diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/FailureAwareRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/FailureAwareRouter.java new file mode 100644 index 000000000..2594a3dca --- /dev/null +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/router/FailureAwareRouter.java @@ -0,0 +1,392 @@ +/* + * 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.Avp; +import org.jdiameter.api.AvpDataException; +import org.jdiameter.api.Configuration; +import org.jdiameter.api.MetaData; +import org.jdiameter.api.NoMorePeersAvailableException; +import org.jdiameter.api.PeerState; +import org.jdiameter.api.RouteException; +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.IPeerTable; +import org.jdiameter.client.api.controller.IRealm; +import org.jdiameter.client.api.controller.IRealmTable; +import org.jdiameter.common.api.concurrent.IConcurrentFactory; +import org.jdiameter.common.api.data.IRoutingAwareSessionDatasource; +import org.jdiameter.common.api.data.ISessionDatasource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Extends capabilities of basic router implementation {@link RouterImpl} with extra features + * related to failure detection and failure aware routing. Rating of a particular peer is + * taken into consideration when deciding about an order of peers usage in case of failure + * detection. The highest rating peers are used first, then lower priorities peers next, etc. + * If several peers are marked with the same rating, load balancing algorithm is executed among + * them. In case of all higher priority peers failure, lower priority peers are considered. + * Afterwards, in case any higher priority peer becomes available again, only new sessions requests + * should be targeted again to higher priority peers, i.e. currently handled session stays + * assigned to a peer selected beforehand. + */ +public class FailureAwareRouter extends WeightedRoundRobinRouter { + + private static final Logger logger = LoggerFactory.getLogger(FailureAwareRouter.class); + + private IRoutingAwareSessionDatasource sessionDatasource = null; + + private int lastSelectedRating = -1; + + /** + * Parameterized constructor. Should be called by any subclasses. + */ + public FailureAwareRouter(IContainer container, IConcurrentFactory concurrentFactory, IRealmTable realmTable, Configuration config, MetaData aMetaData) { + super(container, concurrentFactory, realmTable, config, aMetaData); + + ISessionDatasource sds = container.getAssemblerFacility().getComponentInstance(ISessionDatasource.class); + if (sds instanceof IRoutingAwareSessionDatasource) { + this.sessionDatasource = (IRoutingAwareSessionDatasource) sds; + } + + logger.debug("Constructor for FailureAwareRouter (session persistent routing {})", isSessionPersistentRoutingEnabled() ? "enabled" : "disabled"); + } + + /** + * Narrows down the subset of peers selected by {@link RouterImpl} superclass to those with + * the highest rating only. + */ + protected List getAvailablePeers(String destRealm, String[] peers, IPeerTable manager, IMessage message) { + List selectedPeers = getPeers(destRealm, peers, manager, PeerState.OKAY); + + if (logger.isDebugEnabled()) { + logger.debug("All available peers: {}", selectedPeers); + } + selectedPeers = narrowToAnswerablePeers(selectedPeers, message.getSessionId()); + if (logger.isDebugEnabled()) { + logger.debug("All answerable peers: {}", selectedPeers); + } + + if (message.isRetransmissionSupervised()) { + message.setNumberOfRetransAllowed(selectedPeers.size() - 1); + } + + int maxRating = findMaximumRating(selectedPeers); + if (maxRating >= 0 && maxRating != lastSelectedRating) { + lastSelectedRating = maxRating; + resetRoundRobinContext(); + } + + selectedPeers = narrowToSelectablePeersSubset(selectedPeers, maxRating, message); + if (logger.isDebugEnabled()) { + logger.debug("Final subset of selectable peers (max rating [{}]): {}", maxRating, selectedPeers); + } + + return selectedPeers; + } + + private List narrowToAnswerablePeers(List availablePeers, String sessionId) { + List unAnswerablePeers = sessionDatasource.getUnanswerablePeers(sessionId); + + if (unAnswerablePeers != null) { + for (String peerFqdn : unAnswerablePeers) { + for (IPeer peer : availablePeers) { + if (peer.getUri().getFQDN().equals(peerFqdn)) { + availablePeers.remove(peer); + break; + } + } + } + } + + return availablePeers; + } + + @Override + public IPeer getPeer(IMessage message, IPeerTable manager) throws RouteException, AvpDataException { + logger.debug("Getting a peer for message [{}]", message); + //FIXME: add ability to send without matching realm+peer pair?, that is , route based on peer table entries? + //that is, if msg.destHost != null > getPeer(msg.destHost).sendMessage(msg); + String destRealm = null; + String destHost = null; + IRealm matchedRealm = null; + String[] info = null; + // Get destination information + if (message.isRequest()) { + Avp avpRealm = message.getAvps().getAvp(Avp.DESTINATION_REALM); + if (avpRealm == null) { + throw new RouteException("Destination realm avp is empty"); + } + destRealm = avpRealm.getDiameterIdentity(); + + Avp avpHost = message.getAvps().getAvp(Avp.DESTINATION_HOST); + if (avpHost != null) { + destHost = avpHost.getDiameterIdentity(); + } + if (logger.isDebugEnabled()) { + logger.debug("Looking up peer for request: [{}], DestHost=[{}], DestRealm=[{}]", new Object[] {message, destHost, destRealm}); + } + + matchedRealm = (IRealm) this.realmTable.matchRealm(message); + } + else { + //answer, search + info = getRequestRouteInfo(message); + if (info != null) { + destHost = info[0]; + destRealm = info[1]; + logger.debug("Message is an answer. Host is [{}] and Realm is [{}] as per hopbyhop info from request", destHost, destRealm); + if (destRealm == null) { + logger.warn("Destination-Realm was null for hopbyhop id " + message.getHopByHopIdentifier()); + } + } + else { + logger.debug("No Host and realm found based on hopbyhop id of the answer associated request"); + } + //FIXME: if no info, should not send it ? + //FIXME: add strict deff in route back table so stack does not have to lookup? + if (logger.isDebugEnabled()) { + logger.debug("Looking up peer for answer: [{}], DestHost=[{}], DestRealm=[{}]", new Object[] {message, destHost, destRealm}); + } + matchedRealm = (IRealm) this.realmTable.matchRealm(message, destRealm); + } + + // IPeer peer = getPeerPredProcessing(message, destRealm, destHost); + // + // if (peer != null) { + // logger.debug("Found during preprocessing...[{}]", peer); + // return peer; + // } + + // Check realm name + //TODO: check only if it exists? + if (matchedRealm == null) { + throw new RouteException("Unknown realm name [" + destRealm + "]"); + } + + // THIS IS GET PEER, NOT ROUTE!!!!!!! + // Redirect processing + //redirectProcessing(message, destRealm, destHost); + // Check previous context information, this takes care of most answers. + if (message.getPeer() != null && destHost != null && destHost.equals(message.getPeer().getUri().getFQDN()) && message.getPeer().hasValidConnection()) { + if (logger.isDebugEnabled()) { + logger.debug("Select previous message usage peer [{}]", message.getPeer()); + } + return message.getPeer(); + } + + // Balancing procedure + + IPeer c = destHost != null ? manager.getPeer(destHost) : null; + + if (c != null && c.hasValidConnection()) { + logger.debug("Found a peer using destination host avp [{}] peer is [{}] with a valid connection.", destHost, c); + //here matchedRealm MAY + return c; + } + else { + logger.debug("Finding peer by destination host avp [host={}] did not find anything. Now going to try finding one by destination realm [{}]", + destHost, destRealm); + String[] peers = matchedRealm.getPeerNames(); + if (peers == null || peers.length == 0) { + throw new RouteException("Unable to find context by route information [" + destRealm + " ," + destHost + "]"); + } + + List availablePeers = getAvailablePeers(destRealm, peers, manager, message); + + if (logger.isDebugEnabled()) { + logger.debug("Performing Realm routing. Realm [{}] has the following peers available [{}] from list [{}]", + new Object[] {destRealm, availablePeers, Arrays.asList(peers)}); + } + + // Balancing + IPeer peer = selectPeer(availablePeers, message); + if (peer == null) { + throw new NoMorePeersAvailableException( + "Unable to find a valid connection within realm [" + destRealm + "]"); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Load balancing selected peer with uri [{}]", peer.getUri()); + } + } + + return peer; + } + } + + private List getPeers(String destRealm, String[] peers, IPeerTable manager, PeerState state) { + List availablePeers = new ArrayList(5); + logger.debug("Looping through peers in realm [{}]", destRealm); + for (String peerName : peers) { + IPeer localPeer = manager.getPeer(peerName); + if (logger.isDebugEnabled()) { + logger.debug("Checking peer [{}] for name [{}]", new Object[]{localPeer, peerName}); + } + + if (localPeer != null && localPeer.getState(PeerState.class) == state) { + if (localPeer.hasValidConnection()) { + if (logger.isDebugEnabled()) { + logger.debug( + "Found available peer to add to available peer list with uri [{}] with a valid connection", + localPeer.getUri().toString()); + } + availablePeers.add(localPeer); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Found a peer with uri [{}] with no valid connection", localPeer.getUri()); + } + } + } + } + + return availablePeers; + } + + /** + * Applies load balancing algorithm and session persistence thereafter if its enabled. + */ + public IPeer selectPeer(List availablePeers, IMessage message) { + IPeer peer = null; + if (logger.isDebugEnabled()) { + logger.debug(super.dumpRoundRobinContext()); + } + peer = super.selectPeer(availablePeers); + + if (peer == null) { + return null; + } + if (isSessionPersistentRoutingEnabled()) { + String sessionAssignedPeer = sessionDatasource.getSessionPeer(message.getSessionId()); + if (sessionAssignedPeer != null && !peer.getUri().getFQDN().equals(sessionAssignedPeer)) { + if (logger.isDebugEnabled()) { + logger.debug("Peer reselection took place from [{}] to [{}] on session [{}]", new Object[]{sessionAssignedPeer, peer.getUri().getFQDN(), message + .getSessionId()}); + } + sessionDatasource.setSessionPeer(message.getSessionId(), peer); + } + else if (sessionAssignedPeer == null) { + sessionDatasource.setSessionPeer(message.getSessionId(), peer); + if (logger.isDebugEnabled()) { + logger.debug("Peer [{}] selected and assigned to session [{}]", new Object[]{peer.getUri().getFQDN(), message.getSessionId()}); + } + } + + } + + return peer; + } + + /* + * (non-Javadoc) + * @see org.jdiameter.client.impl.router.RouterImpl#isSessionAware() + */ + @Override + public boolean isSessionAware() { + return isSessionPersistentRoutingEnabled(); + } + + /* + * (non-Javadoc) + * @see org.jdiameter.client.impl.router.RouterImpl#getLastSelectedRating() + */ + @Override + protected int getLastSelectedRating() { + return this.lastSelectedRating; + } + + /* + * (non-Javadoc) + * @see org.jdiameter.client.impl.router.RouterImpl#isWrapperFor(java.lang.Class) + */ + @Override + public boolean isWrapperFor(Class aClass) { + if (aClass == IRoutingAwareSessionDatasource.class) { + return isSessionAware(); + } + else { + return super.isWrapperFor(aClass); + } + } + + /* + * (non-Javadoc) + * @see org.jdiameter.client.impl.router.RouterImpl#unwrap(java.lang.Class) + */ + @Override + public T unwrap(Class aClass) { + if (aClass == IRoutingAwareSessionDatasource.class) { + return aClass.cast(sessionDatasource); + } + else { + return super.unwrap(aClass); + } + } + + /*********************************************************************** + * *********************** Local helper methods ************************* + ***********************************************************************/ + + private List narrowToSelectablePeersSubset(List peers, int rating, IMessage message) { + List peersSubset = new ArrayList(5); + String sessionAssignedPeer = sessionDatasource.getSessionPeer(message.getSessionId()); + for (IPeer peer : peers) { + if (isSessionPersistentRoutingEnabled() && sessionAssignedPeer != null) { + if (peer.getUri().getFQDN().equals(sessionAssignedPeer)) { + if (logger.isDebugEnabled()) { + logger.debug("Sticky sessions are enabled and peer [{}] is assigned to the current session [{}]", new Object[]{sessionAssignedPeer, message + .getSessionId()}); + } + peersSubset.clear(); + peersSubset.add(peer); + break; + } + } + if (peer.getRating() == rating) { + peersSubset.add(peer); + } + } + + if (logger.isDebugEnabled() && sessionAssignedPeer == null) { + logger.debug("Sticky sessions are enabled and no peer has been yet assigned to the current session [{}]", message.getSessionId()); + } + + return peersSubset; + } + + private int findMaximumRating(List peers) { + int maxRating = -1; + for (IPeer peer : peers) { + maxRating = Math.max(maxRating, peer.getRating()); + } + return maxRating; + } + + private boolean isSessionPersistentRoutingEnabled() { + return this.sessionDatasource != 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 09d4cfee1..afd42c7e3 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 @@ -83,6 +83,7 @@ import org.jdiameter.api.LocalAction; import org.jdiameter.api.Message; import org.jdiameter.api.MetaData; +import org.jdiameter.api.NoMorePeersAvailableException; import org.jdiameter.api.PeerState; import org.jdiameter.api.RouteException; import org.jdiameter.api.URI; @@ -469,7 +470,8 @@ public IPeer getPeer(IMessage message, IPeerTable manager) throws RouteException // Balancing IPeer peer = selectPeer(availablePeers); if (peer == null) { - throw new RouteException("Unable to find valid connection to peer[" + destHost + "] in realm[" + destRealm + "]"); + throw new NoMorePeersAvailableException( + "Unable to find a valid connection within realm [" + destRealm + "]"); } else { if (logger.isDebugEnabled()) { @@ -481,7 +483,10 @@ public IPeer getPeer(IMessage message, IPeerTable manager) throws RouteException } } - @Override + public boolean isSessionAware() { + return false; + } + public IRealmTable getRealmTable() { return this.realmTable; } @@ -955,4 +960,27 @@ public String toString() { return "AnswerEntry {" + "createTime=" + createTime + ", hopByHopId=" + hopByHopId + '}'; } } + + protected String dumpRoundRobinContext() { + return "Load balancing is not supported"; + } + + protected int getLastSelectedRating() { + return Integer.MIN_VALUE; + } + + @Override + public boolean isWrapperFor(Class aClass) { + return aClass == IRouter.class; + } + + @Override + public T unwrap(Class aClass) { + if (aClass == IRouter.class) { + return aClass.cast(this); + } + else { + return null; + } + } } 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 b52a898a0..179945f4e 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 @@ -32,8 +32,8 @@ /** * 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 { @@ -52,7 +52,7 @@ public WeightedRoundRobinRouter(IContainer container, IConcurrentFactory concurr /** * 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, @@ -106,7 +106,7 @@ 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. @@ -169,4 +169,24 @@ public IPeer selectPeer(List availablePeers) { protected int gcd(int a, int b) { return (b == 0) ? a : gcd(b, a % b); } + + /** + * Resets all round-robin counters/variables in order to make the whole algorithm + * start from scratch. + */ + protected synchronized void resetRoundRobinContext() { + lastSelectedPeer = -1; + currentWeight = 0; + } + + /** + * Gets a readable format of the current round-robin context, i.e. last selected + * peer and current weight + * + * @return readable summary of round-robin context + */ + @Override + protected String dumpRoundRobinContext() { + return String.format("Current round-robin context is: lastSelectedPeer=[%d] currentWeight=[%d]", lastSelectedPeer, currentWeight); + } } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/transport/tcp/TCPClientConnection.java b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/transport/tcp/TCPClientConnection.java index 10521c373..8e6a98331 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/transport/tcp/TCPClientConnection.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/client/impl/transport/tcp/TCPClientConnection.java @@ -387,6 +387,15 @@ protected boolean processBufferedMessages(Event event) throws AvpDataException { } } + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("TCPClientConnection [createdTime=").append(createdTime) + .append(", cachedKey=").append(cachedKey).append(", isConnected=") + .append(isConnected()).append("]"); + return builder.toString(); + } + //------------------ helper classes ------------------------ private enum EventType { CONNECTED, DISCONNECTED, MESSAGE_RECEIVED, DATA_EXCEPTION diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/api/app/ro/IClientRoSessionContext.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/api/app/ro/IClientRoSessionContext.java index 98bc3d523..dedde8421 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/common/api/app/ro/IClientRoSessionContext.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/api/app/ro/IClientRoSessionContext.java @@ -51,6 +51,7 @@ * * @author Bartosz Baranowski * @author Alexandre Mendonca + * @author Grzegorz Figiel (ProIDS sp. z o.o.) */ public interface IClientRoSessionContext { @@ -62,6 +63,8 @@ public interface IClientRoSessionContext { int getDefaultDDFHValue(); + int getDefaultCCSFValue(); + void grantAccessOnDeliverFailure(ClientRoSession clientCCASessionImpl, Message request); void denyAccessOnDeliverFailure(ClientRoSession clientCCASessionImpl, Message request); diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/api/app/ro/IRoMessageFactory.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/api/app/ro/IRoMessageFactory.java index e6bd8cecf..10569c41f 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/common/api/app/ro/IRoMessageFactory.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/api/app/ro/IRoMessageFactory.java @@ -57,6 +57,16 @@ */ public interface IRoMessageFactory { + /** + * Default CC-Session-Failover AVP value - NOT_SUPPORTED(0) according to RFC 4006. + */ + int SESSION_FAILOVER_NOT_SUPPORTED_VALUE = 0; + + /** + * CC-Session-Failover AVP value - SUPPORTED(1) according to RFC 4006. + */ + int SESSION_FAILOVER_SUPPORTED_VALUE = 1; + ReAuthRequest createReAuthRequest(Request request); ReAuthAnswer createReAuthAnswer(Answer answer); diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/api/data/IRoutingAwareSessionDatasource.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/api/data/IRoutingAwareSessionDatasource.java new file mode 100644 index 000000000..cef1d2632 --- /dev/null +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/api/data/IRoutingAwareSessionDatasource.java @@ -0,0 +1,68 @@ +/* + * 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.common.api.data; + +import org.jdiameter.api.SessionPersistenceStorage; +import org.jdiameter.client.api.controller.IPeer; + +import java.util.List; + +/** + * Extends basic session storage with capabilities of CRUD operations + * for session persistence records which bind sessions with peers that + * are processing those sessions. + */ +public interface IRoutingAwareSessionDatasource extends ISessionDatasource, SessionPersistenceStorage { + + /** + * Gets a name of the peer that is currently assigned to a given session. + * + * @param sessionId session identifier used as mapping key in session storage + * @return peer name + */ + String getSessionPeer(String sessionId); + + /** + * Binds a particular session with a given peer. + * + * @param sessionId session identifier used as mapping key in session storage + * @param peer object to bind + */ + void setSessionPeer(String sessionId, IPeer peer); + + /** + * Unbinds a particular session from a given peer. + * + * @param sessionId session identifier used as mapping key in session storage + * @return peer name that has just been unbound + */ + String removeSessionPeer(String sessionId); + + /** + * @param sessionId session identifier used as mapping key in session storage + */ + void clearUnanswerablePeers(String sessionId); + + /** + * @param sessionId session identifier used as mapping key in session storage + * @return list of peers that did not answer for request within Tx timer value period + */ + List getUnanswerablePeers(String sessionId); +} diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/AppRoutingAwareSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/AppRoutingAwareSessionImpl.java new file mode 100644 index 000000000..c4d74ffb2 --- /dev/null +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/AppRoutingAwareSessionImpl.java @@ -0,0 +1,125 @@ +/* + * 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.common.impl.app; + +import org.jdiameter.api.app.AppEvent; +import org.jdiameter.client.api.IMessage; +import org.jdiameter.client.api.ISessionFactory; +import org.jdiameter.client.api.controller.IPeer; +import org.jdiameter.client.api.controller.IPeerTable; +import org.jdiameter.common.api.app.IAppSessionData; +import org.jdiameter.common.api.data.IRoutingAwareSessionDatasource; +import org.jdiameter.common.api.data.ISessionDatasource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Routing aware extension of {@link AppSessionImpl} that enables proper diameter session + * load balancing. It provides diameter session persistence which maps a single diameter + * session to a single peer which is processing the session. + */ +public abstract class AppRoutingAwareSessionImpl extends AppSessionImpl { + + private static final Logger logger = LoggerFactory.getLogger(AppRoutingAwareSessionImpl.class); + + private transient IPeerTable peerTable = null; + private transient IRoutingAwareSessionDatasource sessionPersistenceStorage = null; + + /** + * Parameterized constructor. If session persistence is supposed to be enabled, sessionStorage + * argument should be of type {@link org.jdiameter.common.impl.data.RoutingAwareDataSource}. + * + * @param sessionStorage session datasource + * @param sessionFactory session factory + * @param appSessionData session data + */ + public AppRoutingAwareSessionImpl(ISessionDatasource sessionStorage, ISessionFactory sessionFactory, IAppSessionData appSessionData) { + super(sessionFactory, appSessionData); + peerTable = sessionFactory.getContainer().getAssemblerFacility().getComponentInstance(IPeerTable.class); + if (sessionStorage instanceof IRoutingAwareSessionDatasource) { + sessionPersistenceStorage = (IRoutingAwareSessionDatasource) sessionStorage; + } + } + + /** + * Initiates session persistence record, i.e. assigns the current session to a peer which is + * processing it. Session persistence record shall be created after a peer had answered the + * first (initial) request for that session. + * + * @param reqEvent request that had been sent beforehand + * @param ansEvent response that has been just received + */ + protected void initSessionPersistenceContext(AppEvent reqEvent, AppEvent ansEvent) { + try { + IPeer peer = null; + if (reqEvent.getMessage() instanceof IMessage) { + sessionPersistenceStorage.clearUnanswerablePeers(this.getSessionId()); + peer = ((IMessage) reqEvent.getMessage()).getPeer(); + } + else { + logger.warn("Cannot retrieve message detailed context for Session-Id/activityId [{}]", this.getSessionId()); + } + + if (peer == null) { + logger.warn("Taking peer from Origin-Host AVP as no peer is assigned yet to the following message in session [{}]: [{}]", this.getSessionId(), + reqEvent.getMessage().getAvps()); + peer = peerTable.getPeer(ansEvent.getOriginHost()); + } + + sessionPersistenceStorage.setSessionPeer(this.getSessionId(), peer); + if (logger.isDebugEnabled()) { + logger.debug("Session persistent routing will be enforced for Session-Id [{}] with peer [{}]", this.getSessionId(), peer); + } + + } catch (Exception ex) { + logger.error("Cannot update session persistence data, default routing will be applied", ex); + } + } + + /** + * Removes mapping between current session and the peer that has been assigned so far. + * + * @return peer name that has been assigned so far + */ + protected String flushSessionPersistenceContext() { + try { + return sessionPersistenceStorage.removeSessionPeer(this.getSessionId()); + } catch (Exception ex) { + logger.error("Cannot update session persistence data", ex); + return null; + } + } + + /** + * Handles expiry of session inactivity timer. Should be called by any subclasses which define + * any additional timers. + * + * @see org.jdiameter.common.impl.app.AppSessionImpl#onTimer(java.lang.String) + */ + @Override + public void onTimer(String timerName) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + //no need to interfere with session state machine (simply remove routing context used for sticky sessions based routing) + String oldPeer = flushSessionPersistenceContext(); + logger.debug("Idle session (inactivity) timer expired so routing context for peer [{}] was removed from session [{}]", oldPeer, this.getSessionId()); + } + } +} diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/AppSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/AppSessionImpl.java index 6b094258a..69151514a 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/AppSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/AppSessionImpl.java @@ -42,6 +42,8 @@ package org.jdiameter.common.impl.app; +import static org.jdiameter.client.impl.helpers.Parameters.SessionTimeOut; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -80,6 +82,8 @@ public abstract class AppSessionImpl implements AppSession { protected ITimerFacility timerFacility; + protected long maxIdleTime = 0; + public AppSessionImpl(ISessionFactory sf, IAppSessionData appSessionData) { if (sf == null) { throw new IllegalArgumentException("SessionFactory must not be null"); @@ -94,6 +98,7 @@ public AppSessionImpl(ISessionFactory sf, IAppSessionData appSessionData) { this.scheduler = assembler.getComponentInstance(IConcurrentFactory.class). getScheduledExecutorService(IConcurrentFactory.ScheduledExecServices.ApplicationSession.name()); this.timerFacility = assembler.getComponentInstance(ITimerFacility.class); + this.maxIdleTime = this.sf.getContainer().getConfiguration().getLongValue(SessionTimeOut.ordinal(), (Long) SessionTimeOut.defValue()); this.session = this.sf.getNewSession(this.appSessionData.getSessionId()); //annoying ;[ ArrayList list = new ArrayList(); @@ -202,4 +207,12 @@ public boolean equals(Object obj) { public abstract void onTimer(String timerName); + protected void checkIdleAppSession() { + if (!isValid() || (maxIdleTime > 0 && System.currentTimeMillis() - getLastAccessedTime() >= maxIdleTime)) { + logger.debug("Terminating idle/invalid application session [{}] with SID[{}]", this, getSessionId()); + release(); + } + } + + } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/cca/AppCCASessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/cca/AppCCASessionImpl.java index 83919a918..d7a0e1123 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/cca/AppCCASessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/cca/AppCCASessionImpl.java @@ -52,14 +52,15 @@ import org.jdiameter.api.cca.CCASession; import org.jdiameter.client.api.ISessionFactory; import org.jdiameter.common.api.app.IAppSessionData; -import org.jdiameter.common.impl.app.AppSessionImpl; +import org.jdiameter.common.api.data.ISessionDatasource; +import org.jdiameter.common.impl.app.AppRoutingAwareSessionImpl; /** * * @author Bartosz Baranowski * @author Alexandre Mendonca */ -public abstract class AppCCASessionImpl extends AppSessionImpl implements CCASession, NetworkReqListener { +public abstract class AppCCASessionImpl extends AppRoutingAwareSessionImpl implements CCASession,NetworkReqListener { protected Lock sendAndStateLock = new ReentrantLock(); @@ -68,8 +69,8 @@ public abstract class AppCCASessionImpl extends AppSessionImpl implements CCASes //FIXME: use FastList ? protected List stateListeners = new CopyOnWriteArrayList(); - public AppCCASessionImpl(ISessionFactory sf, IAppSessionData data) { - super(sf, data); + public AppCCASessionImpl(ISessionDatasource sessionStorage, ISessionFactory sf, IAppSessionData data) { + super(sessionStorage, sf, data); } @Override diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/cca/CCASessionFactoryImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/cca/CCASessionFactoryImpl.java index d8bf0d2f7..1124f91fb 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/cca/CCASessionFactoryImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/cca/CCASessionFactoryImpl.java @@ -94,7 +94,7 @@ public CCASessionFactoryImpl(SessionFactory sessionFactory) { } /** - * @param sessionFactory2 + * @param sessionFactory */ public void init(SessionFactory sessionFactory) { this.sessionFactory = (ISessionFactory) sessionFactory; @@ -278,8 +278,14 @@ public AppSession getSession(String sessionId, Class aClas ClientCCASessionImpl clientSession = null; IClientCCASessionData data = (IClientCCASessionData) this.sessionDataFactory.getAppSessionData(ClientCCASession.class, sessionId); - clientSession = new ClientCCASessionImpl(data, this.getMessageFactory(), sessionFactory, this.getClientSessionListener(), - this.getClientContextListener(), this.getStateListener()); + if (!sessionFactory.isSessionPersistenceEnabled()) { + clientSession = new ClientCCASessionImpl(data, this.getMessageFactory(), sessionFactory, + this.getClientSessionListener(), this.getClientContextListener(), this.getStateListener()); + } + else { + clientSession = new ClientCCASessionImpl(data, this.getMessageFactory(), iss, sessionFactory, + this.getClientSessionListener(), this.getClientContextListener(), this.getStateListener()); + } clientSession.getSessions().get(0).setRequestListener(clientSession); appSession = clientSession; } @@ -320,8 +326,14 @@ public AppSession getNewSession(String sessionId, Class aC } IClientCCASessionData data = (IClientCCASessionData) this.sessionDataFactory.getAppSessionData(ClientCCASession.class, sessionId); data.setApplicationId(applicationId); - clientSession = new ClientCCASessionImpl(data, this.getMessageFactory(), sessionFactory, this.getClientSessionListener(), - this.getClientContextListener(), this.getStateListener()); + if (!sessionFactory.isSessionPersistenceEnabled()) { + clientSession = new ClientCCASessionImpl(data, this.getMessageFactory(), sessionFactory, + this.getClientSessionListener(), this.getClientContextListener(), this.getStateListener()); + } + else { + clientSession = new ClientCCASessionImpl(data, this.getMessageFactory(), iss, sessionFactory, + this.getClientSessionListener(), this.getClientContextListener(), this.getStateListener()); + } // this goes first! iss.addSession(clientSession); clientSession.getSessions().get(0).setRequestListener(clientSession); diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/ro/AppRoSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/ro/AppRoSessionImpl.java index cff2a7435..729c057b9 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/ro/AppRoSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/ro/AppRoSessionImpl.java @@ -52,14 +52,15 @@ import org.jdiameter.api.app.StateMachine; import org.jdiameter.client.api.ISessionFactory; import org.jdiameter.common.api.app.ro.IRoSessionData; -import org.jdiameter.common.impl.app.AppSessionImpl; +import org.jdiameter.common.api.data.ISessionDatasource; +import org.jdiameter.common.impl.app.AppRoutingAwareSessionImpl; /** * * @author Bartosz Baranowski * @author Alexandre Mendonca */ -public abstract class AppRoSessionImpl extends AppSessionImpl implements NetworkReqListener, StateMachine { +public abstract class AppRoSessionImpl extends AppRoutingAwareSessionImpl implements NetworkReqListener, StateMachine { protected Lock sendAndStateLock = new ReentrantLock(); @@ -67,8 +68,8 @@ public abstract class AppRoSessionImpl extends AppSessionImpl implements Network //FIXME: change this to single ref! protected transient List stateListeners = new CopyOnWriteArrayList(); - public AppRoSessionImpl(ISessionFactory sf, IRoSessionData sessionData) { - super(sf, sessionData); + public AppRoSessionImpl(ISessionDatasource sessionStorage, ISessionFactory sf, IRoSessionData sessionData) { + super(sessionStorage, sf, sessionData); } @Override diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/ro/RoSessionFactoryImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/ro/RoSessionFactoryImpl.java index e139b07ff..5bacdbad0 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/ro/RoSessionFactoryImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/app/ro/RoSessionFactoryImpl.java @@ -25,7 +25,9 @@ import org.jdiameter.api.ApplicationId; import org.jdiameter.api.InternalException; import org.jdiameter.api.Message; +import org.jdiameter.api.Peer; import org.jdiameter.api.Request; +import org.jdiameter.api.RouteException; import org.jdiameter.api.SessionFactory; import org.jdiameter.api.app.AppAnswerEvent; import org.jdiameter.api.app.AppRequestEvent; @@ -61,6 +63,7 @@ * * @author Alexandre Mendonca * @author Bartosz Baranowski + * @author Grzegorz Figiel (ProIDS sp. z o.o.) */ public class RoSessionFactoryImpl implements IRoSessionFactory, ClientRoSessionListener, ServerRoSessionListener, StateChangeListener, IRoMessageFactory, IServerRoSessionContext, IClientRoSessionContext { @@ -68,6 +71,7 @@ public class RoSessionFactoryImpl implements IRoSessionFactory, ClientRoSessionL // Message timeout value (in milliseconds) protected int defaultDirectDebitingFailureHandling = 0; protected int defaultCreditControlFailureHandling = 0; + protected int defaultCreditControlSessionFailover = IRoMessageFactory.SESSION_FAILOVER_NOT_SUPPORTED_VALUE; // its seconds protected long defaultValidityTime = 60; @@ -270,8 +274,8 @@ public AppSession getNewSession(String sessionId, Class aC IClientRoSessionData sessionData = (IClientRoSessionData) this.sessionDataFactory.getAppSessionData(ClientRoSession.class, sessionId); sessionData.setApplicationId(applicationId); - clientSession = new ClientRoSessionImpl(sessionData, this.getMessageFactory(), sessionFactory, this.getClientSessionListener(), - this.getClientContextListener(), this.getStateListener()); + clientSession = new ClientRoSessionImpl(sessionData, this.getMessageFactory(), iss, sessionFactory, this.getClientSessionListener(), this + .getClientContextListener(), this.getStateListener()); // this goes first! iss.addSession(clientSession); clientSession.getSessions().get(0).setRequestListener(clientSession); @@ -320,8 +324,8 @@ public AppSession getSession(String sessionId, Class aClas try { if (aClass == ClientRoSession.class) { IClientRoSessionData sessionData = (IClientRoSessionData) this.sessionDataFactory.getAppSessionData(ClientRoSession.class, sessionId); - ClientRoSessionImpl clientSession = new ClientRoSessionImpl(sessionData, this.getMessageFactory(), sessionFactory, this.getClientSessionListener(), - this.getClientContextListener(), this.getStateListener()); + ClientRoSessionImpl clientSession = new ClientRoSessionImpl(sessionData, this.getMessageFactory(), iss, sessionFactory, this.getClientSessionListener + (), this.getClientContextListener(), this.getStateListener()); // this goes first! clientSession.getSessions().get(0).setRequestListener(clientSession); appSession = clientSession; @@ -372,6 +376,21 @@ public void doOtherEvent(AppSession session, AppRequestEvent request, AppAnswerE } + @Override + public void doRequestTxTimeout(ClientRoSession session, Message msg, Peer peer) throws InternalException { + + } + + @Override + public void doRequestTimeout(ClientRoSession session, Message msg, Peer peer) throws InternalException { + + } + + @Override + public void doPeerUnavailability(ClientRoSession session, Message msg, Peer peer, RouteException cause) throws InternalException { + + } + // Message Factory Methods -------------------------------------------------- @Override @@ -466,6 +485,11 @@ public int getDefaultCCFHValue() { return defaultCreditControlFailureHandling; } + @Override + public int getDefaultCCSFValue() { + return defaultCreditControlSessionFailover; + } + @Override public int getDefaultDDFHValue() { return defaultDirectDebitingFailureHandling; @@ -498,14 +522,13 @@ public void indicateServiceError(ClientRoSession clientRoSessionImpl) { @Override public void txTimerExpired(ClientRoSession session) { - // this.resourceAdaptor.sessionDestroyed(session.getSessions().get(0).getSessionId(), session); - session.release(); + // TODO Auto-generated method stub } @Override public long[] getApplicationIds() { // FIXME: What should we do here? - return new long[] { 4 }; + return new long[]{4}; } @Override diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/data/LocalDataSource.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/data/LocalDataSource.java index d066798e6..708194ba6 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/data/LocalDataSource.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/data/LocalDataSource.java @@ -89,7 +89,7 @@ public class LocalDataSource implements ISessionDatasource { protected HashMap, IAppSessionDataFactory> appSessionDataFactories = new HashMap, IAppSessionDataFactory>(); - private ConcurrentHashMap sessionIdToEntry = new ConcurrentHashMap(); + protected ConcurrentHashMap sessionIdToEntry = new ConcurrentHashMap(); private static final Logger logger = LoggerFactory.getLogger(LocalDataSource.class); @@ -152,23 +152,34 @@ public NetworkReqListener removeSessionListener(String sessionId) { @Override public void addSession(BaseSession session) { - logger.debug("addSession({})", session); - SessionEntry se = null; + addSession(session, SessionEntry.class); + } + + protected void addSession(BaseSession session, Class sessionWraperType) { + logger.debug("addSession({}) => {}", session.getSessionId(), session); + T se = null; String sessionId = session.getSessionId(); //FIXME: check here replicable vs not replicable? if (this.sessionIdToEntry.containsKey(sessionId)) { - se = this.sessionIdToEntry.get(sessionId); - if ( !(se.session instanceof ISession) || se.session.isReplicable()) { //must be not replicable so we can "overwrite" - throw new IllegalArgumentException("Sessin with id: " + sessionId + ", already exists!"); - } - else { - this.sessionIdToEntry.put(sessionId, se); + se = sessionWraperType.cast(this.sessionIdToEntry.get(sessionId)); + if( se != null && (!(se.session instanceof ISession) || se.session.isReplicable()) ) { //must be not replicable so we can "overwrite" + throw new IllegalArgumentException("Session with id: " + sessionId + ", already exists!"); } } - else { - se = new SessionEntry(); + + if(se == null) { + try { + se = sessionWraperType.newInstance(); + } catch (InstantiationException e) { + logger.warn("Cannot instantiate session object of type: " + sessionWraperType.getCanonicalName(), e); + throw new IllegalArgumentException("Cannot instantiate session object of type: " + sessionWraperType.getCanonicalName(), e); + } catch (IllegalAccessException e) { + logger.warn("Cannot instantiate session object of type: " + sessionWraperType.getCanonicalName(), e); + throw new IllegalArgumentException("Cannot instantiate session object of type: " + sessionWraperType.getCanonicalName(), e); + } } + se.session = session; this.sessionIdToEntry.put(session.getSessionId(), se); } @@ -217,7 +228,7 @@ public String toString() { } //simple class to reduce collections overhead. - private class SessionEntry { + protected static class SessionEntry { BaseSession session; NetworkReqListener listener; diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/data/RoutingAwareDataSource.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/data/RoutingAwareDataSource.java new file mode 100644 index 000000000..f7b424f28 --- /dev/null +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/data/RoutingAwareDataSource.java @@ -0,0 +1,203 @@ +/* + * 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.common.impl.data; + +import org.jdiameter.api.BaseSession; +import org.jdiameter.client.api.IContainer; +import org.jdiameter.client.api.controller.IPeer; +import org.jdiameter.common.api.data.IRoutingAwareSessionDatasource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * Implementation of routing aware session datasource for {@link IRoutingAwareSessionDatasource}. + */ +public class RoutingAwareDataSource extends LocalDataSource implements IRoutingAwareSessionDatasource { + + private static final Logger logger = LoggerFactory.getLogger(RoutingAwareDataSource.class); + + + /** + * Default constructor. + */ + public RoutingAwareDataSource() { + super(); + logger.debug("Constructor for RoutingAwareDataSource: nothing to do"); + } + + /** + * Parameterized constructor. Should be called by any subclasses. + * + * @param container container object + */ + public RoutingAwareDataSource(IContainer container) { + super(container); + logger.debug("Constructor for RoutingAwareDataSource: nothing to do"); + } + + /* + * (non-Javadoc) + * @see org.jdiameter.common.impl.data.LocalDataSource#addSession(org.jdiameter.api.BaseSession) + */ + @Override + public void addSession(BaseSession session) { + addSession(session, RoutingAwareSessionEntry.class); + } + + /* + * (non-Javadoc) + * @see org.jdiameter.common.api.data.IRoutingAwareSessionDatasource#setSessionPeer(java.lang.String, org.jdiameter.client.api.controller.IPeer) + */ + @Override + public void setSessionPeer(String sessionId, IPeer peer) { + logger.debug("Assigning routing destination peer [{}] to session [{}]", peer, sessionId); + SessionEntry se = sessionIdToEntry.get(sessionId); + if (se == null) { + throw new IllegalArgumentException("No session entry for id: " + sessionId); + } + else if (!(se instanceof RoutingAwareSessionEntry)) { + throw new IllegalArgumentException("Session entry is of a wrong type for id: " + sessionId); + } + else { + ((RoutingAwareSessionEntry) se).peer = peer.getUri().getFQDN(); + } + } + + /* + * (non-Javadoc) + * @see org.jdiameter.common.api.data.IRoutingAwareSessionDatasource#getSessionPeer(java.lang.String) + */ + @Override + public String getSessionPeer(String sessionId) { + SessionEntry se = sessionIdToEntry.get(sessionId); + logger.debug("Looking up routing peer for session [{}]: {}", sessionId, se); + return (se != null && se instanceof RoutingAwareSessionEntry) ? ((RoutingAwareSessionEntry) se).peer : null; + } + + /* + * (non-Javadoc) + * @see org.jdiameter.common.api.data.IRoutingAwareSessionDatasource#removeSessionPeer(java.lang.String) + */ + @Override + public String removeSessionPeer(String sessionId) { + SessionEntry se = sessionIdToEntry.get(sessionId); + logger.debug("Looking up routing peer for removal for session [{}]: {}", sessionId, se); + if (se != null && se instanceof RoutingAwareSessionEntry) { + String oldPeer = ((RoutingAwareSessionEntry) se).peer; + ((RoutingAwareSessionEntry) se).peer = null; + ((RoutingAwareSessionEntry) se).getUnanswerablePeers().add(oldPeer); + return oldPeer; + } + else { + return null; + } + } + + @Override + public void clearUnanswerablePeers(String sessionId) { + SessionEntry se = sessionIdToEntry.get(sessionId); + if (se != null && se instanceof RoutingAwareSessionEntry) { + ((RoutingAwareSessionEntry) se).getUnanswerablePeers().clear(); + } + } + + @Override + public List getUnanswerablePeers(String sessionId) { + SessionEntry se = sessionIdToEntry.get(sessionId); + if (se != null && se instanceof RoutingAwareSessionEntry) { + return ((RoutingAwareSessionEntry) se).getUnanswerablePeers(); + } + else { + return null; + } + } + + /* + * (non-Javadoc) + * @see org.jdiameter.api.SessionPersistenceStorage#dumpStickySessions(int) + */ + @Override + public List dumpStickySessions(int maxLimit) { + int counter = 0; + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + List sessions = maxLimit > 0 ? new ArrayList(maxLimit) : new ArrayList(sessionIdToEntry.size()); + + logger.debug("Reading [{}] sessions out of [{}]", maxLimit > 0 ? String.valueOf(maxLimit) : "unlimited", sessionIdToEntry.size()); + + for (Map.Entry entry : sessionIdToEntry.entrySet()) { + if (entry.getValue() instanceof RoutingAwareSessionEntry) { + RoutingAwareSessionEntry tmpEntry = (RoutingAwareSessionEntry) entry.getValue(); + if (tmpEntry.peer != null) { + sessions.add(tmpEntry.preetyPrint(entry.getKey(), dateFormat)); + if (maxLimit > 0 && ++counter >= maxLimit) { + break; + } + } + } + } + + return sessions; + } + + /** + * Extends basic session entry, which is used to store records in session storage, with extra info about + * a specific peer that is bound to a particular session. Extra info is used for session persistent routing. + */ + protected static class RoutingAwareSessionEntry extends SessionEntry { + private List unanswerable = new ArrayList(); + String peer; + + public List getUnanswerablePeers() { + return unanswerable; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("RoutingAwareSessionEntry [peer=").append(peer).append(", unanswerable=[").append(Arrays.toString(unanswerable.toArray())).append("], " + + "toString()=").append(super.toString()).append("]"); + return builder.toString(); + } + + /** + * Gets a readable and more user friendly format of an entry. + * + * @param key key used to store that entry in a session storage map + * @param dateFormat format used to print last session activity timestamp + * @return readable representation of this session entry + */ + public String preetyPrint(String key, DateFormat dateFormat) { + StringBuilder builder = new StringBuilder("{id=["); + builder.append(key).append("], peer=[").append(peer) + .append("], timestamp=[").append(dateFormat.format(new Date(session.getLastAccessedTime()))) + .append("], unanswerable=[").append(Arrays.toString(unanswerable.toArray())) + .append("]}").toString(); + return builder.toString(); + } + } +} diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/timer/LocalTimerFacilityImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/timer/LocalTimerFacilityImpl.java index c2c2b2302..f08b3c700 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/timer/LocalTimerFacilityImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/common/impl/timer/LocalTimerFacilityImpl.java @@ -55,6 +55,7 @@ import org.apache.commons.pool.impl.GenericObjectPool; import org.jdiameter.api.BaseSession; import org.jdiameter.client.api.IContainer; +import org.jdiameter.client.impl.BaseSessionImpl; import org.jdiameter.common.api.concurrent.IConcurrentFactory; import org.jdiameter.common.api.data.ISessionDatasource; import org.jdiameter.common.api.timer.ITimerFacility; @@ -96,6 +97,7 @@ public void cancel(Serializable f) { if (f != null && f instanceof TimerTaskHandle) { TimerTaskHandle timerTaskHandle = (TimerTaskHandle) f; if (timerTaskHandle.future != null) { + logger.debug("Cancelling timer with id [{}] and delay [{}]", timerTaskHandle.id, timerTaskHandle.future.getDelay(TimeUnit.MILLISECONDS)); if (executor.remove((Runnable) timerTaskHandle.future)) { timerTaskHandle.future.cancel(false); returnTimerTaskHandle(timerTaskHandle); @@ -167,18 +169,24 @@ private final class TimerTaskHandle implements Runnable, Externalizable { public void run() { try { BaseSession bSession = sessionDataSource.getSession(sessionId); - if (bSession == null || !bSession.isAppSession()) { + if (bSession == null) { // FIXME: error ? logger.error("Base Session is null for sessionId: {}", sessionId); return; } else { try { - AppSessionImpl impl = (AppSessionImpl) bSession; - impl.onTimer(timerName); + if (!bSession.isAppSession()) { + BaseSessionImpl impl = (BaseSessionImpl) bSession; + impl.onTimer(timerName); + } + else { + AppSessionImpl impl = (AppSessionImpl) bSession; + impl.onTimer(timerName); + } } catch (Exception e) { - logger.error("Caught exception from app session object!", e); + logger.error("Caught exception from session object!", e); } } } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/FailureAwareRouter.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/FailureAwareRouter.java new file mode 100644 index 000000000..11b87a8d1 --- /dev/null +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/FailureAwareRouter.java @@ -0,0 +1,41 @@ +/* + * 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.server.impl; + +import org.jdiameter.api.Configuration; +import org.jdiameter.api.MetaData; +import org.jdiameter.client.api.IContainer; +import org.jdiameter.client.api.controller.IRealmTable; +import org.jdiameter.common.api.concurrent.IConcurrentFactory; +import org.jdiameter.server.api.IRouter; + +/** + * Just a simple counterpart of failure aware router defined for a client role. + */ +public class FailureAwareRouter extends org.jdiameter.client.impl.router.FailureAwareRouter implements IRouter { + + /** + * Parameterized constructor. Should be called by any subclasses. + */ + public FailureAwareRouter(IContainer container, IConcurrentFactory concurrentFactory, IRealmTable realmTable, Configuration config, MetaData aMetaData) { + super(container, concurrentFactory, realmTable, config, aMetaData); + } + +} diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/MutablePeerTableImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/MutablePeerTableImpl.java index 895f52bb9..df6ce41ac 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/MutablePeerTableImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/MutablePeerTableImpl.java @@ -43,7 +43,6 @@ package org.jdiameter.server.impl; import static org.jdiameter.client.impl.helpers.Parameters.PeerName; -import static org.jdiameter.client.impl.helpers.Parameters.PeerTable; import static org.jdiameter.client.impl.helpers.Parameters.StopTimeOut; import static org.jdiameter.client.impl.helpers.Parameters.UseUriAsFqdn; import static org.jdiameter.common.api.concurrent.IConcurrentFactory.ScheduledExecServices.ConnectionTimer; @@ -100,6 +99,7 @@ import org.jdiameter.client.api.io.TransportException; import org.jdiameter.client.api.parser.IMessageParser; import org.jdiameter.client.impl.controller.PeerTableImpl; +import org.jdiameter.client.impl.helpers.Parameters; import org.jdiameter.common.api.concurrent.IConcurrentFactory; import org.jdiameter.common.api.statistic.IStatisticManager; import org.jdiameter.server.api.IFsmFactory; @@ -227,6 +227,30 @@ public MutablePeerTableImpl(Configuration config, MetaData metaData, IContainer logger.debug("MutablePeerTableImpl has finished initialisation"); } + protected Configuration getPeerConfig(String fqdn) throws URISyntaxException, UnknownServiceException { + if (logger.isDebugEnabled()) { + logger.debug("Searching configuration for peer fqdn: " + fqdn); + } + Configuration result = null; + Configuration[] peers = config.getChildren(Parameters.PeerTable.ordinal()); + if (peers != null && peers.length > 0) { + for (Configuration peerConfig : peers) { + if (peerConfig.isAttributeExist(PeerName.ordinal())) { + String peerConfigFqdn = new URI(peerConfig.getStringValue(PeerName.ordinal(), "")).getFQDN(); + if (fqdn.equals(peerConfigFqdn)) { + result = peerConfig; + break; + } + } + } + } + if (logger.isDebugEnabled()) { + logger.debug("Peer configuration {}found for FQDN: {}", (result == null ? "not " : ""), fqdn); + } + return result; + } + + @Override protected Peer createPeer(int rating, String uri, String ip, String portRange, MetaData metaData, Configuration globalConfig, Configuration peerConfig, org.jdiameter.client.api.fsm.IFsmFactory fsmFactory, @@ -445,7 +469,8 @@ public void messageReceived(String connKey, IMessage message) { try { realm = message.getAvps().getAvp(Avp.ORIGIN_REALM).getDiameterIdentity(); logger.debug("Origin-Realm in new received message is [{}]", realm); - } catch (AvpDataException e) { + } + catch (AvpDataException e) { logger.warn("Unable to retrieve find Origin-Realm AVP in CER", e); unregister(true); return; @@ -510,7 +535,7 @@ public void messageReceived(String connKey, IMessage message) { } peer = newPeerInstance(0, uri, connection.getRemoteAddress().getHostAddress(), null, false, connection, - metaData, config, null, fsmFactory, transportFactory, parser, statisticFactory, concurrentFactory); + metaData, config, getPeerConfig(uri.getFQDN()), fsmFactory, transportFactory, parser, statisticFactory, concurrentFactory); logger.debug("Created new peer instance [{}] and adding to peer table", peer); peer.setRealm(realm); appendPeerToPeerTable(peer); @@ -563,7 +588,7 @@ public void unregister(boolean release) { } } } - ); + ); } private void appendPeerToPeerTable(IPeer peer) { @@ -639,15 +664,8 @@ public Peer addPeer(URI peerURI, String realm, boolean connecting) { //TODO: add sKey here, now it adds peer to all realms. //TODO: better, separate addPeer from realm! try { - Configuration peerConfig = null; - Configuration[] peers = config.getChildren(PeerTable.ordinal()); - // find peer config - for (Configuration c : peers) { - if (peerURI.getFQDN().equals(c.getStringValue(PeerName.ordinal(), ""))) { - peerConfig = c; - break; - } - } + Configuration peerConfig = getPeerConfig(peerURI.getFQDN()); + if (peerConfig == null) { peerConfig = new EmptyConfiguration(false).add(PeerAttemptConnection, connecting); } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/PeerImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/PeerImpl.java index 1b8be7612..07d3422ce 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/PeerImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/PeerImpl.java @@ -205,9 +205,10 @@ public void notifyOvrManager(IOverloadManager ovrManager) { @Override public String toString() { if (fsm != null) { - return "SPeer{" + "Uri=" + uri + "; State=" + fsm.getState(PeerState.class) + "; con=" + connection + "; incCon" + incConnections + " }"; + return "SPeer{" + "Uri=" + uri + "; State=" + fsm.getState(PeerState.class) + + "; Rating=" + rating + "; con="+ connection +"; incCon="+incConnections+" }"; } - return "SPeer{" + "Uri=" + uri + "; State=" + fsm + "; con=" + connection + "; incCon" + incConnections + " }"; + return "SPeer{" + "Uri=" + uri + "; State=" + fsm + "; Rating=" + rating + "; con="+ connection +"; incCon="+incConnections+" }"; } protected class LocalActionConext extends ActionContext { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/acc/ServerAccSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/acc/ServerAccSessionImpl.java index 78a5d7bc1..278094528 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/acc/ServerAccSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/acc/ServerAccSessionImpl.java @@ -407,7 +407,10 @@ private void cancelTsTimer() { */ @Override public void onTimer(String timerName) { - if (timerName.equals(TIMER_NAME_TS)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TIMER_NAME_TS)) { if (context != null) { try { context.sessionTimeoutElapses(ServerAccSessionImpl.this); @@ -419,7 +422,7 @@ public void onTimer(String timerName) { setState(IDLE); } else { - // FIXME: ??? + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); } } diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/auth/ServerAuthSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/auth/ServerAuthSessionImpl.java index 8f79a4461..030c8765f 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/auth/ServerAuthSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/auth/ServerAuthSessionImpl.java @@ -453,7 +453,10 @@ protected void cancelTsTimer() { */ @Override public void onTimer(String timerName) { - if (timerName.equals(TIMER_NAME_TS)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TIMER_NAME_TS)) { try { sendAndStateLock.lock(); sessionData.setTsTimerId(null); @@ -466,6 +469,9 @@ public void onTimer(String timerName) { sendAndStateLock.unlock(); } } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } @Override diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/cca/ServerCCASessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/cca/ServerCCASessionImpl.java index c3e2cf37e..c6619595d 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/cca/ServerCCASessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/cca/ServerCCASessionImpl.java @@ -107,7 +107,7 @@ public class ServerCCASessionImpl extends AppCCASessionImpl implements ServerCCA public ServerCCASessionImpl(IServerCCASessionData data, ICCAMessageFactory fct, ISessionFactory sf, ServerCCASessionListener lst, IServerCCASessionContext ctx, StateChangeListener stLst) { - super(sf, data); + super(null, sf, data); if (lst == null) { throw new IllegalArgumentException("Listener can not be null"); } @@ -419,9 +419,15 @@ private void startTcc(Avp validityAvp) { */ @Override public void onTimer(String timerName) { - if (timerName.equals(TCC_TIMER_NAME)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TCC_TIMER_NAME)) { new TccScheduledTask(this).run(); } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } private void stopTcc(boolean willRestart) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/cxdx/CxDxServerSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/cxdx/CxDxServerSessionImpl.java index d068a5141..033301563 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/cxdx/CxDxServerSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/cxdx/CxDxServerSessionImpl.java @@ -395,7 +395,10 @@ protected void setState(CxDxSessionState newState) { @Override public void onTimer(String timerName) { - if (timerName.equals(CxDxSession.TIMER_NAME_MSG_TIMEOUT)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(CxDxSession.TIMER_NAME_MSG_TIMEOUT)) { try { sendAndStateLock.lock(); try { @@ -411,6 +414,9 @@ public void onTimer(String timerName) { sendAndStateLock.unlock(); } } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } private class RequestDelivery implements Runnable { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/gq/GqServerSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/gq/GqServerSessionImpl.java index 786d2ce9f..fc1148d8e 100755 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/gq/GqServerSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/gq/GqServerSessionImpl.java @@ -479,7 +479,10 @@ protected void cancelTsTimer() { */ @Override public void onTimer(String timerName) { - if (timerName.equals(TIMER_NAME_TS)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TIMER_NAME_TS)) { try { sendAndStateLock.lock(); sessionData.setTsTimerId(null); @@ -492,6 +495,9 @@ public void onTimer(String timerName) { sendAndStateLock.unlock(); } } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } protected ReAuthAnswer createReAuthAnswer(Answer answer) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/gx/ServerGxSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/gx/ServerGxSessionImpl.java index df57e0f93..af5bd3f98 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/gx/ServerGxSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/gx/ServerGxSessionImpl.java @@ -410,9 +410,15 @@ private void startTcc(Avp validityAvp) { */ @Override public void onTimer(String timerName) { - if (timerName.equals(TCC_TIMER_NAME)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TCC_TIMER_NAME)) { new TccScheduledTask(this).run(); } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } private void stopTcc(boolean willRestart) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/rf/ServerRfSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/rf/ServerRfSessionImpl.java index d5b01100d..6e86e99fe 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/rf/ServerRfSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/rf/ServerRfSessionImpl.java @@ -392,7 +392,10 @@ private void cancelTsTimer() { */ @Override public void onTimer(String timerName) { - if (timerName.equals(TIMER_NAME_TS)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TIMER_NAME_TS)) { if (context != null) { try { context.sessionTimeoutElapses(ServerRfSessionImpl.this); @@ -403,6 +406,9 @@ public void onTimer(String timerName) { } setState(IDLE); } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } protected Answer createStopAnswer(Request request) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/ro/ServerRoSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/ro/ServerRoSessionImpl.java index 241b4a89e..ec79a5ac5 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/ro/ServerRoSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/ro/ServerRoSessionImpl.java @@ -108,7 +108,7 @@ public class ServerRoSessionImpl extends AppRoSessionImpl implements ServerRoSes public ServerRoSessionImpl(IServerRoSessionData sessionData, IRoMessageFactory fct, ISessionFactory sf, ServerRoSessionListener lst, IServerRoSessionContext ctx, StateChangeListener stLst) { - super(sf, sessionData); + super(null, sf, sessionData); if (sessionData == null) { throw new IllegalArgumentException("SessionData can not be null"); } @@ -429,9 +429,15 @@ private void startTcc(Avp validityAvp) { */ @Override public void onTimer(String timerName) { - if (timerName.equals(TCC_TIMER_NAME)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(TCC_TIMER_NAME)) { new TccScheduledTask(this).run(); } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } private void stopTcc(boolean willRestart) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/rx/ServerRxSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/rx/ServerRxSessionImpl.java index 314f05708..9ea4133d9 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/rx/ServerRxSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/rx/ServerRxSessionImpl.java @@ -346,6 +346,12 @@ public void receivedSuccessMessage(Request request, Answer answer) { */ @Override public void onTimer(String timerName) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } @Override diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/s13/S13ServerSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/s13/S13ServerSessionImpl.java index 6ee368b75..93449f972 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/s13/S13ServerSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/s13/S13ServerSessionImpl.java @@ -212,7 +212,10 @@ protected void setState(S13SessionState newState) { @Override public void onTimer(String timerName) { - if (timerName.equals(S13Session.TIMER_NAME_MSG_TIMEOUT)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(S13Session.TIMER_NAME_MSG_TIMEOUT)) { try { sendAndStateLock.lock(); try { @@ -226,6 +229,9 @@ public void onTimer(String timerName) { sendAndStateLock.unlock(); } } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } @Override diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/s6a/S6aServerSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/s6a/S6aServerSessionImpl.java index fee538f10..f531f36ee 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/s6a/S6aServerSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/s6a/S6aServerSessionImpl.java @@ -391,7 +391,10 @@ protected void setState(S6aSessionState newState) { @Override public void onTimer(String timerName) { - if (timerName.equals(S6aSession.TIMER_NAME_MSG_TIMEOUT)) { + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else if (timerName.equals(S6aSession.TIMER_NAME_MSG_TIMEOUT)) { try { sendAndStateLock.lock(); try { @@ -407,6 +410,9 @@ public void onTimer(String timerName) { sendAndStateLock.unlock(); } } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } @Override diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/sh/ShServerSessionImpl.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/sh/ShServerSessionImpl.java index ec2b55f85..0daa56690 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/sh/ShServerSessionImpl.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/app/sh/ShServerSessionImpl.java @@ -341,7 +341,12 @@ else if (!sessionData.equals(other.sessionData)) { @Override public void onTimer(String timerName) { - logger.trace("onTimer({})", timerName); + if (timerName.equals(IDLE_SESSION_TIMER_NAME)) { + checkIdleAppSession(); + } + else { + logger.warn("Received an unknown timer '{}' for Session-ID '{}'", timerName, getSessionId()); + } } private class RequestDelivery implements Runnable { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/helpers/EmptyConfiguration.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/helpers/EmptyConfiguration.java index 13b3d89b1..74327bd19 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/helpers/EmptyConfiguration.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/helpers/EmptyConfiguration.java @@ -212,6 +212,22 @@ public void setByteArrayValue(int key, byte[] value) { } @Override + public void setIntArrayValue(int key, int[] value) { + List list = listeners.get(key); + if (list != null) { + boolean commit = true; + for (ConfigurationListener l : list) { + commit &= l.elementChanged(key, value); + } + if (commit) { + putValue(key, value); + } + } + else { + putValue(key, value); + } + } + public void setBooleanValue(int key, boolean value) { List list = listeners.get(key); if (list != null) { diff --git a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/helpers/XMLConfiguration.java b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/helpers/XMLConfiguration.java index 0760eb62c..31d82f210 100644 --- a/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/helpers/XMLConfiguration.java +++ b/core/jdiameter/impl/src/main/java/org/jdiameter/server/impl/helpers/XMLConfiguration.java @@ -103,12 +103,14 @@ import static org.jdiameter.client.impl.helpers.Parameters.RealmEntry; import static org.jdiameter.client.impl.helpers.Parameters.RealmTable; import static org.jdiameter.client.impl.helpers.Parameters.RecTimeOut; +import static org.jdiameter.client.impl.helpers.Parameters.RetransmissionRequiredResCodes; import static org.jdiameter.client.impl.helpers.Parameters.SDEnableSessionCreation; import static org.jdiameter.client.impl.helpers.Parameters.SDName; import static org.jdiameter.client.impl.helpers.Parameters.SDProtocol; import static org.jdiameter.client.impl.helpers.Parameters.SDUseClientMode; import static org.jdiameter.client.impl.helpers.Parameters.Security; import static org.jdiameter.client.impl.helpers.Parameters.SecurityRef; +import static org.jdiameter.client.impl.helpers.Parameters.SessionTimeOut; import static org.jdiameter.client.impl.helpers.Parameters.Statistics; import static org.jdiameter.client.impl.helpers.Parameters.StatisticsActiveList; import static org.jdiameter.client.impl.helpers.Parameters.StatisticsEnabled; @@ -123,6 +125,7 @@ import static org.jdiameter.client.impl.helpers.Parameters.ThreadPoolPriority; import static org.jdiameter.client.impl.helpers.Parameters.ThreadPoolSize; import static org.jdiameter.client.impl.helpers.Parameters.TrustData; +import static org.jdiameter.client.impl.helpers.Parameters.TxTimeOut; import static org.jdiameter.client.impl.helpers.Parameters.UseUriAsFqdn; import static org.jdiameter.client.impl.helpers.Parameters.VendorId; import static org.jdiameter.server.impl.helpers.ExtensionPoint.InternalNetWork; @@ -364,6 +367,9 @@ else if (nodeName.equals("DpaTimeOut")) { else if (nodeName.equals("RecTimeOut")) { add(RecTimeOut, getLongValue(c.item(i))); } + else if (nodeName.equals("SessionTimeOut")) { + add(SessionTimeOut, getLongValue(c.item(i))); + } else if (nodeName.equals("BindDelay")) { add(BindDelay, getLongValue(c.item(i))); } @@ -385,12 +391,29 @@ else if (nodeName.equals("Dictionary")) { else if (nodeName.equals("RequestTable")) { addRequestTable(RequestTable, c.item(i)); } + else if (nodeName.equals("TxTimeOut")) { + add(TxTimeOut, getLongValue(c.item(i))); + } + else if (nodeName.equals("RetransmissionRequiredResCodes")) { + addRetransmissionRequiredResCodes(c.item(i)); + } else { appendOtherParameter(c.item(i)); } } } + protected void addRetransmissionRequiredResCodes(Node node) { + String[] codesArray = getValue(node).replaceAll(" ", "").split(","); + if (codesArray.length > 0) { + int[] parsedCodesArray = new int[codesArray.length]; + for (int i = 0; i < codesArray.length; i++) { + parsedCodesArray[i] = Integer.parseInt(codesArray[i]); + } + add(RetransmissionRequiredResCodes, parsedCodesArray); + } + } + protected void addThreadPool(Node item) { AppConfiguration threadPoolConfiguration = org.jdiameter.client.impl.helpers.EmptyConfiguration.getInstance(); NamedNodeMap attributes = item.getAttributes(); @@ -453,7 +476,8 @@ protected void addStatisticLogger(org.jdiameter.client.impl.helpers.Parameters n String active_records; if (node.getAttributes().getNamedItem("active_records") != null) { active_records = node.getAttributes().getNamedItem("active_records").getNodeValue(); - } else { + } + else { active_records = (String) StatisticsActiveList.defValue(); } add(name, @@ -553,6 +577,7 @@ protected Configuration addSecurityData(Node node) { } return sd; } + protected void addNetwork(Node node) { NodeList c = node.getChildNodes(); for (int i = 0; i < c.getLength(); i++) { diff --git a/core/jdiameter/impl/src/main/resources/META-INF/jdiameter-client.xsd b/core/jdiameter/impl/src/main/resources/META-INF/jdiameter-client.xsd index d283ced65..9ed0d610f 100644 --- a/core/jdiameter/impl/src/main/resources/META-INF/jdiameter-client.xsd +++ b/core/jdiameter/impl/src/main/resources/META-INF/jdiameter-client.xsd @@ -167,6 +167,14 @@ + + + Session idle time out in milliseconds. + + + + + Default stop time out in milliseconds. @@ -215,6 +223,22 @@ + + + Tx timer as described in chapter 13. of RFC 4006 defined in miliseconds. + + + + + + + + Comma delimited list of result codes which make an initial request to be retransmitted. + + + + + Peer FSM Thread Count. diff --git a/core/jdiameter/impl/src/main/resources/META-INF/jdiameter-server.xsd b/core/jdiameter/impl/src/main/resources/META-INF/jdiameter-server.xsd index b30936af1..5b02b2ec5 100644 --- a/core/jdiameter/impl/src/main/resources/META-INF/jdiameter-server.xsd +++ b/core/jdiameter/impl/src/main/resources/META-INF/jdiameter-server.xsd @@ -77,7 +77,8 @@ - + + @@ -232,6 +233,14 @@ + + + Session idle time out in milliseconds. + + + + + Default stop time out in milliseconds. @@ -280,6 +289,22 @@ + + + Tx timer as described in chapter 13. of RFC 4006 defined in miliseconds. + + + + + + + + Comma delimited list of result codes which make an initial request to be retransmitted. + + + + + Server Socket bind delay in milliseconds. diff --git a/core/mux/common/config/jdiameter-config.xml b/core/mux/common/config/jdiameter-config.xml index e14fcc062..af620828c 100644 --- a/core/mux/common/config/jdiameter-config.xml +++ b/core/mux/common/config/jdiameter-config.xml @@ -78,6 +78,7 @@ + diff --git a/core/mux/common/config/jdiameter-config_baseline.xml b/core/mux/common/config/jdiameter-config_baseline.xml new file mode 100644 index 000000000..e14fcc062 --- /dev/null +++ b/core/mux/common/config/jdiameter-config_baseline.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/mux/common/config/jdiameter-config_ext_routing_failover.xml b/core/mux/common/config/jdiameter-config_ext_routing_failover.xml new file mode 100644 index 000000000..0be6654c4 --- /dev/null +++ b/core/mux/common/config/jdiameter-config_ext_routing_failover.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackMultiplexer.java b/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackMultiplexer.java index d033edf2e..f9785ba6a 100644 --- a/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackMultiplexer.java +++ b/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackMultiplexer.java @@ -58,9 +58,11 @@ import static org.jdiameter.client.impl.helpers.Parameters.RealmEntry; import static org.jdiameter.client.impl.helpers.Parameters.RealmTable; import static org.jdiameter.client.impl.helpers.Parameters.RecTimeOut; +import static org.jdiameter.client.impl.helpers.Parameters.RetransmissionRequiredResCodes; import static org.jdiameter.client.impl.helpers.Parameters.StatisticsLoggerDelay; import static org.jdiameter.client.impl.helpers.Parameters.StatisticsLoggerPause; import static org.jdiameter.client.impl.helpers.Parameters.StopTimeOut; +import static org.jdiameter.client.impl.helpers.Parameters.TxTimeOut; import static org.jdiameter.client.impl.helpers.Parameters.UseUriAsFqdn; import static org.jdiameter.server.impl.helpers.Parameters.AcceptUndefinedPeer; import static org.jdiameter.server.impl.helpers.Parameters.DuplicateTimer; @@ -103,6 +105,7 @@ import org.jdiameter.api.Request; import org.jdiameter.api.ResultCode; import org.jdiameter.api.Session; +import org.jdiameter.api.SessionPersistenceStorage; import org.jdiameter.api.Stack; import org.jdiameter.client.api.controller.IRealm; import org.jdiameter.client.api.controller.IRealmTable; @@ -584,6 +587,7 @@ public void unregisterListener(DiameterListener listener) { * n QueueSize */ + private static final String NEW_LINE = System.getProperty("line.separator"); private final String DEFAULT_STRING = "default_string"; private MutableConfiguration getMutableConfiguration() throws MBeanException { @@ -849,6 +853,23 @@ public void _Parameters_setRecTimeout(long stopTimeout) throws MBeanException { getMutableConfiguration().setLongValue(RecTimeOut.ordinal(), stopTimeout); } + public void _Parameters_setTxTimeout(long txTimeout) throws MBeanException { + getMutableConfiguration().setLongValue(TxTimeOut.ordinal(), txTimeout); + } + + public void _Parameters_setRetransmissionRequiredResCodes(String resCodes) throws MBeanException { + if(resCodes != null && resCodes.length() > 0) { + String[] codesArray = resCodes.replaceAll(" ", "").split(","); + if(codesArray.length > 0) { + int[] parsedCodesArray = new int[codesArray.length]; + for(int i=0; i < codesArray.length; i++) { + parsedCodesArray[i] = Integer.parseInt(codesArray[i]); + } + getMutableConfiguration().setIntArrayValue(RetransmissionRequiredResCodes.ordinal(), parsedCodesArray); + } + } + } + @Override public void _Parameters_setConcurrentEntity(String name, String desc, Integer size) throws MBeanException { for (Configuration c : getMutableConfiguration().getChildren(Concurrent.ordinal())) { @@ -989,5 +1010,23 @@ public boolean _Network_Peers_isPeerConnected(String name) throws MBeanException } } + public String _Network_Sessions_getPersistenceMap(int maxLimit) throws MBeanException { + try { + SessionPersistenceStorage sds = stack.getSessionPersistenceStorage(); + if(sds == null) { + return "Session persistence is not supported in current configuration!!"; + } + + StringBuilder sb = new StringBuilder(); + List sessions = sds.dumpStickySessions(maxLimit); + for(String session : sessions) { + sb.append(session).append(NEW_LINE); + } + return sb.length() > 0 ? sb.toString() : "No sessions found"; + } + catch (Exception e) { + throw new MBeanException(e, "Failed to get session storage"); + } + } } diff --git a/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackMultiplexerMBean.java b/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackMultiplexerMBean.java index 8599ac34f..956a641f7 100644 --- a/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackMultiplexerMBean.java +++ b/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackMultiplexerMBean.java @@ -207,6 +207,23 @@ public interface DiameterStackMultiplexerMBean extends ServiceMBean { */ void _Parameters_setRecTimeout(long recTimeout) throws MBeanException; + /** + * Sets the waiting time in the client in the Pending state. (default: 10000, 10 seconds). + * + * @param txTimeout the amount of time, in ms. + * @throws MBeanException if the operation is unable to perform correctly + */ + void _Parameters_setTxTimeout(long txTimeout) throws MBeanException; + + /** + * Defines a list of result codes which make an initial request to be retransmitted to + * another remote peer. + * + * @param resCodes comma delimited list of result codes + * @throws MBeanException if the operation is unable to perform correctly + */ + void _Parameters_setRetransmissionRequiredResCodes(String resCodes) throws MBeanException; + void _Parameters_setConcurrentEntity(String name, String desc, Integer size) throws MBeanException; void _Parameters_setStatisticLoggerDelay(long delay) throws MBeanException; @@ -337,4 +354,14 @@ void _Network_Realms_addRealm(String name, String peers, long appVendorId, long boolean _Network_Peers_isPeerConnected(String name) throws MBeanException; + // Sessions : routing persistence map ------------------------------------ + + /** + * Gets the current state of session persistence map used for routing and lists + * all sticky sessions that are currently in operation. + * + * @param maxLimit maximum number of records to be listed (0 corresponds to no limit) + * @throws MBeanException if the operation is unable to perform correctly + */ + String _Network_Sessions_getPersistenceMap(int maxLimit) throws MBeanException; } diff --git a/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackProxy.java b/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackProxy.java index 4269380ff..6b9d0310e 100644 --- a/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackProxy.java +++ b/core/mux/jar/src/main/java/org/mobicents/diameter/stack/DiameterStackProxy.java @@ -57,6 +57,7 @@ import org.jdiameter.api.NetworkReqListener; import org.jdiameter.api.RouteException; import org.jdiameter.api.SessionFactory; +import org.jdiameter.api.SessionPersistenceStorage; import org.jdiameter.api.Stack; import org.jdiameter.api.validation.Dictionary; import org.jdiameter.client.api.IAssembler; @@ -174,6 +175,11 @@ public IAssembler getAssemblerFacility() { return ((IContainer) realStack).getAssemblerFacility(); } + @Override + public SessionPersistenceStorage getSessionPersistenceStorage() { + return ((IContainer)realStack).getSessionPersistenceStorage(); + } + /* (non-Javadoc) * @see org.jdiameter.api.Stack#getDictionary() */ diff --git a/core/mux/jar/src/main/java/org/mobicents/diameter/stack/management/Parameters.java b/core/mux/jar/src/main/java/org/mobicents/diameter/stack/management/Parameters.java index 3e60ed1e7..a073050cb 100644 --- a/core/mux/jar/src/main/java/org/mobicents/diameter/stack/management/Parameters.java +++ b/core/mux/jar/src/main/java/org/mobicents/diameter/stack/management/Parameters.java @@ -95,6 +95,10 @@ public interface Parameters extends Serializable { void setRecTimeout(long recTimeout); + long getTxTimeout(); + + void setTxTimeout(long txTimeout); + /* Gone since merge with build-350 public String getThreadPool_Priority(); diff --git a/core/mux/jar/src/main/java/org/mobicents/diameter/stack/management/ParametersImpl.java b/core/mux/jar/src/main/java/org/mobicents/diameter/stack/management/ParametersImpl.java index 17f29e8f7..c5b8e1293 100644 --- a/core/mux/jar/src/main/java/org/mobicents/diameter/stack/management/ParametersImpl.java +++ b/core/mux/jar/src/main/java/org/mobicents/diameter/stack/management/ParametersImpl.java @@ -53,9 +53,11 @@ import static org.jdiameter.client.impl.helpers.Parameters.MessageTimeOut; import static org.jdiameter.client.impl.helpers.Parameters.QueueSize; import static org.jdiameter.client.impl.helpers.Parameters.RecTimeOut; +import static org.jdiameter.client.impl.helpers.Parameters.RetransmissionRequiredResCodes; import static org.jdiameter.client.impl.helpers.Parameters.StatisticsLoggerDelay; import static org.jdiameter.client.impl.helpers.Parameters.StatisticsLoggerPause; import static org.jdiameter.client.impl.helpers.Parameters.StopTimeOut; +import static org.jdiameter.client.impl.helpers.Parameters.TxTimeOut; import static org.jdiameter.client.impl.helpers.Parameters.UseUriAsFqdn; import static org.jdiameter.server.impl.helpers.Parameters.AcceptUndefinedPeer; import static org.jdiameter.server.impl.helpers.Parameters.DuplicateProtection; @@ -63,6 +65,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.HashMap; import org.jdiameter.api.Configuration; @@ -93,6 +96,8 @@ public class ParametersImpl implements Parameters { private long dwaTimeout; private long dpaTimeout; private long recTimeout; + private long txTimeout; + private String retransmissionRequiredResCodes; // Gone since merge with build-350 // private String threadPool_Priority; @@ -119,6 +124,8 @@ public ParametersImpl(MutableConfiguration config) { this.dwaTimeout = config.getLongValue(DwaTimeOut.ordinal(), 10000L); this.dpaTimeout = config.getLongValue(DpaTimeOut.ordinal(), 5000L); this.recTimeout = config.getLongValue(RecTimeOut.ordinal(), 10000L); + this.txTimeout = config.getLongValue(TxTimeOut.ordinal(), 10000); + this.retransmissionRequiredResCodes = Arrays.toString(config.getIntArrayValue(RetransmissionRequiredResCodes.ordinal(), null)); // Concurrent Entities for (Configuration concurrentEntity : config.getChildren(Concurrent.ordinal())) { @@ -253,6 +260,22 @@ public void setRecTimeout(long recTimeout) { DiameterConfiguration.getMutableConfiguration().setLongValue(RecTimeOut.ordinal(), recTimeout); } + public long getTxTimeout() { + return txTimeout; + } + + public void setTxTimeout(long txTimeout) { + DiameterConfiguration.getMutableConfiguration().setLongValue(TxTimeOut.ordinal(), txTimeout); + } + + public String getRetransmissionRequiredResCodes() { + return retransmissionRequiredResCodes; + } + + public void setTxTimeout(int[] resCodes) { + DiameterConfiguration.getMutableConfiguration().setIntArrayValue(RetransmissionRequiredResCodes.ordinal(), resCodes); + } + /* Gone since merge with build-350 public String getThreadPool_Priority() { return threadPool_Priority; diff --git a/core/mux/pom.xml b/core/mux/pom.xml index 242066793..442de5580 100644 --- a/core/mux/pom.xml +++ b/core/mux/pom.xml @@ -17,6 +17,8 @@ 1.5.9.0-build538-SNAPSHOT 1.1.0-SNAPSHOT + + jdiameter-config_baseline.xml pom @@ -93,6 +95,12 @@ sar-jboss-7 + + failover-config-enabled + + jdiameter-config_ext_routing_failover.xml + + diff --git a/core/mux/sar-jboss-4/pom.xml b/core/mux/sar-jboss-4/pom.xml index 7b6ab17bd..72fe74248 100644 --- a/core/mux/sar-jboss-4/pom.xml +++ b/core/mux/sar-jboss-4/pom.xml @@ -45,6 +45,16 @@ ../common/config false + + jdiameter-config_*.xml + + + + ../common/config + false + + ${jdiameter.mux.config.file} + @@ -141,6 +151,7 @@ + diff --git a/core/mux/sar-jboss-5/pom.xml b/core/mux/sar-jboss-5/pom.xml index 98f159b29..9859a9034 100644 --- a/core/mux/sar-jboss-5/pom.xml +++ b/core/mux/sar-jboss-5/pom.xml @@ -45,6 +45,16 @@ ../common/config false + + jdiameter-config_*.xml + + + + ../common/config + false + + ${jdiameter.mux.config.file} + @@ -141,7 +151,8 @@ - + + diff --git a/core/mux/sar-jboss-7/pom.xml b/core/mux/sar-jboss-7/pom.xml index 3a1c2a541..406562ff1 100644 --- a/core/mux/sar-jboss-7/pom.xml +++ b/core/mux/sar-jboss-7/pom.xml @@ -149,10 +149,13 @@ - + + - + + diff --git a/testsuite/tests/src/test/java/org/mobicents/diameter/stack/functional/ro/base/Client.java b/testsuite/tests/src/test/java/org/mobicents/diameter/stack/functional/ro/base/Client.java index 66c883fb4..cdc7d9059 100644 --- a/testsuite/tests/src/test/java/org/mobicents/diameter/stack/functional/ro/base/Client.java +++ b/testsuite/tests/src/test/java/org/mobicents/diameter/stack/functional/ro/base/Client.java @@ -23,7 +23,9 @@ import org.jdiameter.api.IllegalDiameterStateException; import org.jdiameter.api.InternalException; +import org.jdiameter.api.Message; import org.jdiameter.api.OverloadException; +import org.jdiameter.api.Peer; import org.jdiameter.api.RouteException; import org.jdiameter.api.app.AppAnswerEvent; import org.jdiameter.api.app.AppRequestEvent; @@ -169,6 +171,22 @@ public void doOtherEvent(AppSession session, AppRequestEvent request, AppAnswerE fail("Received \"Other\" event, request[" + request + "], answer[" + answer + "], on session[" + session + "]", null); } + @Override + public void doRequestTxTimeout(ClientRoSession clientRoSession, Message message, Peer peer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { + + } + + @Override + public void doRequestTimeout(ClientRoSession clientRoSession, Message message, Peer peer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException { + + } + + @Override + public void doPeerUnavailability(ClientRoSession clientRoSession, Message message, Peer peer, RouteException e) throws InternalException, IllegalDiameterStateException, + RouteException, OverloadException { + + } + // ------------ getters for some vars; public boolean isSentINITIAL() { @@ -215,4 +233,8 @@ protected String getServiceContextId() { return "tralalalal ID"; } + @Override + public int getDefaultCCSFValue() { + return 0; + } } diff --git a/testsuite/tests/src/test/java/org/mobicents/diameter/stack/functional/ro/base/RoSessionBasicFlowIdleTest.java b/testsuite/tests/src/test/java/org/mobicents/diameter/stack/functional/ro/base/RoSessionBasicFlowIdleTest.java new file mode 100644 index 000000000..5ece58381 --- /dev/null +++ b/testsuite/tests/src/test/java/org/mobicents/diameter/stack/functional/ro/base/RoSessionBasicFlowIdleTest.java @@ -0,0 +1,238 @@ +/* + * TeleStax, Open Source Cloud Communications + * Copyright 2011-2017, 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.mobicents.diameter.stack.functional.ro.base; + +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileInputStream; +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.jdiameter.api.DisconnectCause; +import org.jdiameter.api.Mode; +import org.jdiameter.api.Peer; +import org.jdiameter.api.PeerState; +import org.jdiameter.api.PeerTable; +import org.jdiameter.api.Stack; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * + * @author Alexandre Mendonca + * @author Bartosz Baranowski + */ +@RunWith(Parameterized.class) +public class RoSessionBasicFlowIdleTest { + + private Client clientNode; + private Server serverNode1; + private URI clientConfigURI; + private URI serverNode1ConfigURI; + + /** + * @param clientNode + * @param node1 + * @param node2 + * @param serverCount + */ + public RoSessionBasicFlowIdleTest(String clientConfigUrl, String serverNode1ConfigURL) throws Exception { + super(); + this.clientConfigURI = new URI(clientConfigUrl); + this.serverNode1ConfigURI = new URI(serverNode1ConfigURL); + } + + @Before + public void setUp() throws Exception { + try { + this.clientNode = new Client(); + this.serverNode1 = new Server(); + + this.serverNode1.init(new FileInputStream(new File(this.serverNode1ConfigURI)), "SERVER1"); + this.serverNode1.start(); + + this.clientNode.init(new FileInputStream(new File(this.clientConfigURI)), "CLIENT"); + this.clientNode.start(Mode.ANY_PEER, 10, TimeUnit.SECONDS); + Stack stack = this.clientNode.getStack(); + List peers = stack.unwrap(PeerTable.class).getPeerTable(); + if (peers.size() == 1) { + // ok + } + else if (peers.size() > 1) { + // works better with replicated, since disconnected peers are also listed + boolean foundConnected = false; + for (Peer p : peers) { + if (p.getState(PeerState.class).equals(PeerState.OKAY)) { + if (foundConnected) { + throw new Exception("Wrong number of connected peers: " + peers); + } + foundConnected = true; + } + } + } + else { + throw new Exception("Wrong number of connected peers: " + peers); + } + } + catch (Throwable e) { + e.printStackTrace(); + } + } + + @After + public void tearDown() { + if (this.serverNode1 != null) { + try { + this.serverNode1.stop(DisconnectCause.REBOOTING); + } + catch (Exception e) { + + } + this.serverNode1 = null; + } + + if (this.clientNode != null) { + try { + this.clientNode.stop(DisconnectCause.REBOOTING); + } + catch (Exception e) { + + } + this.clientNode = null; + } + } + + @Test + public void testBasicFlow() throws Exception { + try { + // pain of parameter tests :) ? + clientNode.sendInitial(); + waitForMessage(); + + serverNode1.sendInitial(); + waitForMessage(); + + clientNode.sendInterim(); + waitForMessage(); + + serverNode1.sendInterim(); + waitForMessage(5500); // we wait a bit more so session expires with idle + + clientNode.sendTermination(); + waitForMessage(); + + serverNode1.sendTermination(); + waitForMessage(); + } + catch (Exception e) { + // in case the session expired even before receiving the termination + if (!"Request: null".equals(e.getMessage())) { + e.printStackTrace(); + fail(e.toString()); + } + } + + if (!clientNode.isReceiveINITIAL()) { + StringBuilder sb = new StringBuilder("Did not receive INITIAL! "); + sb.append("Client ER:\n").append(clientNode.createErrorReport(this.clientNode.getErrors())); + + fail(sb.toString()); + } + if (!clientNode.isReceiveINTERIM()) { + StringBuilder sb = new StringBuilder("Did not receive INTERIM! "); + sb.append("Client ER:\n").append(clientNode.createErrorReport(this.clientNode.getErrors())); + + fail(sb.toString()); + } + if (!clientNode.isReceiveTERMINATE()) { + // we don't fail here, we wanted session to be terminated when server tries to receive/send TERMINATE + } + if (clientNode.isReceiveTERMINATE()) { + StringBuilder sb = new StringBuilder("Did receive TERMINATE, should not, session should have been terminated by idle! "); + sb.append("Client ER:\n").append(clientNode.createErrorReport(this.clientNode.getErrors())); + + fail(sb.toString()); + } + if (!clientNode.isPassed()) { + StringBuilder sb = new StringBuilder(); + sb.append("Client ER:\n").append(clientNode.createErrorReport(this.clientNode.getErrors())); + + fail(sb.toString()); + } + + if (!serverNode1.isReceiveINITIAL()) { + StringBuilder sb = new StringBuilder("Did not receive INITIAL! "); + sb.append("Server ER:\n").append(serverNode1.createErrorReport(this.serverNode1.getErrors())); + + fail(sb.toString()); + } + else if (!serverNode1.isReceiveINTERIM()) { + StringBuilder sb = new StringBuilder("Did not receive INTERIM! "); + sb.append("Server ER:\n").append(serverNode1.createErrorReport(this.serverNode1.getErrors())); + + fail(sb.toString()); + } + else if (!serverNode1.isReceiveTERMINATE()) { + // we don't fail here, we wanted session to be terminated when server tries to receive/send TERMINATE + } + if (!serverNode1.isPassed()) { + // we don't fail here, we wanted session to be terminated when server tries to receive/send TERMINATE + } + } + + @Parameters + public static Collection data() { + String client = "configurations/functional-ro/config-client.xml"; + String server1 = "configurations/functional-ro/config-server-node1-session-idle.xml"; + + String replicatedClient = "configurations/functional-ro/replicated-config-client.xml"; + String replicatedServer1 = "configurations/functional-ro/replicated-config-server-node1.xml"; + + Class t = RoSessionBasicFlowIdleTest.class; + client = t.getClassLoader().getResource(client).toString(); + server1 = t.getClassLoader().getResource(server1).toString(); + replicatedClient = t.getClassLoader().getResource(replicatedClient).toString(); + replicatedServer1 = t.getClassLoader().getResource(replicatedServer1).toString(); + + return Arrays.asList(new Object[][] { { client, server1 }/*, { replicatedClient, replicatedServer1 }*/ }); + } + + private void waitForMessage(long time) { + try { + Thread.sleep(time); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private void waitForMessage() { + waitForMessage(2000); + } + +} diff --git a/testsuite/tests/src/test/resources/configurations/functional-ro/config-server-node1-session-idle.xml b/testsuite/tests/src/test/resources/configurations/functional-ro/config-server-node1-session-idle.xml new file mode 100644 index 000000000..a414482eb --- /dev/null +++ b/testsuite/tests/src/test/resources/configurations/functional-ro/config-server-node1-session-idle.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file