1: -module(ejabberd_sm_SUITE). 2: -include_lib("eunit/include/eunit.hrl"). 3: -include_lib("common_test/include/ct.hrl"). 4: 5: -include_lib("jid/include/jid.hrl"). 6: -include_lib("session.hrl"). 7: -compile([export_all, nowarn_export_all]). 8: 9: -define(eq(E, I), ?assertEqual(E, I)). 10: 11: -define(B(C), (proplists:get_value(backend, C))). 12: -define(MAX_USER_SESSIONS, 2). 13: 14: -import(config_parser_helper, [default_config/1]). 15: 16: all() -> [{group, mnesia}, {group, redis}, {group, cets}]. 17: 18: init_per_suite(C) -> 19: {ok, _} = application:ensure_all_started(jid), 20: application:ensure_all_started(exometer_core), 21: F = fun() -> 22: ejabberd_sm_backend_sup:start_link(), 23: receive stop -> ok end 24: end, 25: Pid = spawn(F), 26: [{pid, Pid} | C]. 27: 28: end_per_suite(C) -> 29: Pid = ?config(pid, C), 30: Pid ! stop, 31: application:stop(exometer), 32: application:stop(exometer_core). 33: 34: groups() -> 35: [{mnesia, [], tests()}, 36: {redis, [], tests()}, 37: {cets, [], tests()}]. 38: 39: tests() -> 40: [open_session, 41: get_full_session_list, 42: get_vh_session_list, 43: get_sessions_2, 44: get_sessions_3, 45: session_is_updated_when_created_twice, 46: delete_session, 47: clean_up, 48: too_many_sessions, 49: unique_count, 50: unique_count_while_removing_entries, 51: session_info_is_stored, 52: session_info_is_updated_if_keys_match, 53: session_info_is_updated_properly_if_session_conflicts, 54: session_info_is_extended_if_new_keys_present, 55: session_info_keys_not_truncated_if_session_opened_with_empty_infolist, 56: kv_can_be_stored_for_session, 57: kv_can_be_updated_for_session, 58: kv_can_be_removed_for_session, 59: store_info_sends_message_to_the_session_owner, 60: remove_info_sends_message_to_the_session_owner 61: ]. 62: 63: init_per_group(mnesia, Config) -> 64: ok = mnesia:create_schema([node()]), 65: ok = mnesia:start(), 66: [{backend, ejabberd_sm_mnesia} | Config]; 67: init_per_group(redis, Config) -> 68: init_redis_group(is_redis_running(), Config); 69: init_per_group(cets, Config) -> 70: DiscoOpts = #{name => mongoose_cets_discovery, disco_file => "does_not_exist.txt"}, 71: {ok, Pid} = cets_discovery:start(DiscoOpts), 72: [{backend, ejabberd_sm_cets}, {cets_disco_pid, Pid} | Config]. 73: 74: init_redis_group(true, Config) -> 75: Self = self(), 76: proc_lib:spawn(fun() -> 77: register(test_helper, self()), 78: mongoose_wpool:ensure_started(), 79: % This would be started via outgoing_pools in normal case 80: Pool = default_config([outgoing_pools, redis, default]), 81: mongoose_wpool:start_configured_pools([Pool], [], []), 82: Self ! ready, 83: receive stop -> ok end 84: end), 85: receive ready -> ok after timer:seconds(30) -> ct:fail(test_helper_not_ready) end, 86: [{backend, ejabberd_sm_redis} | Config]; 87: init_redis_group(_, _) -> 88: {skip, "redis not running"}. 89: 90: end_per_group(mnesia, Config) -> 91: mnesia:stop(), 92: mnesia:delete_schema([node()]), 93: Config; 94: end_per_group(cets, Config) -> 95: exit(proplists:get_value(cets_disco_pid, Config), kill), 96: Config; 97: end_per_group(redis, Config) -> 98: whereis(test_helper) ! stop, 99: Config. 100: 101: init_per_testcase(too_many_sessions, Config) -> 102: set_test_case_meck(?MAX_USER_SESSIONS, true), 103: setup_sm(Config), 104: Config; 105: init_per_testcase(Case, Config) -> 106: set_test_case_meck(infinity, should_meck_c2s(Case)), 107: setup_sm(Config), 108: Config. 109: 110: should_meck_c2s(store_info_sends_message_to_the_session_owner) -> false; 111: should_meck_c2s(remove_info_sends_message_to_the_session_owner) -> false; 112: should_meck_c2s(_) -> true. 113: 114: end_per_testcase(_, Config) -> 115: clean_sessions(Config), 116: terminate_sm(), 117: unload_meck(), 118: mongoose_config:erase_opts(). 119: 120: open_session(C) -> 121: {Sid, USR} = generate_random_user(<<"localhost">>), 122: given_session_opened(Sid, USR), 123: verify_session_opened(C, Sid, USR). 124: 125: get_full_session_list(C) -> 126: ManyUsers = generate_many_random_users(5, [<<"localhost">>, <<"otherhost">>]), 127: ManyUsersLen = length(ManyUsers), 128: [given_session_opened(Sid, USR) || {Sid, USR} <- ManyUsers], 129: AllSessions = ejabberd_sm:get_full_session_list(), 130: AllSessionsLen = length(AllSessions), 131: AllSessionsLen = ManyUsersLen, 132: [verify_session_opened(C, Sid, USR) || {Sid, USR} <- ManyUsers]. 133: 134: get_vh_session_list(C) -> 135: ManyUsersLocal = generate_many_random_users(5, [<<"localhost">>]), 136: ManyUsersOther = generate_many_random_users(5, [<<"otherhost">>]), 137: ManyUsersLocalLen = length(ManyUsersLocal), 138: [given_session_opened(Sid, USR) || {Sid, USR} <- ManyUsersLocal ++ ManyUsersOther], 139: LocalhostSessions = ejabberd_sm:get_vh_session_list(<<"localhost">>), 140: LocalhostSessionsLen = length(LocalhostSessions), 141: LocalhostSessionsLen = ManyUsersLocalLen, 142: ManyUsersLocalLen = ejabberd_sm:get_vh_session_number(<<"localhost">>), 143: [verify_session_opened(C, Sid, USR) || {Sid, USR} <- ManyUsersLocal]. 144: 145: get_sessions_2(C) -> 146: UsersWithManyResources = generate_many_random_res(5, 3, [<<"localhost">>, <<"otherhost">>]), 147: [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources], 148: USDict = get_unique_us_dict(UsersWithManyResources), 149: [verify_session_opened(C, U, S, dict:fetch({U, S}, USDict)) || {U, S} <- dict:fetch_keys(USDict)], 150: [verify_session_opened(C, Sid, USR) || {Sid, USR} <- UsersWithManyResources]. 151: 152: 153: get_sessions_3(C) -> 154: UserRes = generate_many_random_res(1, 3, [<<"localhost">>]), 155: AllSessions = length(UserRes), 156: {_, {User, Server, _}} = hd(UserRes), 157: [given_session_opened(Sid, USR) || {Sid, USR} <- UserRes], 158: Sessions_2 = ?B(C):get_sessions(User, Server), 159: AllSessions = length(Sessions_2), 160: F = fun({Sid, {U, S, R} = USR}) -> 161: [#session{sid = Sid} = Session] = ?B(C):get_sessions(U, S, R), 162: Session = lists:keyfind(Sid, #session.sid, Sessions_2), 163: Session = lists:keyfind(USR, #session.usr, Sessions_2), 164: true 165: end, 166: true = lists:all(F, UserRes). 167: 168: session_is_updated_when_created_twice(C) -> 169: {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>), 170: given_session_opened(Sid, USR), 171: verify_session_opened(C, Sid, USR), 172: 173: given_session_opened(Sid, USR, 20), 174: verify_session_opened(C, Sid, USR), 175: 176: [#session{usr = USR, sid = Sid, priority = 20}] = ?B(C):get_sessions(), 177: [#session{usr = USR, sid = Sid, priority = 20}] = ?B(C):get_sessions(S), 178: [#session{priority = 20}] = ?B(C):get_sessions(U, S). 179: 180: session_info_is_stored(C) -> 181: {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>), 182: given_session_opened(Sid, USR, 1, [{key1, val1}]), 183: 184: [#session{sid = Sid, info = #{key1 := val1}}] 185: = ?B(C):get_sessions(U,S). 186: 187: session_info_is_updated_if_keys_match(C) -> 188: {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>), 189: given_session_opened(Sid, USR, 1, [{key1, val1}]), 190: 191: when_session_opened(Sid, USR, 1, [{key1, val2}]), 192: 193: [#session{sid = Sid, info = #{key1 := val2}}] 194: = ?B(C):get_sessions(U,S). 195: 196: %% Same resource but different sids 197: session_info_is_updated_properly_if_session_conflicts(C) -> 198: %% We use 2 SIDs here 199: {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>), 200: %% Sid2 > Sid in this case, because SIDs have a timestamp in them 201: %% We cannot test store_info for Sid2 though, because it has a different pid 202: Sid2 = make_sid(), 203: 204: %% Two sessions for the same USR are registered after that: 205: given_session_opened(Sid, USR, 1, [{key1, val1}, {key2, a}]), 206: given_session_opened(Sid2, USR, 1, [{key1, val2}, {key3, b}]), 207: 208: %% Each call to open_session overwrites the previous data without merging. 209: %% The current version of mongoose_c2s calls open_session only once per SID. 210: %% Still, we want to test what happens if we call open_session the second time. 211: when_session_opened(Sid, USR, 1, [{key1, val3}, {key4, c}]), 212: 213: [#session{sid = Sid, info = Info1}, #session{sid = Sid2, info = Info2}] 214: = lists:keysort(#session.sid, ?B(C):get_sessions(U, S)), 215: [{key1, val3}, {key4, c}] = maps:to_list(Info1), 216: [{key1, val2}, {key3, b}] = maps:to_list(Info2). 217: 218: session_info_is_extended_if_new_keys_present(C) -> 219: {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>), 220: given_session_opened(Sid, USR, 1, [{key1, val1}]), 221: 222: when_session_opened(Sid, USR, 1, [{key1, val1}, {key2, val2}]), 223: 224: [#session{sid = Sid, info = #{key1 := val1, key2 := val2}}] 225: = ?B(C):get_sessions(U,S). 226: 227: session_info_keys_not_truncated_if_session_opened_with_empty_infolist(C) -> 228: {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>), 229: given_session_opened(Sid, USR, 1, [{key1, val1}]), 230: 231: %% Should not be called twice in the real life 232: when_session_opened(Sid, USR, 1, []), 233: 234: [#session{sid = Sid, info = #{}}] 235: = ?B(C):get_sessions(U,S). 236: 237: 238: kv_can_be_stored_for_session(C) -> 239: {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>), 240: given_session_opened(Sid, USR, 1, [{key1, val1}]), 241: when_session_info_stored(Sid, U, S, R, {key2, newval}), 242: ?assertMatch([#session{sid = Sid, info = #{key1 := val1, key2 := newval}}], 243: ?B(C):get_sessions(U,S)). 244: 245: kv_can_be_updated_for_session(C) -> 246: {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>), 247: given_session_opened(Sid, USR, 1, [{key1, val1}]), 248: 249: when_session_info_stored(Sid, U, S, R, {key2, newval}), 250: when_session_info_stored(Sid, U, S, R, {key2, override}), 251: 252: ?assertMatch([#session{sid = Sid, info = #{key1 := val1, key2 := override}}], 253: ?B(C):get_sessions(U, S)). 254: 255: kv_can_be_removed_for_session(C) -> 256: {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>), 257: given_session_opened(Sid, USR, 1, [{key1, val1}]), 258: 259: when_session_info_stored(Sid, U, S, R, {key2, newval}), 260: 261: [#session{sid = Sid, info = #{key1 := val1, key2 := newval}}] 262: = ?B(C):get_sessions(U, S), 263: 264: when_session_info_removed(Sid, U, S, R, key2), 265: 266: [#session{sid = Sid, info = #{key1 := val1}}] 267: = ?B(C):get_sessions(U, S), 268: 269: when_session_info_removed(Sid, U, S, R, key1), 270: 271: [#session{sid = Sid, info = #{}}] 272: = ?B(C):get_sessions(U, S). 273: 274: store_info_sends_message_to_the_session_owner(C) -> 275: SID = {erlang:system_time(microsecond), self()}, 276: U = <<"alice2">>, 277: S = <<"localhost">>, 278: R = <<"res1">>, 279: Session = #session{sid = SID, usr = {U, S, R}, us = {U, S}, priority = 1, info = #{}}, 280: %% Create session in one process 281: ?B(C):set_session(U, S, R, Session), 282: %% but call store_info from another process 283: JID = jid:make_noprep(U, S, R), 284: spawn_link(fun() -> ejabberd_sm:store_info(JID, SID, cc, undefined) end), 285: %% The original process receives a message 286: receive 287: {'$gen_cast', {async_with_state, 288: _Fun, 289: [SID, #jid{luser = User, lserver = Server, lresource = Resource}, 290: K, V]}} -> 291: ?eq(U, User), 292: ?eq(S, Server), 293: ?eq(R, Resource), 294: ?eq({cc, undefined}, {K, V}), 295: ok; 296: Message -> 297: ct:fail("unexpected message: ~p", [Message]) 298: after 5000 -> 299: ct:fail("store_info_sends_message_to_the_session_owner=timeout") 300: end. 301: 302: remove_info_sends_message_to_the_session_owner(C) -> 303: SID = {erlang:timestamp(), self()}, 304: U = <<"alice2">>, 305: S = <<"localhost">>, 306: R = <<"res1">>, 307: Session = #session{sid = SID, usr = {U, S, R}, us = {U, S}, priority = 1, info = #{}}, 308: %% Create session in one process 309: ?B(C):set_session(U, S, R, Session), 310: %% but call remove_info from another process 311: JID = jid:make_noprep(U, S, R), 312: spawn_link(fun() -> ejabberd_sm:remove_info(JID, SID, cc) end), 313: %% The original process receives a message 314: receive 315: {'$gen_cast', {async_with_state, 316: _Fun, 317: [SID, #jid{luser = User, lserver = Server, lresource = Resource}, 318: Key, undefined]}} -> 319: ?eq(U, User), 320: ?eq(S, Server), 321: ?eq(R, Resource), 322: ?eq(cc, Key), 323: ok; 324: Message -> 325: ct:fail("unexpected message: ~p", [Message]) 326: after 5000 -> 327: ct:fail("remove_info_sends_message_to_the_session_owner=timeout") 328: end. 329: 330: delete_session(C) -> 331: {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>), 332: given_session_opened(Sid, USR), 333: verify_session_opened(C, Sid, USR), 334: 335: ?B(C):delete_session(Sid, U, S, R), 336: 337: [] = ?B(C):get_sessions(), 338: [] = ?B(C):get_sessions(S), 339: [] = ?B(C):get_sessions(U, S), 340: [] = ?B(C):get_sessions(U, S, R). 341: 342: 343: 344: clean_up(C) -> 345: UsersWithManyResources = generate_many_random_res(5, 3, [<<"localhost">>, <<"otherhost">>]), 346: [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources], 347: ?B(C):cleanup(node()), 348: %% give sm backend some time to clean all sessions 349: ensure_empty(C, 10, ?B(C):get_sessions()). 350: 351: ensure_empty(_C, 0, Sessions) -> 352: [] = Sessions; 353: ensure_empty(C, N, Sessions) -> 354: case Sessions of 355: [] -> 356: ok; 357: _ -> 358: timer:sleep(50), 359: ensure_empty(C, N-1, ?B(C):get_sessions()) 360: end. 361: 362: too_many_sessions(_C) -> 363: %% Max sessions set to ?MAX_USER_SESSIONS in init_per_testcase 364: UserSessions = [generate_random_user(<<"a">>, <<"localhost">>) || 365: _ <- lists:seq(1, ?MAX_USER_SESSIONS + 1)], 366: [given_session_opened(Sid, USR) || {Sid, USR} <- UserSessions], 367: 368: receive 369: {forwarded, _Sid, {'$gen_cast', {exit, <<"Replaced by new connection">>}}} -> 370: ok; 371: Message -> 372: ct:fail("Unexpected message: ~p", [Message]) 373: after 10 -> 374: ct:fail("replaced message not sent") 375: end. 376: 377: 378: 379: unique_count(_C) -> 380: UsersWithManyResources = generate_many_random_res(5, 3, [<<"localhost">>, <<"otherhost">>]), 381: [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources], 382: USDict = get_unique_us_dict(UsersWithManyResources), 383: UniqueCount = ejabberd_sm:get_unique_sessions_number(), 384: UniqueCount = dict:size(USDict). 385: 386: 387: unique_count_while_removing_entries(C) -> 388: unique_count(C), 389: UniqueCount = ejabberd_sm:get_unique_sessions_number(), 390: %% Register more sessions and mock the crash 391: UsersWithManyResources = generate_many_random_res(10, 3, [<<"localhost">>, <<"otherhost">>]), 392: [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources], 393: set_test_case_meck_unique_count_crash(?B(C)), 394: USDict = get_unique_us_dict(UsersWithManyResources), 395: %% Check if unique count equals prev cached value 396: UniqueCount = ejabberd_sm:get_unique_sessions_number(), 397: meck:unload(?B(C)), 398: true = UniqueCount /= dict:size(USDict) + UniqueCount. 399: 400: unload_meck() -> 401: meck:unload(acl), 402: meck:unload(gen_hook), 403: meck:unload(mongoose_domain_api), 404: catch ets:delete(test_c2s_info), 405: catch meck:unload(mongoose_c2s). 406: 407: set_test_case_meck(MaxUserSessions, MeckC2s) -> 408: ets:new(test_c2s_info, [public, named_table]), 409: meck:new(acl, []), 410: meck:expect(acl, match_rule, fun(_, _, _, _) -> MaxUserSessions end), 411: meck:new(gen_hook, []), 412: meck:expect(gen_hook, run_fold, fun(_, _, Acc, _) -> {ok, Acc} end), 413: meck:new(mongoose_domain_api, []), 414: meck:expect(mongoose_domain_api, get_domain_host_type, fun(H) -> {ok, H} end), 415: do_meck_c2s(MeckC2s). 416: 417: do_meck_c2s(false) -> 418: ok; 419: do_meck_c2s(true) -> 420: %% Very simple mock, not even reproducing async behaviour 421: %% It is for a limited use only, for more complex tests use a real c2s process (i.e. probably big tests) 422: meck:new(mongoose_c2s, [passthrough]), 423: meck:expect(mongoose_c2s, async_with_state, fun async_with_state/3), 424: meck:expect(mongoose_c2s, get_info, fun get_info/1), 425: meck:expect(mongoose_c2s, set_info, fun set_info/2), 426: %% Just return same thing all the time 427: meck:expect(mongoose_c2s, get_mod_state, fun(_C2sState, _Mod) -> {error, not_found} end). 428: 429: async_with_state(Pid, Fun, Args) -> 430: apply(Fun, [{ministate, Pid}|Args]), 431: ok. 432: 433: get_info({ministate, Pid}) -> 434: case ets:lookup(test_c2s_info, Pid) of 435: [] -> 436: #{}; 437: [{Pid, Info}] -> 438: Info 439: end. 440: 441: set_info({ministate, Pid} = S, Info) -> 442: ets:insert(test_c2s_info, {Pid, Info}), 443: S. 444: 445: set_test_case_meck_unique_count_crash(Backend) -> 446: F = get_fun_for_unique_count(Backend), 447: meck:new(Backend, []), 448: meck:expect(Backend, unique_count, F). 449: 450: get_fun_for_unique_count(ejabberd_sm_mnesia) -> 451: fun() -> 452: mnesia:abort({badarg,[session,{{1442,941593,580189},list_to_pid("<0.23291.6>")}]}) 453: end; 454: get_fun_for_unique_count(ejabberd_sm_cets) -> 455: fun() -> error(oops) end; 456: get_fun_for_unique_count(ejabberd_sm_redis) -> 457: fun() -> 458: %% The code below is on purpose, it's to crash with badarg reason 459: length({error, timeout}) 460: end. 461: 462: make_sid() -> 463: %% A sid consists of a timestamp and a pid. 464: %% Timestamps can repeat, and a unique pid is needed to avoid sid duplication. 465: TestPid = self(), 466: Pid = spawn_link(fun() -> 467: Sid = ejabberd_sm:make_new_sid(), 468: TestPid ! {sid, Sid}, 469: forward_messages(Sid, TestPid) 470: end), 471: receive 472: {sid, Sid = {_, Pid}} -> Sid 473: after 474: 1000 -> ct:fail("Timeout waiting for sid") 475: end. 476: 477: forward_messages(Sid, Target) -> 478: receive 479: Msg -> Target ! {forwarded, Sid, Msg} 480: end, 481: forward_messages(Sid, Target). 482: 483: given_session_opened(Sid, USR) -> 484: given_session_opened(Sid, USR, 1). 485: 486: given_session_opened(Sid, {U, S, R}, Priority) -> 487: given_session_opened(Sid, {U, S, R}, Priority, []). 488: 489: given_session_opened(Sid, {U, S, R}, Priority, Info) -> 490: {_, Pid} = Sid, 491: Map = maps:from_list(Info), 492: %% open_session is called by c2s usually, but here in tests the function is called by the test. 493: %% we still need to remember the initial info for tests to work though. 494: ets:insert_new(test_c2s_info, {Pid, Map}), 495: JID = jid:make_noprep(U, S, R), 496: ejabberd_sm:open_session(S, Sid, JID, Priority, Map). 497: 498: when_session_opened(Sid, {U, S, R}, Priority, Info) -> 499: given_session_opened(Sid, {U, S, R}, Priority, Info). 500: 501: when_session_info_stored(SID, U, S, R, {K, V}) -> 502: JID = jid:make_noprep(U, S, R), 503: ejabberd_sm:store_info(JID, SID, K, V). 504: 505: when_session_info_removed(SID, U, S, R, Key) -> 506: JID = jid:make_noprep(U, S, R), 507: ejabberd_sm:remove_info(JID, SID, Key). 508: 509: verify_session_opened(C, Sid, USR) -> 510: do_verify_session_opened(?B(C), Sid, USR). 511: 512: do_verify_session_opened(ejabberd_sm_mnesia, Sid, {U, S, R} = USR) -> 513: general_session_check(ejabberd_sm_mnesia, Sid, USR, U, S, R); 514: do_verify_session_opened(ejabberd_sm_cets, Sid, {U, S, R} = USR) -> 515: general_session_check(ejabberd_sm_cets, Sid, USR, U, S, R); 516: do_verify_session_opened(ejabberd_sm_redis, Sid, {U, S, R} = USR) -> 517: UHash = iolist_to_binary(hash(U, S, R, Sid)), 518: Hashes = mongoose_redis:cmd(["SMEMBERS", n(node())]), 519: true = lists:member(UHash, Hashes), 520: SessionsUSEncoded = mongoose_redis:cmd(["SMEMBERS", hash(U, S)]), 521: SessionsUS = [binary_to_term(Entry) || Entry <- SessionsUSEncoded], 522: true = lists:keymember(Sid, 2, SessionsUS), 523: [SessionUSREncoded] = mongoose_redis:cmd(["SMEMBERS", hash(U, S, R)]), 524: SessionUSR = binary_to_term(SessionUSREncoded), 525: #session{sid = Sid} = SessionUSR, 526: general_session_check(ejabberd_sm_redis, Sid, USR, U, S, R). 527: 528: verify_session_opened(C, U, S, Resources) -> 529: Sessions = ?B(C):get_sessions(U, S), 530: F = fun({Sid, USR}) -> 531: #session{} = Session = lists:keyfind(Sid, #session.sid, Sessions), 532: Session == lists:keyfind(USR, #session.usr, Sessions) 533: end, 534: true = lists:all(F, Resources). 535: 536: general_session_check(M, Sid, USR, U, S, R) -> 537: [#session{sid = Sid, usr = USR, us = {U, S}}] = M:get_sessions(U, S, R). 538: 539: clean_sessions(C) -> 540: case ?B(C) of 541: ejabberd_sm_mnesia -> 542: mnesia:clear_table(session); 543: ejabberd_sm_redis -> 544: mongoose_redis:cmd(["FLUSHALL"]); 545: ejabberd_sm_cets -> 546: ets:delete_all_objects(cets_session) 547: end. 548: 549: generate_random_user(S) -> 550: U = base16:encode(crypto:strong_rand_bytes(5)), 551: generate_random_user(U, S). 552: 553: generate_random_user(U, S) -> 554: R = base16:encode(crypto:strong_rand_bytes(5)), 555: generate_user(U, S, R). 556: 557: generate_user(U, S, R) -> 558: Sid = make_sid(), 559: {Sid, {U, S, R}}. 560: 561: generate_many_random_users(PerServerCount, Servers) -> 562: Users = [generate_random_users(PerServerCount, Server) || Server <- Servers], 563: lists:flatten(Users). 564: 565: generate_random_users(Count, Server) -> 566: [generate_random_user(Server) || _ <- lists:seq(1, Count)]. 567: 568: generate_many_random_res(UsersPerServer, ResourcesPerUser, Servers) -> 569: Usernames = [base16:encode(crypto:strong_rand_bytes(5)) || _ <- lists:seq(1, UsersPerServer)], 570: [generate_random_user(U, S) || U <- Usernames, S <- Servers, _ <- lists:seq(1, ResourcesPerUser)]. 571: 572: get_unique_us_dict(USRs) -> 573: F = fun({_, {U, S, _}} = I, SetAcc) -> 574: dict:append({U, S}, I, SetAcc) 575: end, 576: lists:foldl(F, dict:new(), USRs). 577: 578: %% Taken from ejabberd_sm_redis 579: 580: -spec hash(binary()) -> iolist(). 581: hash(Val1) -> 582: ["s3:*:", Val1, ":*"]. 583: 584: 585: -spec hash(binary(), binary()) -> iolist(). 586: hash(Val1, Val2) -> 587: ["s2:", Val1, ":", Val2]. 588: 589: 590: -spec hash(binary(), binary(), binary()) -> iolist(). 591: hash(Val1, Val2, Val3) -> 592: ["s3:", Val1, ":", Val2, ":", Val3]. 593: 594: 595: -spec hash(binary(), binary(), binary(), binary()) -> iolist(). 596: hash(Val1, Val2, Val3, Val4) -> 597: ["s4:", Val1, ":", Val2, ":", Val3, ":", term_to_binary(Val4)]. 598: 599: 600: -spec n(atom()) -> iolist(). 601: n(Node) -> 602: ["n:", atom_to_list(Node)]. 603: 604: 605: is_redis_running() -> 606: case eredis:start_link([{host, "127.0.0.1"}]) of 607: {ok, Client} -> 608: Result = eredis:q(Client, [<<"PING">>], 5000), 609: eredis:stop(Client), 610: case Result of 611: {ok,<<"PONG">>} -> 612: true; 613: _ -> 614: false 615: end; 616: _ -> 617: false 618: end. 619: 620: setup_sm(Config) -> 621: mongoose_config:set_opts(opts(Config)), 622: set_meck(), 623: ejabberd_sm:start_link(), 624: case ?config(backend, Config) of 625: ejabberd_sm_redis -> 626: mongoose_redis:cmd(["FLUSHALL"]); 627: ejabberd_sm_mnesia -> 628: ok; 629: ejabberd_sm_cets -> 630: ok 631: end. 632: 633: terminate_sm() -> 634: gen_server:stop(ejabberd_sm). 635: 636: opts(Config) -> 637: #{hosts => [<<"localhost">>], 638: host_types => [], 639: all_metrics_are_global => false, 640: sm_backend => sm_backend(?config(backend, Config))}. 641: 642: sm_backend(ejabberd_sm_redis) -> redis; 643: sm_backend(ejabberd_sm_mnesia) -> mnesia; 644: sm_backend(ejabberd_sm_cets) -> cets. 645: 646: set_meck() -> 647: meck:expect(gen_hook, add_handler, fun(_, _, _, _, _) -> ok end), 648: meck:expect(gen_hook, add_handlers, fun(_) -> ok end), 649: ok.