From f472ddbedec007b37fff5b40a379b97656f15bce Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Fri, 12 Oct 2012 02:51:34 +0200 Subject: Document transport_opt() disconnect_cb Callback makes sending of DPR configurable. --- lib/diameter/doc/src/diameter.xml | 149 +++++++++++++++++++++++----- lib/diameter/doc/src/diameter_transport.xml | 6 +- 2 files changed, 128 insertions(+), 27 deletions(-) diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index 80863f8eff..e1442a108e 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -822,6 +822,7 @@ request a connection with one peer over SCTP or another To listen on both SCTP and TCP, define one transport for each.

+ {applications, [application_alias()]}

@@ -831,6 +832,7 @@ Defaults to all applications configured on the service in question. Applications not configured on the service in question are ignored.

+ {capabilities, [capability()]}

@@ -845,33 +847,136 @@ TLS is desired over TCP as implemented by diameter_tcp(3).

+ {capabilities_cb, evaluable()}

A callback invoked upon reception of CER/CEA during capabilities exchange in order to ask whether or not the connection should be accepted. -Applied to the relevant transport_ref() and the -#diameter_caps{} record of the connection. -Returning ok accepts the connection. -Returning integer() causes an incoming -CER to be answered with the specified Result-Code. -Returning discard causes an incoming CER to -be discarded. -Returning unknown is equivalent to returning 3010, -DIAMETER_UNKNOWN_PEER. -Returning anything but ok or a 2xxx series result -code causes the transport connection to be broken.

+Applied to the transport_ref() and +#diameter_caps{} record of the connection.

+ +

+The return value can have one of the following types.

+ +ok + +

+Accept the connection.

+
+ +integer() + +

+Causes an incoming CER to be answered with the specified Result-Code.

+
+ +discard + +

+Causes an incoming CER to be discarded without CEA being sent.

+
+ +unknown +

+Equivalent to returning 3010, DIAMETER_UNKNOWN_PEER.

+
+
+ +

+Returning anything but ok or a 2xxx series result +code causes the transport connection to be broken. Multiple capabilities_cb options can be specified, in which case the corresponding callbacks are applied until either all return ok or one does not.

+
- + +{disconnect_cb, evaluable()} + + +

+A callback invoked prior to requesting shutdown of a transport process +for a transport connection having watchdog state OKAY. +Applied to Reason=transport|service|application and the +transport_ref() and +diameter_app:peer() +in question, Reason indicating whether the the diameter +application is being stopped, the service in question is being stopped +at diameter:stop_service/1 or +the transport in question is being removed at diameter:remove_transport/2, +respectively.

+ +

+The return value can have one of the following types.

+ + +{dpr, [option()]} + +

+Causes Disconnect-Peer-Request to be sent to the peer, transport +process shutdown being requested after reception of +Disconnect-Peer-Answer or timeout. +An option() can be one of the following.

+ + +{timeout, integer()} + +

+Transport process shutdown will be requested after this number of +milliseconds if DPA is not received. +Defaults to 1000.

+{cause, 0|rebooting|1|busy|2|goaway} + +

+The Disconnect-Cause to send, REBOOTING, BUSY and +DO_NOT_WANT_TO_TALK_TO_YOU respectively. +Defaults to rebooting for Reason=service|application and +goaway for Reason=transport.

+
+
+
+ +dpr + +

+Equivalent to {dpr, []}.

+
+ +close + +

+Causes transport process shutdown to be requested without +Disconnect-Peer-Request being sent to the peer.

+
+ +ignore + +

+Equivalent to not having configured the callback.

+
+
+ +

+Multiple disconnect_cb options can be specified, in which +case the corresponding callbacks are applied until one of them returns +a value other than ignore. +All callbacks returning ignore is equivalent to not having +configured them.

+ +

+Defaults to a single callback returning dpr.

+
+ + {watchdog_timer, TwInit} @@ -891,10 +996,9 @@ the callback.

An integer value must be at least 6000 as required by RFC 3539. Defaults to 30000 if unspecified.

- -
+ {reconnect_timer, Tc} @@ -1185,15 +1289,12 @@ Pred = {M,F,A}: fun(Ref, Type, Opts) -> apply(M, F, [Ref, Type, Opts | A]) end

-Removing a transport causes all associated transport connections to -be broken. -A DPR message with -Disconnect-Cause DO_NOT_WANT_TO_TALK_TO_YOU will be sent -to each connected peer before disassociating the transport configuration -from the service and terminating the transport upon reception of -DPA or timeout.

