1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2019-2020. 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 server being 23%% spread across three Erlang nodes. 24%% 25 26-module(diameter_dist_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/1, 37 stop/1, stop/0]). 38 39%% diameter callbacks 40-export([peer_up/3, 41 peer_down/3, 42 pick_peer/4, 43 prepare_request/3, 44 prepare_retransmit/3, 45 handle_answer/4, 46 handle_error/4, 47 handle_request/3]). 48 49-export([call/1]). 50 51-include("diameter.hrl"). 52-include("diameter_gen_base_rfc6733.hrl"). 53 54%% =========================================================================== 55 56-define(util, diameter_util). 57 58-define(CLIENT, 'CLIENT'). 59-define(SERVER, 'SERVER'). 60-define(REALM, "erlang.org"). 61-define(DICT, diameter_gen_base_rfc6733). 62-define(ADDR, {127,0,0,1}). 63 64%% Config for diameter:start_service/2. 65-define(SERVICE(Host), 66 [{'Origin-Host', Host ++ [$.|?REALM]}, 67 {'Origin-Realm', ?REALM}, 68 {'Host-IP-Address', [?ADDR]}, 69 {'Vendor-Id', 12345}, 70 {'Product-Name', "OTP/diameter"}, 71 {'Auth-Application-Id', [?DICT:id()]}, 72 {'Origin-State-Id', origin()}, 73 {spawn_opt, {diameter_dist, route_session, [#{id => []}]}}, 74 {sequence, fun sequence/0}, 75 {string_decode, false}, 76 {application, [{dictionary, ?DICT}, 77 {module, ?MODULE}, 78 {request_errors, callback}, 79 {answer_errors, callback}]}]). 80 81-define(SUCCESS, 2001). 82-define(BUSY, 3004). 83-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). 84-define(MOVED, ?'DIAMETER_BASE_TERMINATION-CAUSE_USER_MOVED'). 85-define(TIMEOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_SESSION_TIMEOUT'). 86 87-define(L, atom_to_list). 88-define(A, list_to_atom). 89 90%% The order here is significant and causes the server to listen 91%% before the clients connect. The server listens on the first node, 92%% and distributes requests to the other two. 93-define(NODES, [{server0, ?SERVER}, 94 {server1, ?SERVER}, 95 {server2, ?SERVER}, 96 {client, ?CLIENT}]). 97 98%% Options to ct_slave:start/2. 99-define(TIMEOUTS, [{T, 15000} || T <- [boot_timeout, 100 init_timeout, 101 start_timeout]]). 102 103%% =========================================================================== 104 105suite() -> 106 [{timetrap, {seconds, 60}}]. 107 108all() -> 109 [enslave, 110 ping, 111 start, 112 connect, 113 send, 114 stop]. 115 116%% =========================================================================== 117%% start/stop testcases 118 119%% enslave/1 120%% 121%% Start four slave nodes, three to implement a Diameter server, 122%% one to implement a client. 123 124enslave() -> 125 [{timetrap, {seconds, 30*length(?NODES)}}]. 126 127enslave(Config) -> 128 Here = filename:dirname(code:which(?MODULE)), 129 Ebin = filename:join([Here, "..", "ebin"]), 130 Dirs = [Here, Ebin], 131 Nodes = [{N,S} || {M,S} <- ?NODES, N <- [slave(M, Dirs)]], 132 ?util:write_priv(Config, nodes, [{N,S} || {{N,ok},S} <- Nodes]), 133 [] = [{T,S} || {{_,E} = T, S} <- Nodes, E /= ok]. 134 135slave(Name, Dirs) -> 136 add_pathsa(Dirs, ct_slave:start(Name, ?TIMEOUTS)). 137 138add_pathsa(Dirs, {ok, Node}) -> 139 {Node, rpc:call(Node, code, add_pathsa, [Dirs])}; 140add_pathsa(_, No) -> 141 {No, error}. 142 143%% ping/1 144%% 145%% Ensure the server nodes are connected so that diameter_dist can attach. 146 147ping({S, Nodes}) -> 148 ?SERVER = S, 149 [N || {N,_} <- Nodes, 150 node() /= N, 151 pang <- [net_adm:ping(N)]]; 152 153ping(Config) -> 154 Nodes = lists:droplast(?util:read_priv(Config, nodes)), 155 [] = [{N,RC} || {N,S} <- Nodes, 156 RC <- [rpc:call(N, ?MODULE, ping, [{S,Nodes}])], 157 RC /= []]. 158 159%% start/1 160%% 161%% Start diameter services. 162 163%% There's no need to start diameter on a node that only services 164%% diameter_dist as a handler of incoming requests, but the 165%% diameter_dist server must be started since the servers communicate 166%% to determine who services what. The typical case is probably that 167%% handler nodes also want to be able to send Diameter requests, in 168%% which case the application needs to be started and diameter_dist is 169%% started as a part of this, but only start the server here to ensure 170%% everything still works as expected. 171start({_SvcName, [_, {S1, _}, {S2, _}, _]}) 172 when node() == S1; %% server1 173 node() == S2 -> %% server2 174 Mod = diameter_dist, 175 {ok, _} = gen_server:start({local, Mod}, Mod, _Args = [], _Opts = []), 176 ok; 177 178start({SvcName, [{S0, _}, _, _, {C, _}]}) 179 when node() == S0; %% server0 180 node() == C -> %% client 181 ok = diameter:start(), 182 ok = diameter:start_service(SvcName, ?SERVICE((?L(SvcName)))); 183 184start(Config) 185 when is_list(Config) -> 186 Nodes = ?util:read_priv(Config, nodes), 187 [] = [{N,RC} || {N,S} <- Nodes, 188 RC <- [rpc:call(N, ?MODULE, start, [{S, Nodes}])], 189 RC /= ok]. 190 191sequence() -> 192 sequence(sname()). 193 194sequence(client) -> 195 {0,32}; 196sequence(Server) -> 197 "server" ++ N = ?L(Server), 198 {list_to_integer(N), 30}. 199 200origin() -> 201 origin(sname()). 202 203origin(client) -> 204 99; 205origin(Server) -> 206 "server" ++ N = ?L(Server), 207 list_to_integer(N). 208 209%% connect/1 210%% 211%% Establish one connection from the client, terminated on the first 212%% server node, the others handling requests. 213 214connect({?SERVER, Config, [{Node, _} | _]}) 215 when Node == node() -> %% server0 216 ok = ?util:write_priv(Config, lref, {Node, ?util:listen(?SERVER, tcp)}); 217 218connect({?SERVER, _Config, _}) -> %% server[12]: register to receive requests 219 ok = diameter_dist:attach([?SERVER]); 220 221connect({?CLIENT, Config, _}) -> 222 ?util:connect(?CLIENT, tcp, ?util:read_priv(Config, lref)), 223 ok; 224 225connect(Config) -> 226 Nodes = ?util:read_priv(Config, nodes), 227 [] = [{N,RC} || {N,S} <- Nodes, 228 RC <- [rpc:call(N, ?MODULE, connect, [{S, Config, Nodes}])], 229 RC /= ok]. 230 231%% stop/1 232%% 233%% Stop the slave nodes. 234 235stop() -> 236 [{timetrap, {seconds, 30*length(?NODES)}}]. 237 238stop(_Config) -> 239 [] = [{N,E} || {N,_} <- ?NODES, 240 {error, _, _} = E <- [ct_slave:stop(N)]]. 241 242%% =========================================================================== 243%% traffic testcases 244 245%% send/1 246%% 247%% Send 100 requests and ensure the node name sent as User-Name isn't 248%% the node terminating transport. 249 250send(Config) -> 251 send(Config, 100, dict:new()). 252 253%% send/2 254 255send(Config, 0, Dict) -> 256 [{Server0, _} | _] = ?util:read_priv(Config, nodes) , 257 Node = atom_to_binary(Server0, utf8), 258 {false, _} = {dict:is_key(Node, Dict), dict:to_list(Dict)}, 259 %% Check that counters have been incremented as expected on server0. 260 [Info] = rpc:call(Server0, diameter, service_info, [?SERVER, connections]), 261 {[Stats], _} = {[S || {statistics, S} <- Info], Info}, 262 {[{recv, 1, 100}, {send, 0, 100}], _} 263 = {[{D,R,N} || T <- [recv, send], 264 {{{0,275,R}, D}, N} <- Stats, 265 D == T], 266 Stats}, 267 {[{send, 0, 100, 2001}], _} 268 = {[{D,R,N,C} || {{{0,275,R}, D, {'Result-Code', C}}, N} <- Stats], 269 Stats}; 270 271send(Config, N, Dict) -> 272 #diameter_base_STA{'Result-Code' = ?SUCCESS, 273 'User-Name' = [ServerNode]} 274 = send(Config, str(?LOGOUT)), 275 true = is_binary(ServerNode), 276 send(Config, N-1, dict:update_counter(ServerNode, 1, Dict)). 277 278%% =========================================================================== 279 280str(Cause) -> 281 #diameter_base_STR{'Destination-Realm' = ?REALM, 282 'Auth-Application-Id' = ?DICT:id(), 283 'Termination-Cause' = Cause}. 284 285%% send/2 286 287send(Config, Req) -> 288 {Node, _} = lists:last(?util:read_priv(Config, nodes)), 289 rpc:call(Node, ?MODULE, call, [Req]). 290 291%% call/1 292 293call(Req) -> 294 diameter:call(?CLIENT, ?DICT, Req, []). 295 296%% sname/0 297 298sname() -> 299 ?A(hd(string:tokens(?L(node()), "@"))). 300 301%% =========================================================================== 302%% diameter callbacks 303 304%% peer_up/3 305 306peer_up(_SvcName, _Peer, State) -> 307 State. 308 309%% peer_down/3 310 311peer_down(_SvcName, _Peer, State) -> 312 State. 313 314%% pick_peer/4 315 316pick_peer([Peer], [], ?CLIENT, _State) -> 317 {ok, Peer}. 318 319%% prepare_request/3 320 321prepare_request(Pkt, ?CLIENT, {_Ref, Caps}) -> 322 #diameter_packet{msg = Req} 323 = Pkt, 324 #diameter_caps{origin_host = {OH, _}, 325 origin_realm = {OR, _}} 326 = Caps, 327 {send, Req#diameter_base_STR{'Origin-Host' = OH, 328 'Origin-Realm' = OR, 329 'Session-Id' = diameter:session_id(OH)}}. 330 331%% prepare_retransmit/3 332 333prepare_retransmit(_, ?CLIENT, _) -> 334 discard. 335 336%% handle_answer/5 337 338handle_answer(Pkt, _Req, ?CLIENT, _Peer) -> 339 #diameter_packet{msg = Rec, errors = []} = Pkt, 340 Rec. 341 342%% handle_error/5 343 344handle_error(Reason, _Req, ?CLIENT, _Peer) -> 345 {error, Reason}. 346 347%% handle_request/3 348 349handle_request(Pkt, ?SERVER, {_, Caps}) -> 350 #diameter_packet{msg = #diameter_base_STR{'Session-Id' = SId}} 351 = Pkt, 352 #diameter_caps{origin_host = {OH, _}, 353 origin_realm = {OR, _}} 354 = Caps, 355 {reply, #diameter_base_STA{'Result-Code' = ?SUCCESS, 356 'Session-Id' = SId, 357 'Origin-Host' = OH, 358 'Origin-Realm' = OR, 359 'User-Name' = [atom_to_binary(node(), utf8)]}}. 360