1: %%==============================================================================
    2: %% Copyright 2015 Erlang Solutions Ltd.
    3: %%
    4: %% Licensed under the Apache License, Version 2.0 (the "License");
    5: %% you may not use this file except in compliance with the License.
    6: %% You may obtain a copy of the License at
    7: %%
    8: %% http://www.apache.org/licenses/LICENSE-2.0
    9: %%
   10: %% Unless required by applicable law or agreed to in writing, software
   11: %% distributed under the License is distributed on an "AS IS" BASIS,
   12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13: %% See the License for the specific language governing permissions and
   14: %% limitations under the License.
   15: %%==============================================================================
   16: -module(mod_ping_SUITE).
   17: -compile([export_all, nowarn_export_all]).
   18: 
   19: -include_lib("escalus/include/escalus.hrl").
   20: -include_lib("escalus/include/escalus_xmlns.hrl").
   21: -include_lib("exml/include/exml.hrl").
   22: -include_lib("common_test/include/ct.hrl").
   23: 
   24: -import(domain_helper, [domain/0]).
   25: 
   26: %%--------------------------------------------------------------------
   27: %% Suite configuration
   28: %%--------------------------------------------------------------------
   29: all() ->
   30:     [{group, client_ping},
   31:      {group, server_ping},
   32:      {group, server_ping_kill}].
   33: 
   34: groups() ->
   35:     % Don't make these parallel! Metrics tests will most probably fail
   36:     % and injected hook will most probably won't work as expected.
   37:     G = [{client_ping, [], [disco, ping]},
   38:          {server_ping, [], all_tests()},
   39:          {server_ping_kill, [], all_tests()}
   40:         ],
   41:     ct_helper:repeat_all_until_all_ok(G).
   42: 
   43: client_ping_test_cases() ->
   44:     [ping,
   45:      wrong_ping].
   46: 
   47: all_tests() ->
   48:     [disco,
   49:      ping,
   50:      wrong_ping,
   51:      active,
   52:      active_keep_alive,
   53:      server_ping_pong,
   54:      server_ping_pang].
   55: 
   56: suite() ->
   57:     escalus:suite().
   58: 
   59: ping_interval() ->
   60:     timer:seconds(3).
   61: 
   62: ping_req_timeout() ->
   63:     timer:seconds(2).
   64: 
   65: init_per_suite(Config) ->
   66:     mongoose_helper:inject_module(?MODULE),
   67:     escalus:init_per_suite(Config).
   68: 
   69: end_per_suite(Config) ->
   70:     escalus_fresh:clean(),
   71:     escalus:end_per_suite(Config).
   72: 
   73: init_per_group(client_ping, Config) ->
   74:     start_mod_ping([]),
   75:     Config;
   76: init_per_group(server_ping, Config) ->
   77:     start_mod_ping([{send_pings, true},
   78:                     {ping_interval, ping_interval()},
   79:                     {ping_req_timeout, ping_req_timeout()}]),
   80:     Config;
   81: init_per_group(server_ping_kill, Config) ->
   82:     start_mod_ping([{send_pings, true},
   83:                     {ping_interval, ping_interval()},
   84:                     {ping_req_timeout, ping_req_timeout()},
   85:                     {timeout_action, kill}]),
   86:     [{timeout_action, kill} | Config].
   87: 
   88: end_per_group(_GroupName, Config) ->
   89:     HostType = domain_helper:host_type(mim),
   90:     dynamic_modules:stop(HostType, mod_ping),
   91:     Config.
   92: 
   93: init_per_testcase(server_ping_pong = CN, Config) ->
   94:     NConfig = setup_pong_hook(Config),
   95:     escalus:init_per_testcase(CN, NConfig);
   96: init_per_testcase(CaseName, Config) ->
   97:     escalus:init_per_testcase(CaseName, Config).
   98: 
   99: end_per_testcase(server_ping_pong = CN, Config) ->
  100:     NConfig = clear_pong_hook(Config),
  101:     escalus:init_per_testcase(CN, NConfig);
  102: end_per_testcase(CaseName, Config) ->
  103:     escalus:end_per_testcase(CaseName, Config).
  104: 
  105: start_mod_ping(Opts) ->
  106:     HostType = domain_helper:host_type(mim),
  107:     dynamic_modules:start(HostType, mod_ping, Opts).
  108: 
  109: setup_pong_hook(Config) ->
  110:     Pid = self(),
  111:     HostType = domain_helper:host_type(mim),
  112:     mongoose_helper:successful_rpc(?MODULE, setup_pong_hook, [HostType, Pid]),
  113:     [{pid, Pid} | Config].
  114: 
  115: setup_pong_hook(HostType, Pid) ->
  116:     gen_hook:add_handler(user_ping_response, HostType,
  117:                          fun ?MODULE:pong_hook_handler/3,
  118:                          #{pid => Pid}, 50).
  119: 
  120: pong_hook_handler(Acc,
  121:                   #{jid := JID} = _Params,
  122:                   #{pid := Pid} = _Extra) ->
  123:     Pid ! {pong, jid:to_binary(jid:to_lower(JID))},
  124:     {ok, Acc}.
  125: 
  126: clear_pong_hook(Config) ->
  127:     {value, {_, Pid}, NConfig} = lists:keytake(pid, 1, Config),
  128:     HostType = domain_helper:host_type(mim),
  129:     mongoose_helper:successful_rpc(?MODULE, clear_pong_hook, [HostType, Pid]),
  130:     NConfig.
  131: 
  132: clear_pong_hook(HostType, Pid) ->
  133:     gen_hook:delete_handler(user_ping_response, HostType,
  134:                             fun ?MODULE:pong_hook_handler/3,
  135:                             #{pid => Pid}, 50).
  136: 
  137: %%--------------------------------------------------------------------
  138: %% Ping tests
  139: %%--------------------------------------------------------------------
  140: disco(Config) ->
  141:     escalus:fresh_story(
  142:       Config, [{alice, 1}],
  143:       fun(Alice) ->
  144:               escalus_client:send(Alice, escalus_stanza:disco_info(domain())),
  145:               Response = escalus_client:wait_for_stanza(Alice),
  146:               escalus:assert(has_feature, [?NS_PING], Response)
  147:       end).
  148: 
  149: ping(ConfigIn) ->
  150:     Domain = domain(),
  151:     HostType = domain_helper:host_type(mim),
  152:     HostTypePrefix = domain_helper:make_metrics_prefix(HostType),
  153:     Metrics = [
  154:         {[HostTypePrefix, mod_ping, ping_response],0},
  155:         {[HostTypePrefix, mod_ping, ping_response_timeout],0}
  156:     ],
  157:     Config = [{mongoose_metrics, Metrics} | ConfigIn],
  158:     escalus:fresh_story(Config, [{alice, 1}],
  159:         fun(Alice) ->
  160:                 PingReq = escalus_stanza:ping_request(Domain),
  161:                 escalus_client:send(Alice, PingReq),
  162: 
  163:                 PingResp = escalus_client:wait_for_stanza(Alice),
  164:                 escalus:assert(is_iq_result, [PingReq], PingResp)
  165:         end).
  166: 
  167: wrong_ping(Config) ->
  168:     escalus:fresh_story(Config, [{alice, 1}],
  169:         fun(Alice) ->
  170:             Domain = domain(),
  171:             IQ = escalus_stanza:iq(<<"get">>, [#xmlel{name = <<"unsupported">>,
  172:                                                       attrs = [{<<"xmlns">>, ?NS_PING}]
  173:             }]),
  174:             PingReq = escalus_stanza:to(IQ, Domain),
  175:             escalus_client:send(Alice, PingReq),
  176: 
  177:             PingResp = escalus_client:wait_for_stanza(Alice),
  178:             escalus:assert(is_iq_error, [PingReq], PingResp)
  179:         end).
  180: 
  181: active(ConfigIn) ->
  182:     Domain = domain(),
  183:     HostType = domain_helper:host_type(mim),
  184:     HostTypePrefix = domain_helper:make_metrics_prefix(HostType),
  185:     Metrics = [
  186:         {[HostTypePrefix, mod_ping, ping_response],0},
  187:         {[HostTypePrefix, mod_ping, ping_response_timeout],0}
  188:     ],
  189:     Config = [{mongoose_metrics, Metrics} | ConfigIn],
  190:     escalus:fresh_story(Config, [{alice, 1}],
  191:         fun(Alice) ->
  192:                 wait_ping_interval(0.75),
  193:                 escalus_client:send(Alice, escalus_stanza:ping_request(Domain)),
  194:                 escalus:assert(is_iq_result, escalus_client:wait_for_stanza(Alice)),
  195:                 % wait more time and check if connection got ping req
  196:                 wait_ping_interval(0.5),
  197:                 % it shouldn't as the ping was sent
  198:                 false = escalus_client:has_stanzas(Alice)
  199:         end).
  200: 
  201: active_keep_alive(ConfigIn) ->
  202:     HostType = domain_helper:host_type(mim),
  203:     HostTypePrefix = domain_helper:make_metrics_prefix(HostType),
  204:     Metrics = [
  205:         {[HostTypePrefix, mod_ping, ping_response],0},
  206:         {[HostTypePrefix, mod_ping, ping_response_timeout],0}
  207:     ],
  208:     Config = [{mongoose_metrics, Metrics} | ConfigIn],
  209:     escalus:fresh_story(Config, [{alice, 1}],
  210:         fun(Alice) ->
  211:                 wait_ping_interval(0.75),
  212:                 escalus_tcp:send(Alice#client.rcv_pid, #xmlcdata{content = "\n"}),
  213:                 wait_ping_interval(0.5),
  214: 
  215:                 false = escalus_client:has_stanzas(Alice)
  216:         end).
  217: 
  218: server_ping_pong(ConfigIn) ->
  219:     HostType = domain_helper:host_type(mim),
  220:     HostTypePrefix = domain_helper:make_metrics_prefix(HostType),
  221:     Metrics = [
  222:         {[HostTypePrefix, mod_ping, ping_response], 5},
  223:         {[HostTypePrefix, mod_ping, ping_response_timeout], 0},
  224:         {[HostTypePrefix, mod_ping, ping_response_time], changed}
  225:     ],
  226:     Config = [{mongoose_metrics, Metrics} | ConfigIn],
  227:     %% We use 5 Alices because with just 1 sample the histogram may look like it hasn't changed
  228:     %% due to exometer histogram implementation
  229:     escalus:fresh_story(Config, [{alice, 5}],
  230:         fun(Alice1, Alice2, Alice3, Alice4, Alice5) ->
  231:                 lists:foreach(fun(Alice) ->
  232:                                       PingReq = wait_for_ping_req(Alice),
  233:                                       Pong = escalus_stanza:iq_result(PingReq),
  234:                                       escalus_client:send(Alice, Pong)
  235:                               end, [Alice1, Alice2, Alice3, Alice4, Alice5]),
  236:                 wait_for_pong_hooks(5)
  237:         end).
  238: 
  239: server_ping_pang(ConfigIn) ->
  240:     HostType = domain_helper:host_type(mim),
  241:     HostTypePrefix = domain_helper:make_metrics_prefix(HostType),
  242:     Metrics = [
  243:         {[HostTypePrefix, mod_ping, ping_response], 0},
  244:         {[HostTypePrefix, mod_ping, ping_response_timeout], 1}
  245:     ],
  246:     Config = [{mongoose_metrics, Metrics} | ConfigIn],
  247:     escalus:fresh_story(Config, [{alice, 1}],
  248:         fun(Alice) ->
  249:                 wait_for_ping_req(Alice),
  250:                 %% do not resp to ping req
  251:                 ct:sleep(ping_req_timeout() + timer:seconds(1)/2),
  252:                 TimeoutAction = ?config(timeout_action, Config),
  253:                 check_connection(TimeoutAction, Alice),
  254:                 escalus_client:kill_connection(Config, Alice)
  255:         end).
  256: 
  257: wait_ping_interval(Ration) ->
  258:     WaitTime = ping_interval() * Ration,
  259:     ct:sleep(WaitTime).
  260: 
  261: check_connection(kill, Client) ->
  262:     false = escalus_connection:is_connected(Client);
  263: check_connection(_, Client) ->
  264:     true = escalus_connection:is_connected(Client).
  265: 
  266: wait_for_ping_req(Alice) ->
  267:     PingReq = escalus_client:wait_for_stanza(Alice, timer:seconds(10)),
  268:     escalus:assert(is_iq_get, PingReq),
  269:     <<"urn:xmpp:ping">> = exml_query:path(PingReq, [{element, <<"ping">>},
  270:                                                     {attr, <<"xmlns">>}]),
  271:     PingReq.
  272: 
  273: wait_for_pong_hooks(0) ->
  274:     ok;
  275: wait_for_pong_hooks(N) ->
  276:     receive
  277:         {pong, _} -> wait_for_pong_hooks(N-1)
  278:     after
  279:         5000 ->
  280:             ct:fail({pong_hook_runs_missing, N})
  281:     end.