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