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