1: -module(mongoose_cleanup_SUITE).
    2: 
    3: -include_lib("eunit/include/eunit.hrl").
    4: -include_lib("common_test/include/ct.hrl").
    5: -include("mongoose.hrl").
    6: 
    7: -compile([export_all, nowarn_export_all]).
    8: 
    9: -define(HOST, <<"localhost">>).
   10: 
   11: %% -----------------------------------------------------
   12: %% CT callbacks
   13: %% -----------------------------------------------------
   14: 
   15: all() ->
   16:     [
   17:      cleaner_runs_hook_on_nodedown,
   18:      cleaner_runs_hook_on_nodedown_for_host_type,
   19:      auth_anonymous,
   20:      last,
   21:      {group, cets},
   22:      {group, mnesia}
   23:     ].
   24: 
   25: groups() ->
   26:     [{cets, [], backend_tests()},
   27:      {mnesia, [], backend_tests()},
   28:      {component, [], component_cases()},
   29:      {muc, [], muc_cases()}].
   30: 
   31: backend_tests() ->
   32:     [{group, component}, {group, muc}, bosh, stream_management, s2s].
   33: 
   34: component_cases() ->
   35:     [component, component_from_other_node_remains].
   36: 
   37: muc_cases() ->
   38:     [muc_node_cleanup_for_host_type, muc_room, muc_room_from_other_node_remains].
   39: 
   40: init_per_suite(Config) ->
   41:     {ok, _} = application:ensure_all_started(jid),
   42:     ok = mnesia:create_schema([node()]),
   43:     ok = mnesia:start(),
   44:     mongoose_config:set_opts(opts()),
   45:     lists:foreach(fun setup_meck/1, meck_mods()),
   46:     async_helper:start(Config, [{mim_ct_sup, start_link, [ejabberd_sup]},
   47:                                 {mongooseim_helper, start_link_loaded_hooks, []},
   48:                                 {mongoose_domain_sup, start_link, []},
   49:                                 {mongoose_instrument, start_link, []}]).
   50: 
   51: end_per_suite(Config) ->
   52:     async_helper:stop_all(Config),
   53:     lists:foreach(fun unload_meck/1, meck_mods()),
   54:     mongoose_config:erase_opts(),
   55:     mnesia:stop(),
   56:     mnesia:delete_schema([node()]).
   57: 
   58: init_per_group(cets, Config) ->
   59:     [{backend, cets} | start_cets_disco(Config)];
   60: init_per_group(mnesia, Config) ->
   61:     [{backend, mnesia} | Config];
   62: init_per_group(component, Config) ->
   63:     mongoose_config:set_opt(component_backend, ?config(backend, Config)),
   64:     [{needs_component, true} | Config];
   65: init_per_group(Group, Config) ->
   66:     start_modules(Group, Config),
   67:     Config.
   68: 
   69: end_per_group(cets, Config) ->
   70:     stop_cets_disco(Config);
   71: end_per_group(Group, Config) ->
   72:     stop_modules(Group, Config).
   73: 
   74: init_per_testcase(s2s, Config) ->
   75:     mongoose_config:set_opt(s2s_backend, ?config(backend, Config)),
   76:     Config;
   77: init_per_testcase(TestCase, Config) ->
   78:     start_component_if_needed(?config(needs_component, Config)),
   79:     start_modules(TestCase, Config),
   80:     Config.
   81: 
   82: end_per_testcase(TestCase, Config) ->
   83:     stop_modules(TestCase, Config),
   84:     stop_component_if_needed(?config(needs_component, Config)).
   85: 
   86: start_modules(GroupOrCase, Config) ->
   87:     mongoose_modules:replace_modules(?HOST, [], required_modules(GroupOrCase, Config)).
   88: 
   89: stop_modules(GroupOrCase, Config) ->
   90:     mongoose_modules:replace_modules(?HOST, maps:keys(required_modules(GroupOrCase, Config)), #{}).
   91: 
   92: opts() ->
   93:     #{hosts => [?HOST],
   94:       host_types => [],
   95:       all_metrics_are_global => false,
   96:       s2s_backend => mnesia,
   97:       {auth, ?HOST} => config_parser_helper:extra_auth(),
   98:       {modules, ?HOST} => #{},
   99:       instrumentation => config_parser_helper:default_config([instrumentation])}.
  100: 
  101: meck_mods() ->
  102:     [exometer, mod_bosh_socket, mongoose_bin, ejabberd_sm, ejabberd_local].
  103: 
  104: required_modules(muc, Config) ->
  105:     required_module(mod_muc, #{online_backend => ?config(backend, Config), backend => mnesia});
  106: required_modules(bosh, Config) ->
  107:     required_module(mod_bosh, #{backend => ?config(backend, Config)});
  108: required_modules(stream_management, Config) ->
  109:     required_module(mod_stream_management, #{backend => ?config(backend, Config)});
  110: required_modules(_GroupOrCase, _Config) ->
  111:     #{}.
  112: 
  113: required_module(Module, ExtraOpts) ->
  114:     #{Module => config_parser_helper:mod_config(Module, ExtraOpts)}.
  115: 
  116: start_component_if_needed(true) ->
  117:     mongoose_router:start(),
  118:     mongoose_component:start();
  119: start_component_if_needed(_) ->
  120:     ok.
  121: 
  122: stop_component_if_needed(true) ->
  123:     mongoose_component:stop();
  124: stop_component_if_needed(_) ->
  125:     ok.
  126: 
  127: %% -----------------------------------------------------
  128: %% Tests
  129: %% -----------------------------------------------------
  130: 
  131: cleaner_runs_hook_on_nodedown(_Config) ->
  132:     meck:expect(gen_hook, error_running_hook, fun(_, _, _, _, _) -> ok end),
  133:     {ok, Cleaner} = mongoose_cleaner:start_link(),
  134:     gen_hook:add_handler(node_cleanup, global,
  135:                          fun ?MODULE:notify_self_hook/3,
  136:                          #{self => self()}, 50),
  137:     FakeNode = fakename@fakehost,
  138:     Cleaner ! {nodedown, FakeNode},
  139:     receive
  140:         {got_nodedown, FakeNode} -> ok
  141:     after timer:seconds(1) ->
  142:         ct:fail({timeout, got_nodedown})
  143:     end,
  144:     ?assertEqual(false, meck:called(gen_hook, error_running_hook,
  145:                                     ['_', '_', '_', '_', '_'])).
  146: 
  147: cleaner_runs_hook_on_nodedown_for_host_type(_Config) ->
  148:     HostType = ?HOST,
  149:     {ok, Cleaner} = mongoose_cleaner:start_link(),
  150:     gen_hook:add_handler(node_cleanup_for_host_type, HostType,
  151:                          fun ?MODULE:notify_self_hook_for_host_type/3,
  152:                          #{self => self()}, 50),
  153:     FakeNode = fakename@fakehost,
  154:     Cleaner ! {nodedown, FakeNode},
  155:     receive
  156:         {got_nodedown_for_host_type, FakeNode, HostType} -> ok
  157:     after timer:seconds(1) ->
  158:         ct:fail({timeout, got_nodedown})
  159:     end.
  160: 
  161: notify_self_hook(Acc, #{node := Node}, #{self := Self}) ->
  162:     Self ! {got_nodedown, Node},
  163:     {ok, Acc}.
  164: 
  165: notify_self_hook_for_host_type(Acc, #{node := Node}, #{self := Self, host_type := HostType}) ->
  166:     Self ! {got_nodedown_for_host_type, Node, HostType},
  167:     {ok, Acc}.
  168: 
  169: auth_anonymous(_Config) ->
  170:     HostType = ?HOST,
  171:     {U, S, R, JID, SID} = get_fake_session(),
  172:     ejabberd_auth_anonymous:start(HostType),
  173:     Info = #{auth_module => cyrsasl_anonymous},
  174:     ejabberd_auth_anonymous:register_connection(#{},
  175:                                                 #{sid => SID, jid => JID, info => Info},
  176:                                                 #{host_type => HostType}),
  177:     true = ejabberd_auth_anonymous:does_user_exist(HostType, U, S),
  178:     mongoose_hooks:session_cleanup(S, new_acc(S), U, R, SID),
  179:     false = ejabberd_auth_anonymous:does_user_exist(HostType, U, S).
  180: 
  181: last(_Config) ->
  182:     HostType = ?HOST,
  183:     {U, S, R, JID, SID} = get_fake_session(),
  184:     {started, ok} = start(HostType,
  185:                           mod_last,
  186:                           config_parser_helper:mod_config(mod_last, #{iqdisc => no_queue})),
  187:     not_found = mod_last:get_last_info(HostType, U, S),
  188:     Status1 = <<"status1">>,
  189:     {ok, #{}} = mod_last:on_presence_update(new_acc(S), #{jid => JID, status => Status1}, #{}),
  190:     {ok, TS1, Status1} = mod_last:get_last_info(HostType, U, S),
  191:     async_helper:wait_until(
  192:       fun() ->
  193:               mongoose_hooks:session_cleanup(S, new_acc(S), U, R, SID),
  194:               {ok, TS2, <<>>} = mod_last:get_last_info(HostType, U, S),
  195:               TS2 - TS1 > 0
  196:       end,
  197:       true).
  198: 
  199: stream_management(_Config) ->
  200:     HostType = ?HOST,
  201:     {U, S, R, _JID, SID} = get_fake_session(),
  202:     SMID = <<"123">>,
  203:     mod_stream_management:register_smid(HostType, SMID, SID),
  204:     {sid, SID} = mod_stream_management:get_sid(HostType, SMID),
  205:     mongoose_hooks:session_cleanup(S, new_acc(S), U, R, SID),
  206:     {error, smid_not_found} = mod_stream_management:get_sid(HostType, SMID).
  207: 
  208: s2s(_Config) ->
  209:     ejabberd_s2s:start_link(),
  210:     FromTo = {?HOST, <<"foreign">>},
  211:     ejabberd_s2s:try_register(FromTo),
  212:     Self = self(),
  213:     [Self] = ejabberd_s2s:get_s2s_out_pids(FromTo),
  214:     mongoose_hooks:node_cleanup(node()),
  215:     [] = ejabberd_s2s:get_s2s_out_pids(FromTo).
  216: 
  217: bosh(_Config) ->
  218:     SID = <<"sid">>,
  219:     Self = self(),
  220:     {error, _} = mod_bosh:get_session_socket(SID),
  221:     mod_bosh:store_session(SID, Self),
  222:     {ok, Self} = mod_bosh:get_session_socket(SID),
  223:     mongoose_hooks:node_cleanup(node()),
  224:     {error, _} = mod_bosh:get_session_socket(SID),
  225:     ok.
  226: 
  227: component(_Config) ->
  228:     Handler = fun() -> ok end,
  229:     Domain = <<"cool.localhost">>,
  230:     Node = some_node,
  231:     {ok, _} = mongoose_component:register_components([Domain], Node, Handler, false),
  232:     true = mongoose_component:has_component(Domain),
  233:     #{mongoose_component := ok} = mongoose_hooks:node_cleanup(Node),
  234:     [] = mongoose_component:dirty_get_all_components(all),
  235:     false = mongoose_component:has_component(Domain),
  236:     ok.
  237: 
  238: component_from_other_node_remains(_Config) ->
  239:     Handler = fun() -> ok end,
  240:     Domain = <<"cool.localhost">>,
  241:     {ok, Comps} = mongoose_component:register_components([Domain], other_node, Handler, false),
  242:     true = mongoose_component:has_component(Domain),
  243:     #{mongoose_component := ok} = mongoose_hooks:node_cleanup(some_node),
  244:     true = mongoose_component:has_component(Domain),
  245:     mongoose_component:unregister_components(Comps),
  246:     ok.
  247: 
  248: muc_node_cleanup_for_host_type(_Config) ->
  249:     {ok, Pid} = mongoose_cleaner:start_link(),
  250:     Pid ! {nodedown, 'badnode@localhost'},
  251:     %% Check if the cleaner process is still running
  252:     ok = gen_server:call(Pid, ping).
  253: 
  254: muc_room(_Config) ->
  255:     HostType = ?HOST,
  256:     MucHost = <<"muc.localhost">>,
  257:     Pid = remote_pid(),
  258:     Node = node(Pid),
  259:     Room = <<"remote_room">>,
  260:     ok = mod_muc_online_backend:register_room(HostType, MucHost, Room, Pid),
  261:     ok = mod_muc_online_backend:node_cleanup(HostType, Node),
  262:     {error, not_found} = mod_muc_online_backend:find_room_pid(HostType, MucHost, Room).
  263: 
  264: muc_room_from_other_node_remains(_Config) ->
  265:     HostType = ?HOST,
  266:     MucHost = <<"muc.localhost">>,
  267:     Pid = self(),
  268:     RemoteNode = node(remote_pid()),
  269:     Room = <<"room_on_other_node">>,
  270:     ok = mod_muc_online_backend:register_room(HostType, MucHost, Room, Pid),
  271:     ok = mod_muc_online_backend:node_cleanup(HostType, RemoteNode),
  272:     {ok, Pid} = mod_muc_online_backend:find_room_pid(HostType, MucHost, Room).
  273: 
  274: %% -----------------------------------------------------
  275: %% Internal
  276: %% -----------------------------------------------------
  277: 
  278: setup_meck(exometer) ->
  279:     meck:new(exometer, [no_link]),
  280:     meck:expect(exometer, info, fun(_, _) -> undefined end),
  281:     meck:expect(exometer, new, fun(_, _) -> ok end),
  282:     meck:expect(exometer, update, fun(_, _) -> ok end);
  283: setup_meck(ejabberd_sm) ->
  284:     meck:new(ejabberd_sm, [no_link]),
  285:     meck:expect(ejabberd_sm, register_iq_handler,
  286:                 fun(_A1, _A2, _A3) -> ok end);
  287: setup_meck(ejabberd_local) ->
  288:     meck:new(ejabberd_local, [no_link]),
  289:     meck:expect(ejabberd_local, register_iq_handler,
  290:                 fun(_A1, _A2, _A3) -> ok end);
  291: setup_meck(mongoose_bin) ->
  292:     meck:new(mongoose_bin, [passthrough, no_link]),
  293:     meck:expect(mongoose_bin, gen_from_crypto, fun() -> <<"123456">> end);
  294: setup_meck(mod_bosh_socket) ->
  295:     meck:new(mod_bosh_socket, [passthrough, no_link]),
  296:     meck:expect(mod_bosh_socket, start_supervisor, fun() -> {ok, self()} end).
  297: 
  298: unload_meck(Module) ->
  299:     meck:validate(Module),
  300:     meck:unload(Module).
  301: 
  302: -spec get_fake_session() ->
  303:     {U :: binary(), S :: binary(), R :: binary(),
  304:      JID :: jid:jid(), SID :: ejabberd_sm:sid()}.
  305: get_fake_session() ->
  306:     U = <<"someuser">>,
  307:     S = ?HOST,
  308:     R = <<"someresource">>,
  309:     JID = jid:make(U, S, R),
  310:     SID = {os:timestamp(), self()},
  311:     {U, S, R, JID, SID}.
  312: 
  313: new_acc(Server) ->
  314:     mongoose_acc:new(#{location => ?LOCATION,
  315:                        lserver => Server,
  316:                        host_type => ?HOST,
  317:                        element => undefined}).
  318: 
  319: start(HostType, Module) ->
  320:     start(HostType, Module, config_parser_helper:default_mod_config(Module)).
  321: 
  322: start(HostType, Module, Opts) ->
  323:     mongoose_modules:ensure_started(HostType, Module, Opts).
  324: 
  325: disco_opts() ->
  326:     #{name => mongoose_cets_discovery, disco_file => "does_not_exist.txt"}.
  327: 
  328: start_cets_disco(Config) ->
  329:     {ok, Pid} = cets_discovery:start(disco_opts()),
  330:     [{cets_disco, Pid} | Config].
  331: 
  332: stop_cets_disco(Config) ->
  333:     case proplists:get_value(cets_disco, Config) of
  334:         Pid when is_pid(Pid) ->
  335:             exit(Pid, kill);
  336:         _ ->
  337:             ok
  338:     end.
  339: 
  340: %% Pid 90 on cool_node@localhost
  341: %% Made using:
  342: %% erl -name cool_node@localhost
  343: %% rp(term_to_binary(list_to_pid("<0.90.0>"))).
  344: remote_pid_binary() ->
  345:     <<131, 88, 100, 0, 19, 99, 111, 111, 108, 95, 110, 111, 100, 101, 64,
  346:       108, 111, 99, 97, 108, 104, 111, 115, 116, 0, 0, 0, 90, 0, 0, 0, 0, 100,
  347:       200, 255, 233>>.
  348: 
  349: remote_pid() ->
  350:     binary_to_term(remote_pid_binary()).