1: -module(cets_disco_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -import(distributed_helper, [mim/0, mim2/0, rpc/4]).
    5: -include_lib("common_test/include/ct.hrl").
    6: 
    7: %%--------------------------------------------------------------------
    8: %% Suite configuration
    9: %%--------------------------------------------------------------------
   10: 
   11: all() ->
   12:     [{group, file}, {group, rdbms}].
   13: 
   14: groups() ->
   15:     [{file, [], file_cases()},
   16:      {rdbms, [], rdbms_cases()}].
   17: 
   18: file_cases() ->
   19:     [file_backend].
   20: 
   21: rdbms_cases() ->
   22:     [rdbms_backend,
   23:      rdbms_backend_supports_auto_cleaning].
   24: 
   25: suite() ->
   26:     distributed_helper:require_rpc_nodes([mim, mim2]) ++ escalus:suite().
   27: 
   28: %%--------------------------------------------------------------------
   29: %% Init & teardown
   30: %%--------------------------------------------------------------------
   31: init_per_suite(Config) ->
   32:     escalus:init_per_suite(Config).
   33: 
   34: end_per_suite(Config) ->
   35:     escalus:end_per_suite(Config).
   36: 
   37: init_per_group(rdbms, Config) ->
   38:     case not ct_helper:is_ct_running()
   39:          orelse mongoose_helper:is_rdbms_enabled(domain_helper:host_type()) of
   40:         false -> {skip, rdbms_or_ct_not_running};
   41:         true -> Config
   42:     end;
   43: init_per_group(_, Config) ->
   44:     Config.
   45: 
   46: end_per_group(_, Config) ->
   47:     Config.
   48: 
   49: init_per_testcase(rdbms_backend_supports_auto_cleaning = CaseName, Config) ->
   50:     mock_timestamp(mim(), month_ago()) ++
   51:         escalus:init_per_testcase(CaseName, Config);
   52: init_per_testcase(CaseName, Config) ->
   53:     escalus:init_per_testcase(CaseName, Config).
   54: 
   55: end_per_testcase(rdbms_backend_supports_auto_cleaning = CaseName, Config) ->
   56:     unmock_timestamp(mim()),
   57:     escalus:end_per_testcase(CaseName, Config);
   58: end_per_testcase(CaseName, Config) ->
   59:     escalus:end_per_testcase(CaseName, Config).
   60: 
   61: %%--------------------------------------------------------------------
   62: %% Test cases
   63: %%--------------------------------------------------------------------
   64: 
   65: file_backend(Config) ->
   66:     Path = filename:join(?config(mim_data_dir, Config), "nodes.txt"),
   67:     Opts = #{disco_file => Path},
   68:     State = rpc(mim(), cets_discovery_file, init, [Opts]),
   69:     {{ok, Nodes}, _} = rpc(mim(), cets_discovery_file, get_nodes, [State]),
   70:     ['node1@localhost', 'node2@otherhost'] = lists:sort(Nodes).
   71: 
   72: rdbms_backend(_Config) ->
   73:     CN = <<"big_test">>,
   74:     Opts1 = #{cluster_name => CN, node_name_to_insert => <<"test1">>},
   75:     Opts2 = #{cluster_name => CN, node_name_to_insert => <<"test2">>},
   76:     State1 = disco_init(mim(), Opts1),
   77:     disco_get_nodes(mim(), State1),
   78:     State2 = disco_init(mim2(), Opts2),
   79:     {{ok, Nodes}, State2_2} = disco_get_nodes(mim2(), State2),
   80:     %% "test2" node can see "test1"
   81:     true = lists:member(test1, Nodes),
   82:     {{ok, _}, State2_3} = disco_get_nodes(mim2(), State2_2),
   83:     %% Check that we follow the right code branch
   84:     #{last_query_info := #{already_registered := true}} = State2_3.
   85: 
   86: rdbms_backend_supports_auto_cleaning(Config) ->
   87:     ensure_mocked(Config),
   88:     CN = <<"big_test2">>,
   89:     Opts1 = #{cluster_name => CN, node_name_to_insert => <<"test1">>},
   90:     Opts2 = #{cluster_name => CN, node_name_to_insert => <<"test2">>},
   91:     %% test1 row is written with an old (mocked) timestamp
   92:     State1 = disco_init(mim(), Opts1),
   93:     {_, State1_2} = disco_get_nodes(mim(), State1),
   94:     {{ok, Nodes1}, State1_3} = disco_get_nodes(mim(), State1_2),
   95:     Timestamp = proplists:get_value(mocked_timestamp, Config),
   96:     #{last_query_info := #{timestamp := Timestamp}} = State1_3,
   97:     %% It is in DB
   98:     true = lists:member(test1, Nodes1),
   99:     %% test2 would clean test1 registration
  100:     %% We don't mock on mim2 node, so timestamps would differ
  101:     State2 = disco_init(mim2(), Opts2),
  102:     {{ok, Nodes2}, State2_2} = disco_get_nodes(mim2(), State2),
  103:     false = lists:member(test1, Nodes2),
  104:     #{last_query_info := #{run_cleaning_result := {removed, [test1]}}} = State2_2.
  105: 
  106: %%--------------------------------------------------------------------
  107: %% Helpers
  108: %%--------------------------------------------------------------------
  109: 
  110: disco_init(Node, Opts) ->
  111:     rpc(Node, mongoose_cets_discovery_rdbms, init, [Opts]).
  112: 
  113: disco_get_nodes(Node, State) ->
  114:     rpc(Node, mongoose_cets_discovery_rdbms, get_nodes, [State]).
  115: 
  116: timestamp() ->
  117:     os:system_time(second).
  118: 
  119: month_ago() ->
  120:     timestamp() - timer:hours(24 * 30) div 1000.
  121: 
  122: mock_timestamp(Node, Timestamp) ->
  123:     ok = rpc(Node, meck, new, [mongoose_rdbms_timestamp, [passthrough, no_link]]),
  124:     ok = rpc(Node, meck, expect, [mongoose_rdbms_timestamp, select, 0, Timestamp]),
  125:     %% Ensure that we mock
  126:     EnsureMocked = fun() ->
  127:         Timestamp = rpc(Node, mongoose_rdbms_timestamp, select, [])
  128:         end,
  129:     EnsureMocked(),
  130:     [{ensure_mocked, EnsureMocked}, {mocked_timestamp, Timestamp}].
  131: 
  132: ensure_mocked(Config) ->
  133:     EnsureMocked = proplists:get_value(ensure_mocked, Config),
  134:     EnsureMocked().
  135: 
  136: unmock_timestamp(Node) ->
  137:     ok = rpc(Node, meck, unload, [mongoose_rdbms_timestamp]).