1: -module(graphql_server_SUITE).
    2: 
    3: -compile([export_all, nowarn_export_all]).
    4: 
    5: -import(distributed_helper, [is_sm_distributed/0,
    6:                              mim/0, mim2/0, mim3/0,
    7:                              remove_node_from_cluster/2,
    8:                              require_rpc_nodes/1, rpc/4]).
    9: -import(domain_helper, [host_type/0, domain/0]).
   10: -import(graphql_helper, [execute_user_command/5, execute_command/4, get_ok_value/2,
   11:                          get_err_msg/1, get_err_code/1, execute_command/5]).
   12: 
   13: -include_lib("eunit/include/eunit.hrl").
   14: 
   15: suite() ->
   16:     require_rpc_nodes([mim]) ++ escalus:suite().
   17: 
   18: all() ->
   19:     [{group, admin_http},
   20:      {group, admin_cli}].
   21: 
   22: groups() ->
   23:     [{admin_http, [], admin_http_groups()},
   24:      {admin_cli, [], admin_cli_groups()},
   25:      {server_tests, [], admin_tests()},
   26:      {clustering_tests, [], clustering_tests()},
   27:      {clustering_http_tests, [], clustering_http_tests()}].
   28: 
   29: admin_cli_groups() ->
   30:     [{group, server_tests},
   31:      {group, clustering_tests}].
   32: 
   33: admin_http_groups() ->
   34:     [{group, server_tests},
   35:      {group, clustering_http_tests}].
   36: 
   37: admin_tests() ->
   38:     [get_cookie_test,
   39:      set_and_get_loglevel_test,
   40:      get_status_test].
   41: 
   42: clustering_tests() ->
   43:     [join_successful,
   44:      leave_successful,
   45:      join_unsuccessful,
   46:      leave_but_no_cluster,
   47:      join_twice,
   48:      leave_twice,
   49:      remove_dead_from_cluster,
   50:      remove_alive_from_cluster,
   51:      remove_node_test,
   52:      stop_node_test].
   53: 
   54: clustering_http_tests() ->
   55:     [join_successful_http,
   56:      leave_successful_http,
   57:      remove_dead_from_cluster_http,
   58:      remove_alive_from_cluster_http,
   59:      remove_node_test,
   60:      stop_node_test].
   61: 
   62: init_per_suite(Config) ->
   63:     Config1 = dynamic_modules:save_modules(host_type(), Config),
   64:     Config2 = lists:foldl(fun(#{node := Node} = RPCNode, ConfigAcc) ->
   65:         ConfigAcc1 = ejabberd_node_utils:init(RPCNode, ConfigAcc),
   66:         NodeCtlPath = distributed_helper:ctl_path(Node, ConfigAcc1),
   67:         ConfigAcc1 ++ [{ctl_path_atom(Node), NodeCtlPath}]
   68:     end, Config1, [mim(), mim2(), mim3()]),
   69:     escalus:init_per_suite(Config2).
   70: 
   71: ctl_path_atom(NodeName) ->
   72:     CtlString = atom_to_list(NodeName) ++ "_ctl",
   73:     list_to_atom(CtlString).
   74: 
   75: end_per_suite(Config) ->
   76:     dynamic_modules:restore_modules(Config),
   77:     escalus:end_per_suite(Config).
   78: 
   79: init_per_group(admin_http, Config) ->
   80:     graphql_helper:init_admin_handler(Config);
   81: init_per_group(admin_cli, Config) ->
   82:     graphql_helper:init_admin_cli(Config);
   83: init_per_group(Group, Config) when Group =:= clustering_tests; Group =:= clustering_http_tests ->
   84:     case is_sm_distributed() of
   85:         true ->
   86:             Config;
   87:         {false, Backend} ->
   88:             ct:pal("Backend ~p doesn't support distributed tests", [Backend]),
   89:             {skip, nondistributed_sm}
   90:     end;
   91: init_per_group(_, Config) ->
   92:     Config.
   93: 
   94: end_per_group(Group, _Config) when Group =:= admin_http;
   95:                                    Group =:= admin_cli ->
   96:     graphql_helper:clean();
   97: end_per_group(_, _Config) ->
   98:     escalus_fresh:clean().
   99: 
  100: init_per_testcase(set_and_get_loglevel_test = CaseName, Config) ->
  101:     Config1 = mim_loglevel:save_log_level(Config),
  102:     escalus:init_per_testcase(CaseName, Config1);
  103: init_per_testcase(CaseName, Config) ->
  104:     escalus:init_per_testcase(CaseName, Config).
  105: 
  106: 
  107: end_per_testcase(set_and_get_loglevel_test = CaseName, Config) ->
  108:     mim_loglevel:restore_log_level(Config),
  109:     escalus:end_per_testcase(CaseName, Config);
  110: end_per_testcase(CaseName, Config) when CaseName == join_successful
  111:                                    orelse CaseName == join_successful_http
  112:                                    orelse CaseName == join_twice
  113:                                    orelse CaseName == leave_twice ->
  114:     remove_node_from_cluster(mim2(), Config),
  115:     escalus:end_per_testcase(CaseName, Config);
  116: end_per_testcase(CaseName, Config) when CaseName == remove_alive_from_cluster
  117:                                    orelse CaseName == remove_dead_from_cluster
  118:                                    orelse CaseName == remove_alive_from_cluster_http
  119:                                    orelse CaseName == remove_dead_from_cluster_http ->
  120:     remove_node_from_cluster(mim2(), Config),
  121:     remove_node_from_cluster(mim3(), Config),
  122:     escalus:end_per_testcase(CaseName, Config);
  123: end_per_testcase(CaseName, Config) ->
  124:     escalus:end_per_testcase(CaseName, Config).
  125: 
  126: get_cookie_test(Config) ->
  127:     Result = get_ok_value([data, server, getCookie], get_cookie(Config)),
  128:     ?assert(is_binary(Result)).
  129: 
  130: set_and_get_loglevel_test(Config) ->
  131:     LogLevels = all_log_levels(),
  132:     lists:foreach(fun(LogLevel) ->
  133:         Value = get_ok_value([data, server, setLoglevel], set_loglevel(LogLevel, Config)),
  134:         ?assertEqual(<<"Log level successfully set.">>, Value),
  135:         Value1 = get_ok_value([data, server, getLoglevel], get_loglevel(Config)),
  136:         ?assertEqual(LogLevel, Value1)
  137:     end, LogLevels),
  138:     {_, Res} = set_loglevel(<<"AAAA">>, Config),
  139:     [Res1] = maps:get(<<"errors">>, Res),
  140:     ?assertEqual(<<"unknown_enum">>, graphql_helper:get_value([extensions, code], Res1)).
  141: 
  142: get_status_test(Config) ->
  143:     Result = get_ok_value([data, server, status], get_status(Config)),
  144:     ?assertEqual(<<"RUNNING">>, maps:get(<<"statusCode">>, Result)),
  145:     ?assert(is_binary(maps:get(<<"message">>, Result))),
  146:     ?assert(is_binary(maps:get(<<"version">>, Result))).
  147: 
  148: 
  149: join_successful(Config) ->
  150:     #{node := Node2} = RPCSpec2 = mim2(),
  151:     leave_cluster(Config),
  152:     get_ok_value([], join_cluster(atom_to_binary(Node2), Config)),
  153:     distributed_helper:verify_result(RPCSpec2, add).
  154: 
  155: leave_successful(Config) ->
  156:     #{node := Node2} = RPCSpec2 = mim2(),
  157:     join_cluster(atom_to_binary(Node2), Config),
  158:     get_ok_value([], leave_cluster(Config)),
  159:     distributed_helper:verify_result(RPCSpec2, remove).
  160: 
  161: join_unsuccessful(Config) ->
  162:     Node2 = mim2(),
  163:     join_cluster(<<>>, Config),
  164:     distributed_helper:verify_result(Node2, remove).
  165: 
  166: leave_but_no_cluster(Config) ->
  167:     Node2 = mim2(),
  168:     get_err_code(leave_cluster(Config)),
  169:     distributed_helper:verify_result(Node2, remove).
  170: 
  171: join_twice(Config) ->
  172:     #{node := Node2} = RPCSpec2 = mim2(),
  173:     get_ok_value([], join_cluster(atom_to_binary(Node2), Config)),
  174:     ?assertEqual(<<"already_joined">>, get_err_code(join_cluster(atom_to_binary(Node2), Config))),
  175:     distributed_helper:verify_result(RPCSpec2, add).
  176: 
  177: leave_twice(Config) ->
  178:     #{node := Node2} = RPCSpec2 = mim2(),
  179:     join_cluster(atom_to_binary(Node2), Config),
  180:     get_ok_value([], leave_cluster(Config)),
  181:     distributed_helper:verify_result(RPCSpec2, remove),
  182:     ?assertEqual(<<"not_in_cluster">>, get_err_code(leave_cluster(Config))).
  183: 
  184: remove_dead_from_cluster(Config) ->
  185:     % given
  186:     Timeout = timer:seconds(60),
  187:     #{node := Node1Nodename} = Node1 = mim(),
  188:     #{node := _Node2Nodename} = Node2 = mim2(),
  189:     #{node := Node3Nodename} = Node3 = mim3(),
  190:     ok = rpc(Node2#{timeout => Timeout}, mongoose_cluster, join, [Node1Nodename]),
  191:     ok = rpc(Node3#{timeout => Timeout}, mongoose_cluster, join, [Node1Nodename]),
  192:     %% when
  193:     distributed_helper:stop_node(Node3Nodename, Config),
  194:     get_ok_value([data, server, removeFromCluster],
  195:                   remove_from_cluster(atom_to_binary(Node3Nodename), Config)),
  196:     %% then
  197:     % node is down hence its not in mnesia cluster
  198:     have_node_in_mnesia_wait(Node1, Node2, true),
  199:     have_node_in_mnesia_wait(Node1, Node3, false),
  200:     have_node_in_mnesia_wait(Node2, Node3, false),
  201:     % after node awakening nodes are clustered again
  202:     distributed_helper:start_node(Node3Nodename, Config),
  203:     have_node_in_mnesia_wait(Node1, Node3, true),
  204:     have_node_in_mnesia_wait(Node2, Node3, true).
  205: 
  206: remove_alive_from_cluster(Config) ->
  207:     % given
  208:     Timeout = timer:seconds(60),
  209:     #{node := Node1Name} = Node1 = mim(),
  210:     #{node := Node2Name} = Node2 = mim2(),
  211:     Node3 = mim3(),
  212:     ok = rpc(Node2#{timeout => Timeout}, mongoose_cluster, join, [Node1Name]),
  213:     ok = rpc(Node3#{timeout => Timeout}, mongoose_cluster, join, [Node1Name]),
  214:     %% when
  215:     %% Node2 is still running
  216:     %% then
  217:     get_ok_value([], remove_from_cluster(atom_to_binary(Node2Name), Config)),
  218:     have_node_in_mnesia(Node1, Node3, true),
  219:     have_node_in_mnesia(Node1, Node2, false),
  220:     have_node_in_mnesia(Node3, Node2, false).
  221: 
  222: remove_node_test(Config) ->
  223:     #{node := NodeName} = mim3(),
  224:     Value = get_ok_value([data, server, removeNode], remove_node(NodeName, Config)),
  225:     ?assertEqual(<<"MongooseIM node removed from the Mnesia schema">>, Value).
  226: 
  227: stop_node_test(Config) ->
  228:     #{node := Node3Nodename} = mim3(),
  229:     get_ok_value([data, server, stop], stop_node(Node3Nodename, Config)),
  230:     Timeout = timer:seconds(3),
  231:     F = fun() -> rpc:call(Node3Nodename, application, which_applications, [], Timeout) end,
  232:     mongoose_helper:wait_until(F, {badrpc, nodedown}, #{sleep_time => 1000, name => stop_node}),
  233:     distributed_helper:start_node(Node3Nodename, Config).
  234: 
  235: join_successful_http(Config) ->
  236:     #{node := Node2} = RPCSpec2 = mim2(),
  237:     leave_cluster(Config),
  238:     distributed_helper:verify_result(RPCSpec2, remove),
  239:     get_ok_value([], join_cluster(atom_to_binary(Node2), Config)),
  240:     distributed_helper:verify_result(RPCSpec2, add).
  241: 
  242: leave_successful_http(Config) ->
  243:     #{node := Node2} = RPCSpec2 = mim2(),
  244:     join_cluster(atom_to_binary(Node2), Config),
  245:     distributed_helper:verify_result(RPCSpec2, add),
  246:     get_ok_value([], leave_cluster(Config)),
  247:     distributed_helper:verify_result(RPCSpec2, remove).
  248: 
  249: remove_dead_from_cluster_http(Config) ->
  250:     % given
  251:     Timeout = timer:seconds(60),
  252:     #{node := Node1Nodename} = Node1 = mim(),
  253:     #{node := _Node2Nodename} = Node2 = mim2(),
  254:     #{node := Node3Nodename} = Node3 = mim3(),
  255:     ok = rpc(Node2#{timeout => Timeout}, mongoose_cluster, join, [Node1Nodename]),
  256:     ok = rpc(Node3#{timeout => Timeout}, mongoose_cluster, join, [Node1Nodename]),
  257:     %% when
  258:     distributed_helper:stop_node(Node3Nodename, Config),
  259:     F2 = fun() ->
  260:         test == rpc(Node1#{timeout => Timeout}, mongoose_config, get_opt, [listen, test])
  261:     end,
  262:     mongoose_helper:wait_until(F2, false, #{sleep_time => 200, name => wait_for_mim1,
  263:                                             time_left => timer:seconds(20)}),
  264:     get_ok_value([data, server, removeFromCluster],
  265:                   remove_from_cluster(atom_to_binary(Node3Nodename), Config)),
  266:     have_node_in_mnesia_wait(Node1, Node2, true),
  267:     have_node_in_mnesia_wait(Node1, Node3, false),
  268:     have_node_in_mnesia_wait(Node2, Node3, false),
  269:     % after node awakening nodes are clustered again
  270:     distributed_helper:start_node(Node3Nodename, Config),
  271:     ensure_node_started(Node3),
  272:     have_node_in_mnesia_wait(Node1, Node3, true),
  273:     have_node_in_mnesia_wait(Node2, Node3, true).
  274: 
  275: remove_alive_from_cluster_http(Config) ->
  276:     % given
  277:     Timeout = timer:seconds(60),
  278:     #{node := Node1Name} = Node1 = mim(),
  279:     #{node := Node2Name} = Node2 = mim2(),
  280:     Node3 = mim3(),
  281:     ok = rpc(Node2#{timeout => Timeout}, mongoose_cluster, join, [Node1Name]),
  282:     ok = rpc(Node3#{timeout => Timeout}, mongoose_cluster, join, [Node1Name]),
  283:     %% when
  284:     %% Node2 is still running
  285:     %% then
  286:     get_ok_value([], remove_from_cluster(atom_to_binary(Node2Name), Config)),
  287:     have_node_in_mnesia_wait(Node1, Node3, true),
  288:     have_node_in_mnesia_wait(Node1, Node2, false),
  289:     have_node_in_mnesia_wait(Node3, Node2, false).
  290: 
  291: ensure_node_started(Node) ->
  292:     Timeout = timer:seconds(60),
  293:     F = fun() ->
  294:         case rpc(Node#{timeout => Timeout}, mongoose_server_api, status, []) of
  295:             {ok, {true, _, _}} -> true;
  296:             _Other -> false
  297:         end
  298:     end,
  299:     mongoose_helper:wait_until(F, true, #{sleep_time => 200, name => wait_for_start_mim3,
  300:                                           time_left => timer:seconds(20)}).
  301: 
  302: %-----------------------------------------------------------------------
  303: %                                Helpers
  304: %-----------------------------------------------------------------------
  305: 
  306: have_node_in_mnesia_wait(Node1, #{node := Node2}, Value) ->
  307:     mongoose_helper:wait_until(fun() ->
  308:                                    DbNodes1 = distributed_helper:rpc(Node1, mnesia,
  309:                                                                      system_info, [db_nodes]),
  310:                                    lists:member(Node2, DbNodes1)
  311:                                end,
  312:                                Value,
  313:                                #{
  314:                                  time_left => timer:seconds(12),
  315:                                  sleep_time => 200,
  316:                                  name => have_node_in_mnesia
  317:                                 }).
  318: 
  319: all_log_levels() ->
  320:     [<<"NONE">>,
  321:      <<"EMERGENCY">>,
  322:      <<"ALERT">>,
  323:      <<"CRITICAL">>,
  324:      <<"ERROR">>,
  325:      <<"WARNING">>,
  326:      <<"NOTICE">>,
  327:      <<"INFO">>,
  328:      <<"DEBUG">>,
  329:      <<"ALL">>].
  330: 
  331: have_node_in_mnesia(Node1, #{node := Node2}, ShouldBe) ->
  332:     DbNodes1 = distributed_helper:rpc(Node1, mnesia, system_info, [db_nodes]),
  333:     ?assertEqual(ShouldBe, lists:member(Node2, DbNodes1)).
  334: 
  335: get_cookie(Config) ->
  336:     execute_command(<<"server">>, <<"getCookie">>, #{}, Config).
  337: 
  338: get_loglevel(Config) ->
  339:     execute_command(<<"server">>, <<"getLoglevel">>, #{}, Config).
  340: 
  341: set_loglevel(LogLevel, Config) ->
  342:     execute_command(<<"server">>, <<"setLoglevel">>, #{<<"level">> => LogLevel}, Config).
  343: 
  344: get_status(Config) ->
  345:     execute_command(<<"server">>, <<"status">>, #{}, Config).
  346: 
  347: get_status(Node, Config) ->
  348:     execute_command(Node, <<"server">>, <<"status">>, #{}, Config).
  349: 
  350: join_cluster(Node, Config) ->
  351:     execute_command(<<"server">>, <<"joinCluster">>, #{<<"node">> => Node}, Config).
  352: 
  353: leave_cluster(Config) ->
  354:     execute_command(<<"server">>, <<"leaveCluster">>, #{}, Config).
  355: 
  356: remove_from_cluster(Node, Config) ->
  357:     execute_command(<<"server">>, <<"removeFromCluster">>, #{<<"node">> => Node}, Config).
  358: 
  359: stop_node(Node, Config) ->
  360:     execute_command(Node, <<"server">>, <<"stop">>, #{}, Config).
  361: 
  362: remove_node(Node, Config) ->
  363:     execute_command(Node, <<"server">>, <<"removeNode">>, #{<<"node">> => Node}, Config).