- - +Removing a transport causes the corresponding transport processes to +be asked to terminate. +Whether or not a DPR message is sent to a peer is +controlled by +value of disconnect_cb +configured on the transport.

diff --git a/lib/diameter/doc/src/diameter_transport.xml b/lib/diameter/doc/src/diameter_transport.xml index d9b36a1e09..0c8b41397a 100644 --- a/lib/diameter/doc/src/diameter_transport.xml +++ b/lib/diameter/doc/src/diameter_transport.xml @@ -149,9 +149,9 @@ contains the binary to send.

{diameter, {close, Pid}}

-A request to close the transport connection. -The transport process should terminate after closing the -connection. +A request to terminate the transport process after having received DPA +in response to DPR. +The transport process should exit. Pid is the pid() of the parent process.

-- cgit v1.2.3 From 380a3f0aece61c649efaed45041c8679038891f1 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Fri, 12 Oct 2012 11:19:49 +0200 Subject: Implement transport_opt() disconnect_cb --- lib/diameter/src/base/diameter.erl | 1 + lib/diameter/src/base/diameter_peer_fsm.erl | 187 +++++++++++++++++++++------- lib/diameter/src/base/diameter_service.erl | 64 +++++----- 3 files changed, 179 insertions(+), 73 deletions(-) diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl index 3e3a6be0ef..95702f03d4 100644 --- a/lib/diameter/src/base/diameter.erl +++ b/lib/diameter/src/base/diameter.erl @@ -330,6 +330,7 @@ call(SvcName, App, Message) -> | {applications, [app_alias()]} | {capabilities, [capability()]} | {capabilities_cb, evaluable()} + | {disconnect_cb, evaluable()} | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}} | {reconnect_timer, 'Unsigned32'()} | {private, any()}. diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index 3f4945f7a6..ecdb09d34a 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -48,15 +48,19 @@ -include("diameter_internal.hrl"). -include("diameter_gen_base_rfc3588.hrl"). +%% Values of Disconnect-Cause in DPR. -define(GOAWAY, ?'DIAMETER_BASE_DISCONNECT-CAUSE_DO_NOT_WANT_TO_TALK_TO_YOU'). -define(REBOOT, ?'DIAMETER_BASE_DISCONNECT-CAUSE_REBOOTING'). +-define(BUSY, ?'DIAMETER_BASE_DISCONNECT-CAUSE_BUSY'). -define(NO_INBAND_SECURITY, 0). -define(TLS, 1). %% Keys in process dictionary. -define(CB_KEY, cb). %% capabilities callback +-define(DPR_KEY, dpr). %% disconnect callback -define(DWA_KEY, dwa). %% outgoing DWA +-define(REF_KEY, ref). %% transport_ref() -define(Q_KEY, q). %% transport start queue -define(START_KEY, start). %% start of connected transport -define(SEQUENCE_KEY, mask). %% mask for sequence numbers @@ -68,6 +72,13 @@ %% A 2xxx series Result-Code. Not necessarily 2001. -define(IS_SUCCESS(N), 2 == (N) div 1000). +%% Guards. +-define(IS_UINT32(N), (0 =< N andalso 0 == N bsr 32)). +-define(IS_TIMEOUT(N), ?IS_UINT32(N)). +-define(IS_CAUSE(N), N == ?REBOOT; N == rebooting; + N == ?GOAWAY; N == goaway; + N == ?BUSY; N == busy). + %% RFC 3588: %% %% Timeout An application-defined timer has expired while waiting @@ -75,18 +86,16 @@ %% -define(EVENT_TIMEOUT, 10000). -%% How long to wait for a DPA in response to DPR before simply -%% aborting. Used to distinguish between shutdown and not but there's -%% not really any need. Stopping a service will require a timeout if -%% the peer doesn't answer DPR so the value should be short-ish. +%% Default timeout for DPA in response to DPR. A bit short but the +%% timeout used to be hardcoded. (So it could be worse.) -define(DPA_TIMEOUT, 1000). -record(state, {state = 'Wait-Conn-Ack' %% state of RFC 3588 Peer State Machine :: 'Wait-Conn-Ack' | recv_CER | 'Wait-CEA' | 'Open', mode :: accept | connect | {connect, reference()}, - parent :: pid(), - transport :: pid(), + parent :: pid(), %% watchdog process + transport :: pid(), %% transport process service :: #diameter_service{}, dpr = false :: false | {diameter:'Unsigned32'(), diameter:'Unsigned32'()}}). @@ -163,14 +172,16 @@ i({WPid, Type, Opts, #diameter_service{} = Svc}) -> %% from old code i({WPid, Type, Opts, {?NOMASK, [node() | nodes()], Svc}}); i({WPid, T, Opts, {Mask, Nodes, #diameter_service{applications = Apps, - capabilities = Caps} + capabilities = LCaps} = Svc}}) -> [] /= Apps orelse ?ERROR({no_apps, T, Opts}), - putr(?DWA_KEY, dwa(Caps)), + putr(?DWA_KEY, dwa(LCaps)), {M, Ref} = T, diameter_stats:reg(Ref), - {[Ts], Rest} = proplists:split(Opts, [capabilities_cb]), - putr(?CB_KEY, {Ref, [F || {_,F} <- Ts]}), + {[Cs,Ds], Rest} = proplists:split(Opts, [capabilities_cb, disconnect_cb]), + putr(?CB_KEY, {Ref, [F || {_,F} <- Cs]}), + putr(?DPR_KEY, [F || {_, F} <- Ds]), + putr(?REF_KEY, Ref), putr(?SEQUENCE_KEY, Mask), putr(?RESTRICT_KEY, Nodes), erlang:monitor(process, WPid), @@ -188,8 +199,8 @@ i({WPid, T, Opts, {Mask, Nodes, #diameter_service{applications = Apps, %% watchdog start (start/2) succeeds regardless so as not to crash the %% service. -start_transport(T, Opts, #diameter_service{capabilities = Caps} = Svc) -> - Addrs0 = Caps#diameter_caps.host_ip_address, +start_transport(T, Opts, #diameter_service{capabilities = LCaps} = Svc) -> + Addrs0 = LCaps#diameter_caps.host_ip_address, start_transport(Addrs0, {T, Opts, Svc}). start_transport(Addrs0, T) -> @@ -212,9 +223,9 @@ svc(Svc, []) -> svc(Svc, Addrs) -> readdr(Svc, Addrs). -readdr(#diameter_service{capabilities = Caps0} = Svc, Addrs) -> - Caps = Caps0#diameter_caps{host_ip_address = Addrs}, - Svc#diameter_service{capabilities = Caps}. +readdr(#diameter_service{capabilities = LCaps0} = Svc, Addrs) -> + LCaps = LCaps0#diameter_caps{host_ip_address = Addrs}, + Svc#diameter_service{capabilities = LCaps}. %% The 4-tuple Data returned from diameter_peer:start/1 identifies the %% transport module/config use to start the transport process in @@ -375,17 +386,17 @@ transition({send, Msg}, #state{transport = TPid}) -> send(TPid, Msg), ok; -%% Request for graceful shutdown. -transition({shutdown, Pid}, #state{parent = Pid, dpr = false} = S) -> - dpr(?GOAWAY, S); -transition({shutdown, Pid}, #state{parent = Pid}) -> - ok; - -%% Application shutdown. -transition(shutdown, #state{dpr = false} = S) -> - dpr(?REBOOT, S); -transition(shutdown, _) -> %% DPR already send: ensure expected timeout - dpa_timer(), +%% Messages from old (diameter_service) code. +transition(shutdown = T, #state{parent = Pid} = S) -> + transition({T, Pid, service}, S); %% Reason irrelevant: old code has no cb + +%% Request for graceful shutdown at remove_transport, stop_service of +%% application shutdown. +transition({shutdown = T, Pid}, S) -> + transition({T, Pid, transport}, S); +transition({shutdown, Pid, Reason}, #state{parent = Pid, dpr = false} = S) -> + dpr(Reason, S); +transition({shutdown, Pid, _}, #state{parent = Pid}) -> ok; %% Request to close the transport connection. @@ -441,13 +452,13 @@ start_next(#state{service = Svc0} = S) -> %% send_CER/1 send_CER(#state{mode = {connect, Remote}, - service = #diameter_service{capabilities = Caps}, + service = #diameter_service{capabilities = LCaps}, transport = TPid} = S) -> - OH = Caps#diameter_caps.origin_host, + OH = LCaps#diameter_caps.origin_host, req_send_CER(OH, Remote) orelse - close({already_connected, Remote, Caps}, S), + close({already_connected, Remote, LCaps}, S), CER = build_CER(S), ?LOG(send, 'CER'), send(TPid, encode(CER)), @@ -471,8 +482,8 @@ start_timer(#state{state = PS} = S) -> %% build_CER/1 -build_CER(#state{service = #diameter_service{capabilities = Caps}}) -> - {ok, CER} = diameter_capx:build_CER(Caps), +build_CER(#state{service = #diameter_service{capabilities = LCaps}}) -> + {ok, CER} = diameter_capx:build_CER(LCaps), CER. %% encode/1 @@ -800,8 +811,8 @@ a('CER', #diameter_caps{vendor_id = Vid, {'Product-Name', Name}, {'Origin-State-Id', OSI}]; -a('DPR', #diameter_caps{origin_host = Host, - origin_realm = Realm}) -> +a('DPR', #diameter_caps{origin_host = {Host, _}, + origin_realm = {Realm, _}}) -> ['DPA', {'Origin-Host', Host}, {'Origin-Realm', Realm}]. @@ -909,7 +920,9 @@ rejected(N) %% open/5 -open(Pkt, SupportedApps, Caps, {Type, IS}, #state{parent = Pid} = S) -> +open(Pkt, SupportedApps, Caps, {Type, IS}, #state{parent = Pid, + service = Svc} + = S) -> #diameter_caps{origin_host = {_,_} = H, inband_security_id = {LS,_}} = Caps, @@ -917,7 +930,9 @@ open(Pkt, SupportedApps, Caps, {Type, IS}, #state{parent = Pid} = S) -> tls_ack(lists:member(?TLS, LS), Caps, Type, IS, S), Pid ! {open, self(), H, {Caps, SupportedApps, Pkt}}, - S#state{state = 'Open'}. + %% Replace capabilities record with local/remote pairs. + S#state{state = 'Open', + service = Svc#diameter_service{capabilities = Caps}}. %% We've advertised TLS support: tell the transport the result %% and expect a reply when the handshake is complete. @@ -970,24 +985,110 @@ dwa(#diameter_caps{origin_host = OH, {'Origin-State-Id', OSI}]. %% dpr/2 +%% +%% The RFC isn't clear on whether DPR should be send in a non-Open +%% state. The Peer State Machine transitions it documents aren't +%% exhaustive (no Stop in Wait-I-CEA for example) so assume it's up to +%% the implementation and transition to Closed (ie. die) if we haven't +%% yet reached Open. + +%% Connection is open, DPR has not been sent. +dpr(Reason, #state{state = 'Open', + dpr = false, + service = #diameter_service{capabilities = Caps}} + = S) -> + case getr(?DPR_KEY) of + CBs when is_list(CBs) -> + Ref = getr(?REF_KEY), + Peer = {self(), Caps}, + dpr(CBs, [Reason, Ref, Peer], S); + undefined -> %% started in old code + send_dpr(Reason, [], S) + end; -dpr(Cause, #state{transport = TPid, - service = #diameter_service{capabilities = Caps}} - = S) -> - #diameter_caps{origin_host = OH, - origin_realm = OR} +%% Connection is open, DPR already sent. +dpr(_, #state{state = 'Open'}) -> + ok; + +%% Connection not open. +dpr(_Reason, _S) -> + stop. + +%% dpr/3 +%% +%% Note that an implementation that wants to do something +%% transport_module-specific can lookup the pid of the transport +%% process and contact it. (eg. diameter:service_info/2) + +dpr([CB|Rest], [Reason | _] = Args, S) -> + try diameter_lib:eval([CB | Args]) of + {dpr, Opts} when is_list(Opts) -> + send_dpr(Reason, Opts, S); + dpr -> + send_dpr(Reason, [], S); + close = T -> + {stop, {disconnect_cb, T}}; + ignore -> + dpr(Rest, Args, S); + T -> + No = {disconnect_cb, T}, + diameter_lib:error_report(invalid, No), + {stop, No} + catch + E:R -> + No = {disconnect_cb, E, R, ?STACK}, + diameter_lib:error_report(failure, No), + {stop, No} + end; + +dpr([], [Reason | _], S) -> + send_dpr(Reason, [], S). + +-record(opts, {cause, timeout = ?DPA_TIMEOUT}). + +send_dpr(Reason, Opts, #state{transport = TPid, + service = #diameter_service{capabilities = Caps}} + = S) -> + #opts{cause = Cause, timeout = Tmo} + = lists:foldl(fun opt/2, + #opts{cause = case Reason of + transport -> ?GOAWAY; + _ -> ?REBOOT + end, + timeout = ?DPA_TIMEOUT}, + Opts), + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, _}} = Caps, Bin = encode(['DPR', {'Origin-Host', OH}, {'Origin-Realm', OR}, {'Disconnect-Cause', Cause}]), send(TPid, Bin), - dpa_timer(), + dpa_timer(Tmo), ?LOG(send, 'DPR'), S#state{dpr = diameter_codec:sequence_numbers(Bin)}. -dpa_timer() -> - erlang:send_after(?DPA_TIMEOUT, self(), dpa_timeout). +opt({timeout, Tmo}, Rec) + when ?IS_TIMEOUT(Tmo) -> + Rec#opts{timeout = Tmo}; +opt({cause, Cause}, Rec) + when ?IS_CAUSE(Cause) -> + Rec#opts{cause = cause(Cause)}; +opt(T, _) -> + ?ERROR({invalid_option, T}). + +cause(rebooting) -> ?REBOOT; +cause(goaway) -> ?GOAWAY; +cause(busy) -> ?BUSY; +cause(N) + when ?IS_CAUSE(N) -> + N; +cause(N) -> + ?ERROR({invalid_cause, N}). + +dpa_timer(Tmo) -> + erlang:send_after(Tmo, self(), dpa_timeout). %% register_everywhere/1 %% diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index cffba4fc94..0a0fd6ded1 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -494,7 +494,7 @@ handle_call({info, Item}, _From, S) -> {reply, service_info(Item, S), S}; handle_call(stop, _From, S) -> - shutdown(S), + shutdown(service, S), {stop, normal, ok, S}; %% The server currently isn't guaranteed to be dead when the caller %% gets the reply. We deal with this in the call to the server, @@ -683,7 +683,7 @@ upgrade_insert(#state{service = #diameter_service{pid = Pid}} = S) -> terminate(Reason, #state{service_name = Name} = S) -> ets:delete(?STATE_TABLE, Name), shutdown == Reason %% application shutdown - andalso shutdown(S). + andalso shutdown(application, S). %%% --------------------------------------------------------------------------- %%% # code_change(FromVsn, State, Extra) @@ -766,44 +766,48 @@ mod_state(Alias, ModS) -> %%% # shutdown/2 %%% --------------------------------------------------------------------------- -shutdown(Refs, #state{peerT = PeerT}) -> - ets:foldl(fun(P,ok) -> s(P, Refs), ok end, ok, PeerT). +%% remove_transport: ask watchdogs to terminate their transport. +shutdown(Refs, #state{peerT = PeerT}) + when is_list(Refs) -> + ets:foldl(fun(P,ok) -> sp(P, Refs), ok end, ok, PeerT); -s(#peer{ref = Ref, pid = Pid}, Refs) -> - s(lists:member(Ref, Refs), Pid); - -s(true, Pid) -> - Pid ! {shutdown, self()}; %% 'DOWN' will cleanup as usual -s(false, _) -> - ok. - -%%% --------------------------------------------------------------------------- -%%% # shutdown/1 -%%% --------------------------------------------------------------------------- - -shutdown(#state{peerT = PeerT}) -> +%% application/service shutdown: ask transports to terminate themselves. +shutdown(Reason, #state{peerT = PeerT}) -> %% A transport might not be alive to receive the shutdown request %% but give those that are a chance to shutdown gracefully. - wait(fun st/2, PeerT), + shutdown(conn, Reason, PeerT), %% Kill the watchdogs explicitly in case there was no transport. - wait(fun sw/2, PeerT). + shutdown(peer, Reason, PeerT). -wait(Fun, T) -> - diameter_lib:wait(ets:foldl(Fun, [], T)). +%% sp/2 -st(#peer{op_state = {OS,_}} = P, Acc) -> - st(P#peer{op_state = OS}, Acc); -st(#peer{op_state = ?STATE_UP, conn = Pid}, Acc) -> - Pid ! shutdown, - [Pid | Acc]; -st(#peer{}, Acc) -> - Acc. +sp(#peer{ref = Ref, pid = Pid}, Refs) -> + lists:member(Ref, Refs) + andalso (Pid ! {shutdown, self()}). %% 'DOWN' cleans up + +%% shutdown/3 + +shutdown(Who, Reason, T) -> + diameter_lib:wait(ets:foldl(fun(X,A) -> shutdown(Who, X, Reason, A) end, + [], + T)). -sw(#peer{pid = Pid}, Acc) +shutdown(conn = Who, #peer{op_state = {OS,_}} = P, Reason, Acc) -> + shutdown(Who, P#peer{op_state = OS}, Reason, Acc); + +shutdown(conn, + #peer{pid = Pid, op_state = ?STATE_UP, conn = TPid}, + Reason, + Acc) -> + TPid ! {shutdown, Pid, Reason}, + [TPid | Acc]; + +shutdown(peer, #peer{pid = Pid}, _Reason, Acc) when is_pid(Pid) -> exit(Pid, shutdown), [Pid | Acc]; -sw(#peer{}, Acc) -> + +shutdown(_, #peer{}, _, Acc) -> Acc. %%% --------------------------------------------------------------------------- -- cgit v1.2.3 From 3ce7a87d6a7e096fa1850dbaa0942a919a81955b Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Tue, 13 Nov 2012 10:30:02 +0100 Subject: Remove dead clause Long dead. --- lib/diameter/src/base/diameter_peer_fsm.erl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index ecdb09d34a..4acfd8313b 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -399,12 +399,6 @@ transition({shutdown, Pid, Reason}, #state{parent = Pid, dpr = false} = S) -> transition({shutdown, Pid, _}, #state{parent = Pid}) -> ok; -%% Request to close the transport connection. -transition({close = T, Pid}, #state{parent = Pid, - transport = TPid}) -> - diameter_peer:close(TPid), - {stop, T}; - %% DPA reception has timed out. transition(dpa_timeout, _) -> stop; -- cgit v1.2.3 From 5903d6db03084f4279bb4834f8542f726c9d3cdb Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 8 Nov 2012 12:54:20 +0100 Subject: Ensure watchdog dies with transport if DPR was sent A watchdog timeout after DPR but before DPA would previously result in the watchdog restarting the transport. --- lib/diameter/src/base/diameter_watchdog.erl | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index d814f1afe2..d6a2d2833b 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -48,18 +48,19 @@ -record(watchdog, {%% PCB - Peer Control Block; see RFC 3539, Appendix A status = initial :: initial | okay | suspect | down | reopen, - pending = false :: boolean(), + pending = false :: boolean(), %% DWA tw :: 6000..16#FFFFFFFF | {module(), atom(), list()}, %% {M,F,A} -> integer() >= 0 num_dwa = 0 :: -1 | non_neg_integer(), %% number of DWAs received during reopen %% end PCB - parent = self() :: pid(), - transport :: pid() | undefined, + parent = self() :: pid(), %% service process + transport :: pid() | undefined, %% peer_fsm process tref :: reference(), %% reference for current watchdog timer message_data, %% term passed into diameter_service with message sequence :: diameter:sequence(), %% mask - restrict :: {diameter:restriction(), boolean()}}). + restrict :: {diameter:restriction(), boolean()}, + shutdown = false :: boolean()}). %% start/2 %% @@ -168,7 +169,8 @@ handle_info(T, S) -> handle_info(T, upgrade(S)). upgrade(S) -> - #watchdog{} = list_to_tuple(tuple_to_list(S) ++ [?NOMASK, {nodes, true}]). + #watchdog{} = list_to_tuple(tuple_to_list(S) + ++ [?NOMASK, {nodes, true}, false]). event(#watchdog{status = T}, #watchdog{status = T}) -> ok; @@ -225,9 +227,10 @@ transition({shutdown, Pid}, #watchdog{parent = Pid, down = S, %% sanity check stop; transition({shutdown = T, Pid}, #watchdog{parent = Pid, - transport = TPid}) -> + transport = TPid} + = S) -> TPid ! {T, self()}, - ok; + S#watchdog{shutdown = true}; %% Parent process has died, transition({'DOWN', _, process, Pid, _Reason}, @@ -301,7 +304,10 @@ transition({open = P, TPid, _Hosts, T}, transition({'DOWN', _, process, TPid, _}, #watchdog{transport = TPid, - status = initial}) -> + status = S, + shutdown = D}) + when S == initial; + D -> stop; transition({'DOWN', _, process, TPid, _}, -- cgit v1.2.3 From 8b1bba0c46e6afdcd820c09ba8e432e447088854 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Tue, 6 Nov 2012 16:14:00 +0100 Subject: Fix broken doc link Broken in commit e28ced7b. --- lib/diameter/doc/src/diameter_app.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/diameter/doc/src/diameter_app.xml b/lib/diameter/doc/src/diameter_app.xml index 9d8a6568eb..ac056c2d39 100644 --- a/lib/diameter/doc/src/diameter_app.xml +++ b/lib/diameter/doc/src/diameter_app.xml @@ -309,12 +309,12 @@ by either handle_answer/4 or handle_error/4 depending on whether or not an answer message is received from the peer. If the transport becomes unavailable after prepare_request/3 then a new prepare_request/3 then a new pick_peer/4 callback may take place to failover to an alternate peer, after which prepare_retransmit/3 takes the place of prepare_request/3 in resending the +marker="#prepare_request">prepare_request/3 in resending the request. There is no guarantee that a pick_peer/4 callback to select -- cgit v1.2.3 From d192dc3d1e0726ab23ebeaa39cca965535952032 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 8 Nov 2012 16:28:47 +0100 Subject: Correct diameter:remove_transport/2 doc Error can be returned if the service process goes down while remove_transport is ongoing. --- lib/diameter/doc/src/diameter.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index e1442a108e..b40161045d 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -1254,7 +1254,7 @@ at the time the diameter application was started.

-remove_transport(SvcName, Pred) -> ok +remove_transport(SvcName, Pred) -> ok | {error, Reason} Remove previously added transports. SvcName = service_name() @@ -1264,6 +1264,7 @@ at the time the diameter application was started.

    | fun((transport_ref(), list()) -> boolean())     | fun((list()) -> boolean()) MFA = {atom(), atom(), list()} +Reason = term()

-- cgit v1.2.3 From d78891e4e1566923164185894beee5e25151399f Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Wed, 14 Nov 2012 20:06:42 +0100 Subject: Add simple DPR suite --- lib/diameter/test/diameter_dpr_SUITE.erl | 196 +++++++++++++++++++++++++++++++ lib/diameter/test/modules.mk | 5 +- 2 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 lib/diameter/test/diameter_dpr_SUITE.erl diff --git a/lib/diameter/test/diameter_dpr_SUITE.erl b/lib/diameter/test/diameter_dpr_SUITE.erl new file mode 100644 index 0000000000..9252650bf7 --- /dev/null +++ b/lib/diameter/test/diameter_dpr_SUITE.erl @@ -0,0 +1,196 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Tests of the disconnect_cb configuration. +%% + +-module(diameter_dpr_SUITE). + +-export([suite/0, + all/0, + groups/0, + init_per_group/2, + end_per_group/2]). + +%% testcases +-export([start/1, + connect/1, + remove_transport/1, + stop_service/1, + check/1, + stop/1]). + +%% disconnect_cb +-export([disconnect/5]). + +-include("diameter.hrl"). + +%% =========================================================================== + +-define(util, diameter_util). + +-define(ADDR, {127,0,0,1}). + +-define(CLIENT, "CLIENT"). +-define(SERVER, "SERVER"). + +-define(DICT_COMMON, ?DIAMETER_DICT_COMMON). +-define(APP_ID, ?DICT_COMMON:id()). + +%% Config for diameter:start_service/2. +-define(SERVICE(Host), + [{'Origin-Host', Host}, + {'Origin-Realm', "erlang.org"}, + {'Host-IP-Address', [?ADDR]}, + {'Vendor-Id', hd(Host)}, %% match this in disconnect/5 + {'Product-Name', "OTP/diameter"}, + {'Acct-Application-Id', [?APP_ID]}, + {restrict_connections, false}, + {application, [{dictionary, ?DICT_COMMON}, + {module, #diameter_callback{_ = false}}]}]). + +%% Disconnect reasons that diameter passes as the first argument of a +%% function configured as disconnect_cb. +-define(REASONS, [transport, service, application]). + +%% Valid values for Disconnect-Cause. +-define(CAUSES, [0, rebooting, 1, busy, 2, goaway]). + +%% Establish one client connection for element of this list, +%% configured with disconnect/5 as disconnect_cb and returning the +%% specified value. +-define(RETURNS, + [[close, {dpr, [{cause, invalid}]}], [ignore, close], []] + ++ [[{dpr, [{timeout, 5000}, {cause, T}]}] || T <- ?CAUSES]). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 60}}]. + +all() -> + [{group, R} || R <- ?REASONS]. + +%% The group determines how transports are terminated: by remove_transport, +%% stop_service or application stop. +groups() -> + Ts = tc(), + [{R, [], Ts} || R <- ?REASONS]. + +init_per_group(Name, Config) -> + [{group, Name} | Config]. + +end_per_group(_, _) -> + ok. + +tc() -> + [start, connect, remove_transport, stop_service, check, stop]. + +%% =========================================================================== +%% start/stop testcases + +start(_Config) -> + ok = diameter:start(), + ok = diameter:start_service(?SERVER, ?SERVICE(?SERVER)), + ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT)). + +connect(Config) -> + Pid = spawn(fun init/0), %% process for disconnect_cb to bang + Grp = group(Config), + LRef = ?util:listen(?SERVER, tcp), + Refs = [?util:connect(?CLIENT, tcp, LRef, opts(RCs, {Grp, Pid})) + || RCs <- ?RETURNS], + ?util:write_priv(Config, config, [Pid | Refs]). + +%% Remove all the client transports only in the transport group. +remove_transport(Config) -> + transport == group(Config) + andalso (ok = diameter:remove_transport(?CLIENT, true)). + +%% Stop the service only in the service group. +stop_service(Config) -> + service == group(Config) + andalso (ok = diameter:stop_service(?CLIENT)). + +%% Check for callbacks and stop the service. (Not the other way around +%% for the timing reason explained below.) +check(Config) -> + Grp = group(Config), + [Pid | Refs] = ?util:read_priv(Config, config), + Pid ! self(), %% ask for dictionary + Dict = receive {Pid, D} -> D end, %% get it + check(Refs, ?RETURNS, Grp, Dict). %% check for callbacks + +stop(_Config) -> + ok = diameter:stop(). + +%% Whether or not there are callbacks after diameter:stop() depends on +%% timing as long as the server runs on the same node: a server +%% transport could close the connection before the client has chance +%% to apply its callback. Therefore, just check that there haven't +%% been any callbacks yet. +check(_, _, application, Dict) -> + [] = dict:to_list(Dict); + +check([], [], _, _) -> + ok; + +check([Ref | Refs], CBs, Grp, Dict) -> + check1(Ref, hd(CBs), Grp, Dict), + check(Refs, tl(CBs), Grp, Dict). + +check1(Ref, [ignore | RCs], Reason, Dict) -> + check1(Ref, RCs, Reason, Dict); + +check1(Ref, [_|_], Reason, Dict) -> + {ok, Reason} = dict:find(Ref, Dict); %% callback with expected reason + +check1(Ref, [], _, Dict) -> + error = dict:find(Ref, Dict). %% no callback + +%% ---------------------------------------- + +group(Config) -> + {group, Grp} = lists:keyfind(group, 1, Config), + Grp. + +%% Configure the callback with the group name (= disconnect reason) as +%% extra argument. +opts(RCs, T) -> + [{disconnect_cb, {?MODULE, disconnect, [T, RC]}} || RC <- RCs]. + +%% Match the group name with the disconnect reason to ensure the +%% callback is being called as expected. +disconnect(Reason, Ref, Peer, {Reason, Pid}, RC) -> + io:format("disconnect: ~p ~p~n", [Ref, Reason]), + {_, #diameter_caps{vendor_id = {$C,$S}}} = Peer, + Pid ! {Reason, Ref}, + RC. + +init() -> + exit(recv(dict:new())). + +recv(Dict) -> + receive + Pid when is_pid(Pid) -> + Pid ! {self(), Dict}; + {Reason, Ref} -> + recv(dict:store(Ref, Reason, Dict)) + end. diff --git a/lib/diameter/test/modules.mk b/lib/diameter/test/modules.mk index 7f163536fb..5898e125ae 100644 --- a/lib/diameter/test/modules.mk +++ b/lib/diameter/test/modules.mk @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2010-2011. All Rights Reserved. +# Copyright Ericsson AB 2010-2012. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -39,7 +39,8 @@ MODULES = \ diameter_traffic_SUITE \ diameter_relay_SUITE \ diameter_tls_SUITE \ - diameter_failover_SUITE + diameter_failover_SUITE \ + diameter_dpr_SUITE HRL_FILES = \ diameter_ct.hrl -- cgit v1.2.3