1: -module(mongoose_rdbms_SUITE). 2: -include_lib("eunit/include/eunit.hrl"). 3: -include_lib("common_test/include/ct.hrl"). 4: -include("mongoose.hrl"). 5: 6: -compile([export_all, nowarn_export_all]). 7: 8: -define(_eq(E, I), ?_assertEqual(E, I)). 9: -define(eq(E, I), ?assertEqual(E, I)). 10: -define(ne(E, I), ?assert(E =/= I)). 11: 12: -define(KEEPALIVE_INTERVAL, 1). 13: -define(KEEPALIVE_QUERY, <<"SELECT 1;">>). 14: -define(MAX_INTERVAL, 30). 15: 16: all() -> 17: [{group, odbc}, 18: {group, mysql}, 19: {group, pgsql}]. 20: 21: init_per_suite(Config) -> 22: application:ensure_all_started(exometer_core), 23: Config. 24: 25: end_per_suite(_Config) -> 26: meck:unload(), 27: application:stop(exometer_core). 28: 29: groups() -> 30: [{odbc, [], tests()}, 31: {mysql, [], tests()}, 32: {pgsql, [], tests()}]. 33: 34: tests() -> 35: [keepalive_interval, 36: does_backoff_increase_to_a_point, 37: keepalive_exit]. 38: 39: init_per_group(odbc, Config) -> 40: case code:ensure_loaded(eodbc) of 41: {module, eodbc} -> 42: mongoose_backend:init(global, mongoose_rdbms, [], #{backend => odbc}), 43: [{db_type, odbc} | Config]; 44: _ -> 45: {skip, no_odbc_application} 46: end; 47: init_per_group(Group, Config) -> 48: mongoose_backend:init(global, mongoose_rdbms, [], #{backend => Group}), 49: [{db_type, Group} | Config]. 50: 51: end_per_group(_, Config) -> 52: % clean up after mongoose_backend:init 53: persistent_term:erase({backend_module, global, mongoose_rdbms}), 54: Config. 55: 56: init_per_testcase(does_backoff_increase_to_a_point, Config) -> 57: DbType = ?config(db_type, Config), 58: set_opts(), 59: meck_db(DbType), 60: meck_connection_error(DbType), 61: meck_rand(), 62: ServerOpts = server_opts(DbType), 63: [{db_opts, ServerOpts#{query_timeout => 5000, keepalive_interval => 2, max_start_interval => 10}} | Config]; 64: init_per_testcase(_, Config) -> 65: DbType = ?config(db_type, Config), 66: set_opts(), 67: meck_db(DbType), 68: ServerOpts = server_opts(DbType), 69: [{db_opts, ServerOpts#{query_timeout => 5000, keepalive_interval => ?KEEPALIVE_INTERVAL, 70: max_start_interval => ?MAX_INTERVAL}} | Config]. 71: 72: end_per_testcase(does_backoff_increase_to_a_point, Config) -> 73: meck_unload_rand(), 74: Db = ?config(db_type, Config), 75: meck_config_and_db_unload(Db), 76: unset_opts(), 77: Config; 78: end_per_testcase(_, Config) -> 79: meck_config_and_db_unload(?config(db_type, Config)), 80: unset_opts(), 81: Config. 82: 83: %% Test cases 84: keepalive_interval(Config) -> 85: {ok, Pid} = gen_server:start(mongoose_rdbms, ?config(db_opts, Config), []), 86: timer:sleep(5500), 87: ?eq(5, query_calls(Config)), 88: true = erlang:exit(Pid, kill), 89: ok. 90: 91: keepalive_exit(Config) -> 92: {ok, Pid} = gen_server:start(mongoose_rdbms, ?config(db_opts, Config), []), 93: Monitor = erlang:monitor(process, Pid), 94: timer:sleep(3500), 95: ?eq(3, query_calls(Config)), 96: meck_error(?config(db_type, Config)), 97: receive 98: {'DOWN', Monitor, process, Pid, {keepalive_failed, _}} -> 99: ok 100: after 1500 -> 101: ct:fail(no_down_message) 102: end. 103: 104: %% 5 retries. Max retry 10. Initial retry 2. 105: %% We should get a sequence: 2 -> 4 -> 10 -> 10 -> 10. 106: does_backoff_increase_to_a_point(Config) -> 107: {error, _} = gen_server:start(mongoose_rdbms, ?config(db_opts, Config), []), 108: % We expect to have 2 at the beginning, then values up to 10 and 10 three times in total 109: receive_backoffs(2, 10, 3). 110: 111: receive_backoffs(InitialValue, MaxValue, MaxCount) -> 112: receive_backoffs(InitialValue, MaxValue, MaxCount, 0). 113: 114: receive_backoffs(ExpectedVal, MaxValue, MaxCountExpected, MaxCount) -> 115: receive 116: {backoff, MaxValue} when MaxCount =:= MaxCountExpected - 1 -> 117: ok; 118: {backoff, MaxValue} -> 119: receive_backoffs(MaxValue, MaxValue, MaxCountExpected, MaxCount + 1); 120: {backoff, ExpectedVal} -> 121: receive_backoffs(min(ExpectedVal * ExpectedVal, MaxValue), MaxValue, MaxCountExpected, MaxCount) 122: after 200 -> % Lower this 123: ct:fail(no_backoff) 124: end. 125: 126: %% Mocks 127: 128: meck_rand() -> 129: meck:new(rand, [unstick, no_link]), 130: Self = self(), 131: Fun = fun(Val) -> ct:log("sending backoff: ~p to pid: ~p~n", [Val, Self]), Self ! {backoff, Val}, 0 end, 132: meck:expect(rand, uniform, Fun). 133: 134: meck_unload_rand() -> 135: meck:unload(rand). 136: 137: set_opts() -> 138: mongoose_config:set_opts(opts()). 139: 140: unset_opts() -> 141: mongoose_config:erase_opts(). 142: 143: opts() -> 144: #{all_metrics_are_global => false, 145: max_fsm_queue => 1024}. 146: 147: meck_db(odbc) -> 148: meck:new(eodbc, [no_link]), 149: meck:expect(mongoose_rdbms_odbc, disconnect, fun(_) -> ok end), 150: meck:expect(eodbc, connect, fun(_, _) -> {ok, self()} end), 151: meck:expect(eodbc, sql_query, fun(_Ref, _Query, _Timeout) -> {selected, ["row"]} end); 152: meck_db(mysql) -> 153: meck:new(mysql, [no_link]), 154: meck:expect(mongoose_rdbms_mysql, disconnect, fun(_) -> ok end), 155: meck:expect(mysql, start_link, fun(_) -> {ok, self()} end), 156: meck:expect(mysql, query, fun(_Ref, _Query) -> {ok, [], []} end); 157: meck_db(pgsql) -> 158: meck:new(epgsql, [no_link]), 159: meck:expect(mongoose_rdbms_pgsql, disconnect, fun(_) -> ok end), 160: meck:expect(epgsql, connect, fun(_) -> {ok, self()} end), 161: meck:expect(epgsql, squery, 162: fun(_Ref, <<"SET", _/binary>>) -> {ok, 0}; 163: (_Ref, [<<"SET", _/binary>> | _]) -> {ok, 0}; 164: (_Ref, [<<"SELECT", _/binary>>]) -> {ok, [], [{1}]} end). 165: 166: meck_connection_error(pgsql) -> 167: meck:expect(epgsql, connect, fun(_) -> connection_error end); 168: meck_connection_error(odbc) -> 169: meck:expect(eodbc, connect, fun(_, _) -> connection_error end); 170: meck_connection_error(mysql) -> 171: meck:expect(mongoose_rdbms_mysql, connect, fun(_, _) -> {error, connection_error} end). 172: 173: 174: meck_error(odbc) -> 175: meck:expect(eodbc, sql_query, 176: fun(_Ref, _Query, _Timeout) -> 177: {error, "connection broken"} 178: end); 179: meck_error(mysql) -> 180: meck:expect(mysql, query, fun(_Ref, _Query) -> {error, {123, "", <<"connection broken">>}} end); 181: meck_error(pgsql) -> 182: meck:expect(epgsql, connect, fun(_) -> {ok, self()} end), 183: meck:expect(epgsql, squery, 184: fun(_Ref, _Query) -> {error, {error, 2, 3, 4, <<"connection broken">>, 5}} end). 185: 186: meck_config_and_db_unload(DbType) -> 187: do_meck_unload(DbType). 188: 189: do_meck_unload(odbc) -> 190: meck:unload(eodbc); 191: do_meck_unload(mysql) -> 192: meck:unload(mongoose_rdbms_mysql), 193: meck:unload(mysql); 194: do_meck_unload(pgsql) -> 195: meck:unload(epgsql). 196: 197: query_calls(Config) -> 198: DbType = ?config(db_type, Config), 199: {M, F} = mf(DbType), 200: meck:num_calls(M, F, a(DbType)). 201: 202: mf(odbc) -> 203: {eodbc, sql_query}; 204: mf(mysql) -> 205: {mysql, query}; 206: mf(pgsql) -> 207: {epgsql, squery}. 208: 209: a(odbc) -> 210: ['_', [?KEEPALIVE_QUERY], '_']; 211: a(mysql) -> 212: ['_', [?KEEPALIVE_QUERY]]; 213: a(pgsql) -> 214: ['_', [?KEEPALIVE_QUERY]]. 215: 216: server_opts(odbc) -> 217: #{driver => odbc, settings => "fake-connection-string"}; 218: server_opts(mysql) -> 219: #{driver => mysql, host => "fake-host", port => 3306, 220: database => "fake-db", username => "fake-user", password => "fake-pass"}; 221: server_opts(pgsql) -> 222: #{driver => pgsql, host => "fake-host", port => 5432, 223: database => "fake-db", username => "fake-user", password => "fake-pass"}.