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:     ?assert(is_binary(maps:get(<<"commitHash">>, Result))).
  148: 
  149: 
  150: join_successful(Config) ->
  151:     #{node := Node2} = RPCSpec2 = mim2(),
  152:     leave_cluster(Config),
  153:     get_ok_value([], join_cluster(atom_to_binary(Node2), Config)),
  154:     distributed_helper:verify_result(RPCSpec2, add).
  155: 
  156: leave_successful(Config) ->
  157:     #{node := Node2} = RPCSpec2 = mim2(),
  158:     join_cluster(atom_to_binary(Node2), Config),
  159:     get_ok_value([], leave_cluster(Config)),
  160:     distributed_helper:verify_result(RPCSpec2, remove).
  161: 
  162: join_unsuccessful(Config) ->
  163:     Node2 = mim2(),
  164:     join_cluster(<<>>, Config),
  165:     distributed_helper:verify_result(Node2, remove).
  166: 
  167: leave_but_no_cluster(Config) ->
  168:     Node2 = mim2(),
  169:     get_err_code(leave_cluster(Config)),
  170:     distributed_helper:verify_result(Node2, remove).
  171: 
  172: join_twice(Config) ->
  173:     #{node := Node2} = RPCSpec2 = mim2(),
  174:     get_ok_value([], join_cluster(atom_to_binary(Node2), Config)),
  175:     ?assertEqual(<<"already_joined">>, get_err_code(join_cluster(atom_to_binary(Node2), Config))),
  176:     distributed_helper:verify_result(RPCSpec2, add).
  177: 
  178: leave_twice(Config) ->
  179:     #{node := Node2} = RPCSpec2 = mim2(),
  180:     join_cluster(atom_to_binary(Node2), Config),
  181:     get_ok_value([], leave_cluster(Config)),
  182:     distributed_helper:verify_result(RPCSpec2, remove),
  183:     ?assertEqual(<<"not_in_cluster">>, get_err_code(leave_cluster(Config))).
  184: 
  185: remove_dead_from_cluster(Config) ->
  186:     % given
  187:     Timeout = timer:seconds(60),
  188:     #{node := Node1Nodename} = Node1 = mim(),
  189:     #{node := _Node2Nodename} = Node2 = mim2(),
  190:     #{node := Node3Nodename} = Node3 = mim3(),
  191:     ok = rpc(Node2#{timeout => Timeout}, mongoose_cluster, join, [Node1Nodename]),
  192:     ok = rpc(Node3#{timeout => Timeout}, mongoose_cluster, join, [Node1Nodename]),
  193:     %% when
  194:     distributed_helper:stop_node(Node3Nodename, Config),
  195:     get_ok_value([data, server, removeFromCluster],
  196:                   remove_from_cluster(atom_to_binary(Node3Nodename), Config)),
  197:     %% then
  198:     % node is down hence its not in mnesia cluster
  199:     have_node_in_mnesia_wait(Node1, Node2, true),
  200:     have_node_in_mnesia_wait(Node1, Node3, false),
  201:     have_node_in_mnesia_wait(Node2, Node3, false),
  202:     % after node awakening nodes are clustered again
  203:     distributed_helper:start_node(Node3Nodename, Config),
  204:     have_node_in_mnesia_wait(Node1, Node3, true),
  205:     have_node_in_mnesia_wait(Node2, Node3, true).
  206: 
  207: remove_alive_from_cluster(Config) ->
  208:     % given
  209:     Timeout = timer:seconds(60),
  210:     #{node := Node1Name} = Node1 = mim(),
  211:     #{node := Node2Name} = Node2 = mim2(),
  212:     Node3 = mim3(),
  213:     ok = rpc(Node2#{timeout => Timeout}, mongoose_cluster, join, [Node1Name]),
  214:     ok = rpc(Node3#{timeout => Timeout}, mongoose_cluster, join, [Node1Name]),
  215:     %% when
  216:     %% Node2 is still running
  217:     %% then
  218:     get_ok_value([], remove_from_cluster(atom_to_binary(Node2Name), Config)),
  219:     have_node_in_mnesia(Node1, Node3, true),
  220:     have_node_in_mnesia(Node1, Node2, false),
  221:     have_node_in_mnesia(Node3, Node2, false).
  222: 
  223: remove_node_test(Config) ->
  224:     #{node := NodeName} = mim3(),
  225:     Value = get_ok_value([data, server, removeNode], remove_node(NodeName, Config)),
  226:     ?assertEqual(<<"MongooseIM node removed from the Mnesia schema">>, Value).
  227: 
  228: stop_node_test(Config) ->
  229:     #{node := Node3Nodename} = mim3(),
  230:     get_ok_value([data, server, stop], stop_node(Node3Nodename, Config)),
  231:     Timeout = timer:seconds(3),
  232:     F = fun() -> rpc:call(Node3Nodename, application, which_applications, [], Timeout) end,
  233:     mongoose_helper:wait_until(F, {badrpc, nodedown}, #{sleep_time => 1000, name => stop_node}),
  234:     distributed_helper:start_node(Node3Nodename, Config).
  235: 
  236: join_successful_http(Config) ->
  237:     #{node := Node2} = RPCSpec2 = mim2(),
  238:     leave_cluster(Config),
  239:     distributed_helper:verify_result(RPCSpec2, remove),
  240:     get_ok_value([], join_cluster(atom_to_binary(Node2), Config)),
  241:     distributed_helper:verify_result(RPCSpec2, add).
  242: 
  243: leave_successful_http(Config) ->
  244:     #{node := Node2} = RPCSpec2 = mim2(),
  245:     join_cluster(atom_to_binary(Node2), Config),
  246:     distributed_helper:verify_result(RPCSpec2, add),
  247:     get_ok_value([], leave_cluster(Config)),
  248:     distributed_helper:verify_result(RPCSpec2, remove).
  249: 
  250: remove_dead_from_cluster_http(Config) ->
  251:     % given
  252:     Timeout = timer:seconds(60),
  253:     #{node := Node1Nodename} = Node1 = mim(),
  254:     #{node := _Node2Nodename} = Node2 = mim2(),
  255:     #{node := Node3Nodename} = Node3 = mim3(),
  256:     ok = rpc(Node2#{timeout => Timeout}, mongoose_cluster, join, [Node1Nodename]),
  257:     ok = rpc(Node3#{timeout => Timeout}, mongoose_cluster, join, [Node1Nodename]),
  258:     %% when
  259:     distributed_helper:stop_node(Node3Nodename, Config),
  260:     F2 = fun() ->
  261:         test == rpc(Node1#{timeout => Timeout}, mongoose_config, get_opt, [listen, test])
  262:     end,
  263:     mongoose_helper:wait_until(F2, false, #{sleep_time => 200, name => wait_for_mim1,
  264:                                             time_left => timer:seconds(20)}),
  265:     get_ok_value([data, server, removeFromCluster],
  266:                   remove_from_cluster(atom_to_binary(Node3Nodename), Config)),
  267:     have_node_in_mnesia_wait(Node1, Node2, true),
  268:     have_node_in_mnesia_wait(Node1, Node3, false),
  269:     have_node_in_mnesia_wait(Node2, Node3, false),
  270:     % after node awakening nodes are clustered again
  271:     distributed_helper:start_node(Node3Nodename, Config),
  272:     ensure_node_started(Node3),
  273:     have_node_in_mnesia_wait(Node1, Node3, true),
  274:     have_node_in_mnesia_wait(Node2, Node3, true).
  275: 
  276: remove_alive_from_cluster_http(Config) ->
  277:     % given
  278:     Timeout = timer:seconds(60),
  279:     #{node := Node1Name} = Node1 = mim(),
  280:     #{node := Node2Name} = Node2 = mim2(),
  281:     Node3 = mim3(),
  282:     ok = rpc(Node2#{timeout => Timeout}, mongoose_cluster, join, [Node1Name]),
  283:     ok = rpc(Node3#{timeout => Timeout}, mongoose_cluster, join, [Node1Name]),
  284:     %% when
  285:     %% Node2 is still running
  286:     %% then
  287:     get_ok_value([], remove_from_cluster(atom_to_binary(Node2Name), Config)),
  288:     have_node_in_mnesia_wait(Node1, Node3, true),
  289:     have_node_in_mnesia_wait(Node1, Node2, false),
  290:     have_node_in_mnesia_wait(Node3, Node2, false).
  291: 
  292: ensure_node_started(Node) ->
  293:     Timeout = timer:seconds(60),
  294:     F = fun() ->
  295:         case rpc(Node#{timeout => Timeout}, mongoose_server_api, status, []) of
  296:             {ok, {true, _, _, _}} -> true;
  297:             _Other -> false
  298:         end
  299:     end,
  300:     mongoose_helper:wait_until(F, true, #{sleep_time => 200, name => wait_for_start_mim3,
  301:                                           time_left => timer:seconds(20)}).
  302: 
  303: %-----------------------------------------------------------------------
  304: %                                Helpers
  305: %-----------------------------------------------------------------------
  306: 
  307: have_node_in_mnesia_wait(Node1, #{node := Node2}, Value) ->
  308:     mongoose_helper:wait_until(fun() ->
  309:                                    DbNodes1 = distributed_helper:rpc(Node1, mnesia,
  310:                                                                      system_info, [db_nodes]),
  311:                                    lists:member(Node2, DbNodes1)
  312:                                end,
  313:                                Value,
  314:                                #{
  315:                                  time_left => timer:seconds(12),
  316:                                  sleep_time => 200,
  317:                                  name => have_node_in_mnesia
  318:                                 }).
  319: 
  320: all_log_levels() ->
  321:     [<<"NONE">>,
  322:      <<"EMERGENCY">>,
  323:      <<"ALERT">>,
  324:      <<"CRITICAL">>,
  325:      <<"ERROR">>,
  326:      <<"WARNING">>,
  327:      <<"NOTICE">>,
  328:      <<"INFO">>,
  329:      <<"DEBUG">>,
  330:      <<"ALL">>].
  331: 
  332: have_node_in_mnesia(Node1, #{node := Node2}, ShouldBe) ->
  333:     DbNodes1 = distributed_helper:rpc(Node1, mnesia, system_info, [db_nodes]),
  334:     ?assertEqual(ShouldBe, lists:member(Node2, DbNodes1)).
  335: 
  336: get_cookie(Config) ->
  337:     execute_command(<<"server">>, <<"getCookie">>, #{}, Config).
  338: 
  339: get_loglevel(Config) ->
  340:     execute_command(<<"server">>, <<"getLoglevel">>, #{}, Config).
  341: 
  342: set_loglevel(LogLevel, Config) ->
  343:     execute_command(<<"server">>, <<"setLoglevel">>, #{<<"level">> => LogLevel}, Config).
  344: 
  345: get_status(Config) ->
  346:     execute_command(<<"server">>, <<"status">>, #{}, Config).
  347: 
  348: get_status(Node, Config) ->
  349:     execute_command(Node, <<"server">>, <<"status">>, #{}, Config).
  350: 
  351: join_cluster(Node, Config) ->
  352:     execute_command(<<"server">>, <<"joinCluster">>, #{<<"node">> => Node}, Config).
  353: 
  354: leave_cluster(Config) ->
  355:     execute_command(<<"server">>, <<"leaveCluster">>, #{}, Config).
  356: 
  357: remove_from_cluster(Node, Config) ->
  358:     execute_command(<<"server">>, <<"removeFromCluster">>, #{<<"node">> => Node}, Config).
  359: 
  360: stop_node(Node, Config) ->
  361:     execute_command(Node, <<"server">>, <<"stop">>, #{}, Config).
  362: 
  363: remove_node(Node, Config) ->
  364:     execute_command(Node, <<"server">>, <<"removeNode">>, #{<<"node">> => Node}, Config).