1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2010-2016. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21%% 22%% Tests of traffic between seven Diameter nodes in four realms, 23%% connected as follows. 24%% 25%% ----- SERVER1.REALM2 ----- 26%% / \ 27%% / ----- SERVER2.REALM2 ----- \ 28%% | / \ | 29%% CLIENT.REALM1 ------ SERVER3.REALM2 ------ CLIENT.REALM4 30%% | \ / | 31%% | \ / | 32%% \ ---- SERVER1.REALM3 ----- / 33%% \ / 34%% ----- SERVER2.REALM3 ----- 35%% 36 37-module(diameter_failover_SUITE). 38 39-export([suite/0, 40 all/0]). 41 42%% testcases 43-export([start/1, 44 start_services/1, 45 connect/1, 46 send_ok/1, 47 send_nok/1, 48 send_discard_1/1, 49 send_discard_2/1, 50 stop_services/1, 51 empty/1, 52 stop/1]). 53 54%% diameter callbacks 55-export([pick_peer/4, 56 prepare_request/3, 57 prepare_retransmit/3, 58 handle_error/4, 59 handle_answer/4, 60 handle_request/3]). 61 62-include("diameter.hrl"). 63-include("diameter_gen_base_rfc3588.hrl"). 64 65%% =========================================================================== 66 67-define(util, diameter_util). 68 69-define(ADDR, {127,0,0,1}). 70 71-define(CLIENT1, "CLIENT.REALM1"). 72-define(CLIENT2, "CLIENT.REALM4"). 73-define(SERVER1, "SERVER1.REALM2"). 74-define(SERVER2, "SERVER2.REALM2"). 75-define(SERVER3, "SERVER3.REALM2"). 76-define(SERVER4, "SERVER1.REALM3"). 77-define(SERVER5, "SERVER2.REALM3"). 78 79-define(IS_CLIENT(Svc), Svc == ?CLIENT1; Svc == ?CLIENT2). 80 81-define(CLIENTS, [?CLIENT1, ?CLIENT2]). 82-define(SERVERS, [?SERVER1, ?SERVER2, ?SERVER3, ?SERVER4, ?SERVER5]). 83 84-define(DICT_COMMON, ?DIAMETER_DICT_COMMON). 85 86-define(APP_ALIAS, the_app). 87-define(APP_ID, ?DICT_COMMON:id()). 88 89%% Config for diameter:start_service/2. 90-define(SERVICE(Host), 91 [{'Origin-Host', Host}, 92 {'Origin-Realm', realm(Host)}, 93 {'Host-IP-Address', [?ADDR]}, 94 {'Vendor-Id', 12345}, 95 {'Product-Name', "OTP/diameter"}, 96 {'Acct-Application-Id', [?APP_ID]}, 97 {application, [{alias, ?APP_ALIAS}, 98 {dictionary, ?DICT_COMMON}, 99 {module, #diameter_callback 100 {peer_up = false, 101 peer_down = false, 102 default = ?MODULE}}, 103 {answer_errors, callback}]}]). 104 105-define(SUCCESS, 2001). 106 107%% Value of Termination-Cause determines client/server behaviour. 108-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). 109-define(MOVED, ?'DIAMETER_BASE_TERMINATION-CAUSE_USER_MOVED'). 110-define(TIMEOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_SESSION_TIMEOUT'). 111 112%% =========================================================================== 113 114suite() -> 115 [{timetrap, {seconds, 60}}]. 116 117all() -> 118 [start, 119 start_services, 120 connect, 121 send_ok, 122 send_nok, 123 send_discard_1, 124 send_discard_2, 125 stop_services, 126 empty, 127 stop]. 128 129%% =========================================================================== 130%% start/stop testcases 131 132start(_Config) -> 133 ok = diameter:start(). 134 135start_services(_Config) -> 136 Servers = [server(N) || N <- ?SERVERS], 137 [] = [T || C <- ?CLIENTS, 138 T <- [diameter:start_service(C, ?SERVICE(C))], 139 T /= ok], 140 141 {save_config, Servers}. 142 143connect(Config) -> 144 {start_services, Servers} = proplists:get_value(saved_config, Config), 145 146 lists:foreach(fun(C) -> connect(C, Servers) end, ?CLIENTS). 147 148stop_services(_Config) -> 149 [] = [{H,T} || H <- ?CLIENTS ++ ?SERVERS, 150 T <- [diameter:stop_service(H)], 151 T /= ok]. 152 153%% Ensure transports have been removed from request table. 154empty(_Config) -> 155 [] = ets:tab2list(diameter_request). 156 157stop(_Config) -> 158 ok = diameter:stop(). 159 160%% ---------------------------------------- 161 162server(Name) -> 163 ok = diameter:start_service(Name, ?SERVICE(Name)), 164 {Name, ?util:listen(Name, tcp)}. 165 166connect(Name, Refs) -> 167 [{{Name, ?util:connect(Name, tcp, LRef)}, T} || {_, LRef} = T <- Refs]. 168 169%% =========================================================================== 170%% traffic testcases 171 172%% Send an STR and expect success after SERVER3 answers after a couple 173%% of failovers. 174send_ok(_Config) -> 175 Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER1), 176 'Termination-Cause' = ?LOGOUT, 177 'Auth-Application-Id' = ?APP_ID}, 178 #diameter_base_STA{'Result-Code' = ?SUCCESS, 179 'Origin-Host' = ?SERVER3} 180 = call(?CLIENT1, Req). 181 182%% Send an STR and expect failure when both servers fail. 183send_nok(_Config) -> 184 Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER4), 185 'Termination-Cause' = ?LOGOUT, 186 'Auth-Application-Id' = ?APP_ID}, 187 {failover, ?LOGOUT} = call(?CLIENT1, Req). 188 189%% Send an STR and have prepare_retransmit discard it. 190send_discard_1(_Config) -> 191 Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER1), 192 'Termination-Cause' = ?TIMEOUT, 193 'Auth-Application-Id' = ?APP_ID}, 194 {rejected, ?TIMEOUT} = call(?CLIENT2, Req). 195send_discard_2(_Config) -> 196 Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER4), 197 'Termination-Cause' = ?MOVED, 198 'Auth-Application-Id' = ?APP_ID}, 199 {discarded, ?MOVED} = call(?CLIENT2, Req). 200 201%% =========================================================================== 202 203realm(Host) -> 204 tl(lists:dropwhile(fun(C) -> C /= $. end, Host)). 205 206call(Svc, Req) -> 207 diameter:call(Svc, ?APP_ALIAS, Req, [{filter, realm}]). 208 209%% =========================================================================== 210%% diameter callbacks 211 212%% pick_peer/4 213 214%% Choose a server other than SERVER3 or SERVER5 if possible. 215pick_peer(Peers, _, Svc, _State) 216 when ?IS_CLIENT(Svc) -> 217 case lists:partition(fun({_, #diameter_caps{origin_host = {_, OH}}}) -> 218 OH /= ?SERVER3 andalso OH /= ?SERVER5 219 end, 220 Peers) 221 of 222 {[], [Peer]} -> 223 {ok, Peer}; 224 {[Peer | _], _} -> 225 {ok, Peer} 226 end. 227 228%% prepare_request/3 229 230prepare_request(Pkt, Svc, {_Ref, Caps}) 231 when ?IS_CLIENT(Svc) -> 232 {send, prepare(Pkt, Caps)}. 233 234prepare(#diameter_packet{msg = Req}, Caps) -> 235 #diameter_caps{origin_host = {OH, _}, 236 origin_realm = {OR, _}} 237 = Caps, 238 Req#diameter_base_STR{'Origin-Host' = OH, 239 'Origin-Realm' = OR, 240 'Session-Id' = diameter:session_id(OH)}. 241 242%% prepare_retransmit/3 243 244prepare_retransmit(#diameter_packet{header = H} = P, Svc, {_,_}) 245 when ?IS_CLIENT(Svc) -> 246 #diameter_header{is_retransmitted = true} = H, %% assert 247 prepare(P). 248 249prepare(#diameter_packet{msg = M} = P) -> 250 case M#diameter_base_STR.'Termination-Cause' of 251 ?LOGOUT -> {send, P}; 252 ?MOVED -> discard; 253 ?TIMEOUT -> {discard, rejected} 254 end. 255 256%% handle_error/4 257 258handle_error(Reason, Req, _, _) -> 259 {Reason, Req#diameter_base_STR.'Termination-Cause'}. 260 261%% handle_answer/4 262 263handle_answer(Pkt, _Req, Svc, _Peer) 264 when ?IS_CLIENT(Svc) -> 265 #diameter_packet{msg = Rec, errors = []} = Pkt, 266 Rec. 267 268%% handle_request/3 269 270%% Only SERVER3 actually answers. 271handle_request(Pkt, ?SERVER3, {_, Caps}) -> 272 #diameter_packet{header = #diameter_header{is_retransmitted = true}, 273 msg = #diameter_base_STR{'Session-Id' = SId}} 274 = Pkt, 275 #diameter_caps{origin_host = {OH, _}, 276 origin_realm = {OR, _}} 277 = Caps, 278 279 {reply, #diameter_base_STA{'Result-Code' = ?SUCCESS, 280 'Session-Id' = SId, 281 'Origin-Host' = OH, 282 'Origin-Realm' = OR}}; 283 284%% Others kill the transport to force failover. 285handle_request(_, _, {TPid, _}) -> 286 exit(TPid, kill), 287 discard. 288