1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2013-2019. 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 two Diameter nodes, the client being 23%% spread across three Erlang nodes. 24%% 25 26-module(diameter_distribution_SUITE). 27 28-export([suite/0, 29 all/0]). 30 31%% testcases 32-export([enslave/1, enslave/0, 33 ping/1, 34 start/1, 35 connect/1, 36 send_local/1, 37 send_remote/1, 38 send_timeout/1, 39 send_failover/1, 40 stop/1, stop/0]). 41 42%% diameter callbacks 43-export([peer_up/3, 44 peer_down/3, 45 pick_peer/5, 46 prepare_request/4, 47 prepare_retransmit/4, 48 handle_answer/5, 49 handle_error/5, 50 handle_request/3]). 51 52-export([call/1]). 53 54-include("diameter.hrl"). 55-include("diameter_gen_base_rfc6733.hrl"). 56 57%% =========================================================================== 58 59-define(util, diameter_util). 60 61-define(CLIENT, 'CLIENT'). 62-define(SERVER, 'SERVER'). 63-define(REALM, "erlang.org"). 64-define(DICT, diameter_gen_base_rfc6733). 65-define(ADDR, {127,0,0,1}). 66 67%% Config for diameter:start_service/2. 68-define(SERVICE(Host), 69 [{'Origin-Host', Host ++ [$.|?REALM]}, 70 {'Origin-Realm', ?REALM}, 71 {'Host-IP-Address', [?ADDR]}, 72 {'Vendor-Id', 12345}, 73 {'Product-Name', "OTP/diameter"}, 74 {'Auth-Application-Id', [?DICT:id()]}, 75 {'Origin-State-Id', origin()}, 76 {share_peers, peers()}, 77 {use_shared_peers, peers()}, 78 {restrict_connections, false}, 79 {spawn_opt, {diameter_dist, spawn_local, []}}, 80 {sequence, fun sequence/0}, 81 {application, [{dictionary, ?DICT}, 82 {module, ?MODULE}, 83 {request_errors, callback}, 84 {answer_errors, callback}]}]). 85 86-define(SUCCESS, 2001). 87-define(BUSY, 3004). 88-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). 89-define(MOVED, ?'DIAMETER_BASE_TERMINATION-CAUSE_USER_MOVED'). 90-define(TIMEOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_SESSION_TIMEOUT'). 91 92-define(L, atom_to_list). 93-define(A, list_to_atom). 94 95%% The order here is significant and causes the server to listen 96%% before the clients connect. 97-define(NODES, [{server, ?SERVER}, 98 {client0, ?CLIENT}, 99 {client1, ?CLIENT}, 100 {client2, ?CLIENT}]). 101 102%% Options to ct_slave:start/2. 103-define(TIMEOUTS, [{T, 15000} || T <- [boot_timeout, 104 init_timeout, 105 start_timeout]]). 106 107%% =========================================================================== 108 109suite() -> 110 [{timetrap, {seconds, 60}}]. 111 112all() -> 113 [enslave, 114 ping, 115 start, 116 connect, 117 send_local, 118 send_remote, 119 send_timeout, 120 send_failover, 121 stop]. 122 123%% =========================================================================== 124%% start/stop testcases 125 126%% enslave/1 127%% 128%% Start four slave nodes, one to implement a Diameter server, 129%% three to implement a client. 130 131enslave() -> 132 [{timetrap, {seconds, 30*length(?NODES)}}]. 133 134enslave(Config) -> 135 Here = filename:dirname(code:which(?MODULE)), 136 Ebin = filename:join([Here, "..", "ebin"]), 137 Dirs = [Here, Ebin], 138 Nodes = [{N,S} || {M,S} <- ?NODES, N <- [slave(M, Dirs)]], 139 ?util:write_priv(Config, nodes, [{N,S} || {{N,ok},S} <- Nodes]), 140 [] = [{T,S} || {{_,E} = T, S} <- Nodes, E /= ok]. 141 142slave(Name, Dirs) -> 143 add_pathsa(Dirs, ct_slave:start(Name, ?TIMEOUTS)). 144 145add_pathsa(Dirs, {ok, Node}) -> 146 {Node, rpc:call(Node, code, add_pathsa, [Dirs])}; 147add_pathsa(_, No) -> 148 {No, error}. 149 150%% ping/1 151%% 152%% Ensure the client nodes are connected since the sharing of 153%% transports is only between connected nodes. 154 155ping({?SERVER, _Nodes}) -> 156 []; 157 158ping({?CLIENT, Nodes}) -> 159 [N || {N,_} <- Nodes, 160 node() /= N, 161 pang <- [net_adm:ping(N)]]; 162 163ping(Config) -> 164 Nodes = ?util:read_priv(Config, nodes), 165 [] = [{N,RC} || {N,S} <- Nodes, 166 RC <- [rpc:call(N, ?MODULE, ping, [{S, Nodes}])], 167 RC /= []]. 168 169%% start/1 170%% 171%% Start diameter services. 172 173start(SvcName) 174 when is_atom(SvcName) -> 175 ok = diameter:start(), 176 ok = diameter:start_service(SvcName, ?SERVICE((?L(SvcName)))); 177 178start(Config) -> 179 Nodes = ?util:read_priv(Config, nodes), 180 [] = [{N,RC} || {N,S} <- Nodes, 181 RC <- [rpc:call(N, ?MODULE, start, [S])], 182 RC /= ok]. 183 184sequence() -> 185 sequence(sname()). 186 187sequence(server) -> 188 {0,32}; 189sequence(Client) -> 190 "client" ++ N = ?L(Client), 191 {list_to_integer(N), 30}. 192 193origin() -> 194 origin(sname()). 195 196origin(server) -> 197 99; 198origin(Client) -> 199 "client" ++ N = ?L(Client), 200 list_to_integer(N). 201 202peers() -> 203 peers(sname()). 204 205peers(server) -> true; 206peers(client0) -> [node() | nodes()]; 207peers(client1) -> fun erlang:nodes/0; 208peers(client2) -> nodes(). 209 210%% connect/1 211%% 212%% Establish one connection to the server from each of the client 213%% nodes. 214 215connect({?SERVER, Config}) -> 216 ?util:write_priv(Config, lref, {node(), ?util:listen(?SERVER, tcp)}), 217 ok; 218 219connect({?CLIENT, Config}) -> 220 ?util:connect(?CLIENT, tcp, ?util:read_priv(Config, lref)), 221 ok; 222 223connect(Config) -> 224 Nodes = ?util:read_priv(Config, nodes), 225 [] = [{N,RC} || {N,S} <- Nodes, 226 RC <- [rpc:call(N, ?MODULE, connect, [{S,Config}])], 227 RC /= ok]. 228 229%% stop/1 230%% 231%% Stop the slave nodes. 232 233stop() -> 234 [{timetrap, {seconds, 30*length(?NODES)}}]. 235 236stop(_Config) -> 237 [] = [{N,E} || {N,_} <- ?NODES, 238 {error, _, _} = E <- [ct_slave:stop(N)]]. 239 240%% =========================================================================== 241%% traffic testcases 242 243%% send_local/1 244%% 245%% Send a request from the first client node, using a the local 246%% transport. 247 248send_local(Config) -> 249 #diameter_base_STA{'Result-Code' = ?SUCCESS} 250 = send(Config, local, str(?LOGOUT)). 251 252%% send_remote/1 253%% 254%% Send a request from the first client node, using a transport on the 255%% another node. 256 257send_remote(Config) -> 258 #diameter_base_STA{'Result-Code' = ?SUCCESS} 259 = send(Config, remote, str(?LOGOUT)). 260 261%% send_timeout/1 262%% 263%% Send a request that the server discards. 264 265send_timeout(Config) -> 266 {error, timeout} = send(Config, remote, str(?TIMEOUT)). 267 268%% send_failover/1 269%% 270%% Send a request that causes the server to remote transports down. 271 272send_failover(Config) -> 273 #'diameter_base_answer-message'{'Result-Code' = ?BUSY} 274 = send(Config, remote, str(?MOVED)). 275 276%% =========================================================================== 277 278str(Cause) -> 279 #diameter_base_STR{'Destination-Realm' = ?REALM, 280 'Auth-Application-Id' = ?DICT:id(), 281 'Termination-Cause' = Cause}. 282 283%% send/2 284 285send(Config, Where, Req) -> 286 [_, {Node, _} | _] = ?util:read_priv(Config, nodes) , 287 rpc:call(Node, ?MODULE, call, [{Where, Req}]). 288 289%% call/1 290 291call({Where, Req}) -> 292 diameter:call(?CLIENT, ?DICT, Req, [{extra, [{Where, sname()}]}]). 293 294%% sname/0 295 296sname() -> 297 ?A(hd(string:tokens(?L(node()), "@"))). 298 299%% =========================================================================== 300%% diameter callbacks 301 302%% peer_up/3 303 304peer_up(_SvcName, _Peer, State) -> 305 State. 306 307%% peer_down/3 308 309peer_down(_SvcName, _Peer, State) -> 310 State. 311 312%% pick_peer/4 313 314pick_peer([LP], [_, _], ?CLIENT, _State, {local, client0}) -> 315 {ok, LP}; 316 317pick_peer([_], [RP | _], ?CLIENT, _State, {remote, client0}) -> 318 {ok, RP}; 319 320pick_peer([LP], [], ?CLIENT, _State, {remote, client0}) -> 321 {ok, LP}. 322 323%% prepare_request/4 324 325prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, {_, client0}) -> 326 #diameter_packet{msg = Req} 327 = Pkt, 328 #diameter_caps{origin_host = {OH, _}, 329 origin_realm = {OR, _}} 330 = Caps, 331 {send, Req#diameter_base_STR{'Origin-Host' = OH, 332 'Origin-Realm' = OR, 333 'Session-Id' = diameter:session_id(OH)}}. 334 335%% prepare_retransmit/4 336 337prepare_retransmit(Pkt, ?CLIENT, _, {_, client0}) -> 338 #diameter_packet{msg = #diameter_base_STR{'Termination-Cause' = ?MOVED}} 339 = Pkt, %% assert 340 {send, Pkt}. 341 342%% handle_answer/5 343 344handle_answer(Pkt, _Req, ?CLIENT, _Peer, {_, client0}) -> 345 #diameter_packet{msg = Rec, errors = []} = Pkt, 346 Rec. 347 348%% handle_error/5 349 350handle_error(Reason, _Req, ?CLIENT, _Peer, {_, client0}) -> 351 {error, Reason}. 352 353%% handle_request/3 354 355handle_request(Pkt, ?SERVER, Peer) -> 356 server = sname(), %% assert 357 #diameter_packet{msg = Req} 358 = Pkt, 359 request(Req, Peer). 360 361request(#diameter_base_STR{'Termination-Cause' = ?TIMEOUT}, _) -> 362 discard; 363 364request(#diameter_base_STR{'Termination-Cause' = ?MOVED}, Peer) -> 365 {TPid, #diameter_caps{origin_state_id = {_, [N]}}} = Peer, 366 fail(N, TPid); 367 368request(#diameter_base_STR{'Session-Id' = SId}, {_, Caps}) -> 369 #diameter_caps{origin_host = {OH, _}, 370 origin_realm = {OR, _}} 371 = Caps, 372 {reply, #diameter_base_STA{'Result-Code' = ?SUCCESS, 373 'Session-Id' = SId, 374 'Origin-Host' = OH, 375 'Origin-Realm' = OR}}. 376 377fail(0, _) -> %% sent from the originating node ... 378 {protocol_error, ?BUSY}; 379 380fail(_, TPid) -> %% ... or through a remote node: force failover 381 exit(TPid, kill), 382 discard. 383