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