%% %% %CopyrightBegin% %% %% 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 %% 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 traffic between six Diameter nodes in three realms, %% connected as follows. %% %% ----- SERVER1.REALM2 %% / %% / ----- SERVER2.REALM2 %% | / %% CLIENT.REALM1 ------ SERVER3.REALM2 %% | \ %% | \ %% \ ---- SERVER1.REALM3 %% \ %% ----- SERVER2.REALM3 %% -module(diameter_failover_SUITE). -export([suite/0, all/0]). %% testcases -export([start/1, start_services/1, connect/1, send_ok/1, send_nok/1, stop_services/1, stop/1]). %% diameter callbacks -export([pick_peer/4, prepare_request/3, handle_answer/4, handle_request/3]). -include("diameter.hrl"). -include("diameter_gen_base_rfc3588.hrl"). %% =========================================================================== -define(util, diameter_util). -define(ADDR, {127,0,0,1}). -define(CLIENT, "CLIENT.REALM1"). -define(SERVER1, "SERVER1.REALM2"). -define(SERVER2, "SERVER2.REALM2"). -define(SERVER3, "SERVER3.REALM2"). -define(SERVER4, "SERVER1.REALM3"). -define(SERVER5, "SERVER2.REALM3"). -define(SERVICES, [?CLIENT, ?SERVER1, ?SERVER2, ?SERVER3, ?SERVER4, ?SERVER5]). -define(DICT_COMMON, ?DIAMETER_DICT_COMMON). -define(APP_ALIAS, the_app). -define(APP_ID, ?DICT_COMMON:id()). %% Config for diameter:start_service/2. -define(SERVICE(Host, Dict), [{'Origin-Host', Host}, {'Origin-Realm', realm(Host)}, {'Host-IP-Address', [?ADDR]}, {'Vendor-Id', 12345}, {'Product-Name', "OTP/diameter"}, {'Acct-Application-Id', [Dict:id()]}, {application, [{alias, ?APP_ALIAS}, {dictionary, Dict}, {module, #diameter_callback {peer_up = false, peer_down = false, handle_error = false, prepare_retransmit = false, default = ?MODULE}}, {answer_errors, callback}]}]). -define(SUCCESS, 2001). -define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). %% =========================================================================== suite() -> [{timetrap, {seconds, 60}}]. all() -> [start, start_services, connect, send_ok, send_nok, stop_services, stop]. %% =========================================================================== %% start/stop testcases start(_Config) -> ok = diameter:start(). start_services(_Config) -> S = [server(N, ?DICT_COMMON) || N <- tl(?SERVICES)], ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT, ?DICT_COMMON)), {save_config, [{?CLIENT, S}]}. connect(Config) -> {_, Conns} = proplists:get_value(saved_config, Config), lists:foreach(fun({CN,Ss}) -> connect(CN, Ss) end, Conns). stop_services(_Config) -> [] = [{H,T} || H <- ?SERVICES, T <- [diameter:stop_service(H)], T /= ok]. stop(_Config) -> ok = diameter:stop(). %% ---------------------------------------- server(Name, Dict) -> ok = diameter:start_service(Name, ?SERVICE(Name, Dict)), {Name, ?util:listen(Name, tcp)}. connect(Name, Refs) -> [{{Name, ?util:connect(Name, tcp, LRef)}, T} || {_, LRef} = T <- Refs]. %% =========================================================================== %% traffic testcases %% Send an STR and expect success after SERVER3 answers after a couple %% of failovers. send_ok(_Config) -> Req = ['STR', {'Destination-Realm', realm(?SERVER1)}, {'Termination-Cause', ?LOGOUT}, {'Auth-Application-Id', ?APP_ID}], #diameter_base_STA{'Result-Code' = ?SUCCESS, 'Origin-Host' = ?SERVER3} = call(Req, [{filter, realm}]). %% Send an STR and expect failure when both servers fail. send_nok(_Config) -> Req = ['STR', {'Destination-Realm', realm(?SERVER4)}, {'Termination-Cause', ?LOGOUT}, {'Auth-Application-Id', ?APP_ID}], {error, failover} = call(Req, [{filter, realm}]). %% =========================================================================== realm(Host) -> tl(lists:dropwhile(fun(C) -> C /= $. end, Host)). call(Req, Opts) -> diameter:call(?CLIENT, ?APP_ALIAS, Req, Opts). set([H|T], Vs) -> [H | Vs ++ T]. %% =========================================================================== %% diameter callbacks %% pick_peer/4 %% Choose a server other than SERVER3 or SERVER5 if possible. pick_peer(Peers, _, ?CLIENT, _State) -> case lists:partition(fun({_, #diameter_caps{origin_host = {_, OH}}}) -> OH /= ?SERVER3 andalso OH /= ?SERVER5 end, Peers) of {[], [Peer]} -> {ok, Peer}; {[Peer | _], _} -> {ok, Peer} end. %% prepare_request/3 prepare_request(Pkt, ?CLIENT, {_Ref, Caps}) -> {send, prepare(Pkt, Caps)}. prepare(#diameter_packet{msg = Req}, Caps) -> #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}} = Caps, set(Req, [{'Session-Id', diameter:session_id(OH)}, {'Origin-Host', OH}, {'Origin-Realm', OR}]). %% handle_answer/4 handle_answer(Pkt, _Req, ?CLIENT, _Peer) -> #diameter_packet{msg = Rec, errors = []} = Pkt, Rec. %% handle_request/3 %% Only SERVER3 actually answers. handle_request(Pkt, ?SERVER3, {_, Caps}) -> #diameter_packet{msg = #diameter_base_STR{'Session-Id' = SId, 'Origin-Host' = ?CLIENT}} = Pkt, #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}} = Caps, {reply, #diameter_base_STA{'Result-Code' = ?SUCCESS, 'Session-Id' = SId, 'Origin-Host' = OH, 'Origin-Realm' = OR}}; %% Others kill the transport to force failover. handle_request(_, _, {TPid, _}) -> exit(TPid, kill), discard.