From 095d5e3c01091b0dfc4b3ec40138ed417f0ad425 Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Thu, 11 Aug 2022 10:50:58 +0200 Subject: [PATCH] Adding backup and restore tests --- big_tests/tests/graphql_mnesia_SUITE.erl | 72 ++++++++++++++ ...mongoose_graphql_mnesia_admin_mutation.erl | 10 ++ src/mnesia_api.erl | 96 ++++++++++--------- 3 files changed, 131 insertions(+), 47 deletions(-) diff --git a/big_tests/tests/graphql_mnesia_SUITE.erl b/big_tests/tests/graphql_mnesia_SUITE.erl index aa7f957f55..fe2b5a0d90 100644 --- a/big_tests/tests/graphql_mnesia_SUITE.erl +++ b/big_tests/tests/graphql_mnesia_SUITE.erl @@ -11,6 +11,7 @@ get_ok_value/2, get_err_code/1, get_err_value/2]). -record(mnesia_table_test, {key :: integer(), name :: binary()}). +-record(vcard, {us, vcard}). all() -> [{group, admin_mnesia_cli}, @@ -27,6 +28,10 @@ admin_mnesia_tests() -> dump_mnesia_table_no_table_error_test, dump_mnesia_test, dump_mnesia_file_error_test, + backup_and_restore_test, + backup_wrong_filename_test, + restore_no_file_test, + restore_wrong_file_format_test, install_fallback_error_test, install_fallback_test, set_master_test]. @@ -84,6 +89,39 @@ dump_mnesia_file_error_test(Config) -> Res = dump_mnesia(<<"">>, Config), ?assertEqual(<<"file_error">>, get_err_code(Res)). +backup_and_restore_test(Config) -> + Filename = <<"backup_restore_mnesia_test">>, + create_vcard_table(), + write_to_vcard(), + ?assert(is_record_in_a_vcard_table()), + Res = backup_mnesia(Filename, Config), + ParsedRes = get_ok_value([data, mnesia, backup], Res), + ?assertEqual(<<"Mnesia was successfully backuped">>, ParsedRes), + delete_record_from_table(), + ?assertEqual(false, is_record_in_a_vcard_table()), + Res2 = restore_mnesia(Filename, Config), + ParsedRes2 = get_ok_value([data, mnesia, restore], Res2), + ?assertEqual(<<"Mnesia was successfully restored">>, ParsedRes2), + ?assert(is_record_in_a_vcard_table()), + delete_record_from_table(), + delete_file(create_full_filename(Filename)). + +backup_wrong_filename_test(Config) -> + Res = backup_mnesia(<<"">>, Config), + ?assertEqual(<<"wrong_filename">>, get_err_code(Res)). + +restore_no_file_test(Config) -> + Res = restore_mnesia(<<"">>, Config), + ?assertEqual(<<"file_not_found">>, get_err_code(Res)). + +restore_wrong_file_format_test(Config) -> + Filename = <<"restore_error">>, + FileFullPath = create_full_filename(Filename), + create_file(FileFullPath), + Res = restore_mnesia(Filename, Config), + delete_file(Filename), + ?assertEqual(<<"not_a_log_file_error">>, get_err_code(Res)). + get_info_test(Config) -> Res = get_info(mnesia_info_keys(), Config), ?assertEqual(<<"bad_key_error">>, get_err_code(Res)), @@ -136,6 +174,12 @@ install_fallback(Node, Config) -> dump_mnesia(Path, Config) -> execute_command(<<"mnesia">>, <<"dump">>, #{path => Path}, Config). +backup_mnesia(Path, Config) -> + execute_command(<<"mnesia">>, <<"backup">>, #{path => Path}, Config). + +restore_mnesia(Path, Config) -> + execute_command(<<"mnesia">>, <<"restore">>, #{path => Path}, Config). + dump_mnesia_table(Path, Table, Config) -> execute_command(<<"mnesia">>, <<"dumpTable">>, #{path => Path, table => Table}, Config). @@ -148,6 +192,27 @@ create_mnesia_table_and_write(Attrs) -> rpc_call(mnesia, dirty_write, [mnesia_table_test, #mnesia_table_test{key = 1, name = <<"TEST">>}]). +create_vcard_table() -> + rpc_call(mnesia, delete_table, [vcard]), + rpc_call(mnesia, create_table, [vcard, [{disc_copies, [maps:get(node, mim())]}, + {attributes, record_info(fields, vcard)}]]). + +write_to_vcard() -> + rpc_call(mnesia, dirty_write, [vcard, #vcard{us = 1, vcard = <<"TEST">>}]). + +is_record_in_a_table() -> + Expected = [#mnesia_table_test{key = 1, name = <<"TEST">>}], + Record = rpc_call(mnesia, dirty_read, [mnesia_table_test, 1]), + Expected == Record. + +is_record_in_a_vcard_table() -> + Expected = [#vcard{us = 1, vcard = <<"TEST">>}], + Record = rpc_call(mnesia, dirty_read, [vcard, 1]), + Expected == Record. + +delete_record_from_table() -> + ok = rpc_call(mnesia, dirty_delete, [vcard, 1]). + create_full_filename(Filename) -> Path = list_to_binary(get_mim_cwd() ++ "/"), <>. @@ -158,6 +223,13 @@ delete_mnesia_table() -> check_created_file(FullPath, ExpectedInsides) -> {ok, FileInsides} = file:read_file(FullPath), ?assertMatch({_,_}, binary:match(FileInsides, ExpectedInsides)), + delete_file(FullPath). + +create_file(FullPath) -> + file:open(FullPath, [write]), + file:close(FullPath). + +delete_file(FullPath) -> file:delete(FullPath). mnesia_info_keys() -> diff --git a/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl index d63eaed71f..fd76c74ef1 100644 --- a/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl @@ -26,6 +26,16 @@ execute(_Ctx, mnesia, <<"dumpTable">>, #{<<"path">> := Path, <<"table">> := Tabl {ok, _} -> {ok, "Mnesia table successfully dumped"}; {error, Error} -> make_error(Error, #{path => Path, table => Table}) end; +execute(_Ctx, mnesia, <<"backup">>, #{<<"path">> := Path}) -> + case mnesia_api:backup_mnesia(binary_to_list(Path)) of + {ok, _} -> {ok, "Mnesia was successfully backuped"}; + {error, Error} -> make_error(Error, #{path => Path}) + end; +execute(_Ctx, mnesia, <<"restore">>, #{<<"path">> := Path}) -> + case mnesia_api:restore_mnesia(binary_to_list(Path)) of + {ok, _} -> {ok, "Mnesia was successfully restored"}; + {error, Error} -> make_error(Error, #{path => Path}) + end; execute(_Ctx, mnesia, <<"installFallback">>, #{<<"path">> := Path}) -> case mnesia_api:install_fallback_mnesia(Path) of {ok, _} -> {ok, "Fallback installed"}; diff --git a/src/mnesia_api.erl b/src/mnesia_api.erl index a5f0130a82..bdb6d4dc51 100644 --- a/src/mnesia_api.erl +++ b/src/mnesia_api.erl @@ -9,7 +9,7 @@ -type info_result() :: #{binary() => binary() | [binary()] | integer()}. -type info_error() :: {{internal_server_error | bad_key_error, binary()}, #{key => binary()}}. --type dump_error() :: mnesia_not_running | table_does_not_exist | file_error | cannot_dump. +-type dump_error() :: table_does_not_exist | file_error | cannot_dump. -spec mnesia_info(Keys::[binary()]) -> [info_result() | info_error()]. mnesia_info(Keys) -> @@ -38,16 +38,55 @@ mnesia_info(Keys) -> end end, [], Keys). --spec dump_mnesia(file:name()) -> {error, {dump_error(), io_lib:chars()}} | {'ok', []}. +-spec dump_mnesia(file:name()) -> {error, {dump_error(), io_lib:chars()}} | {ok, []}. dump_mnesia(Path) -> Tabs = get_local_tables(), dump_tables(Path, Tabs). --spec dump_table(file:name(), string()) -> {error, {dump_error(), io_lib:chars()}} | {'ok', []}. +-spec dump_table(file:name(), string()) -> {error, {dump_error(), io_lib:chars()}} | {ok, []}. dump_table(Path, STable) -> Table = list_to_atom(STable), dump_tables(Path, [Table]). +-spec backup_mnesia(file:name()) -> + {error, {wrong_filename | cannot_backup, io_lib:chars()}} | {ok, []}. +backup_mnesia(Path) -> + case mnesia:backup(Path) of + ok -> + {ok, ""}; + {error, {'EXIT', {error, enoent}}} -> + {error, {wrong_filename, io_lib:format("Wrong filename: ~p", [Path])}}; + {error, Reason} -> + String = io_lib:format("Can't store backup in ~p at node ~p: ~p", + [filename:absname(Path), node(), Reason]), + {error, {cannot_backup, String}} + end. + +-spec restore_mnesia(file:name()) -> {error, {cannot_restore, io_lib:chars()}} + | {error, {file_not_found, io_lib:chars()}} + | {error, {not_a_log_file_error, io_lib:chars()}} + | {ok, []} + | {error, {table_does_not_exist, io_lib:chars()}}. +restore_mnesia(Path) -> + ErrorString=lists:flatten( io_lib:format("Can't restore backup from ~p at node ~p: ", + [filename:absname(Path), node()])), + case mnesia_api:restore(Path) of + {atomic, _} -> + {ok, ""}; + {aborted, {no_exists, Table}} -> + String = io_lib:format("~sTable ~p does not exist.", [ErrorString, Table]), + {error, {table_does_not_exist, String}}; + {aborted, enoent} -> + String = ErrorString ++ "File not found.", + {error, {file_not_found, String}}; + {aborted, {not_a_log_file, Filename}} -> + String = "Wrong file " ++ Filename ++ " structure", + {error, {not_a_log_file_error, String}}; + {aborted, Reason} -> + String = io_lib:format("~s~p", [ErrorString, Reason]), + {error, {cannot_restore, String}} + end. + %--------------------------------------------------------------------------------------------------- % Helpers %--------------------------------------------------------------------------------------------------- @@ -68,7 +107,7 @@ convert_value(Value) when is_list(Value) -> convert_value(Value) -> list_to_binary(io_lib:format("~p", [Value])). --spec dump_tables(file:name(), list()) -> {error, {dump_error(), io_lib:chars()}} | {'ok', []}. +-spec dump_tables(file:name(), list()) -> {error, {dump_error(), io_lib:chars()}} | {ok, []}. dump_tables(File, Tabs) -> case dump_to_textfile(mnesia:system_info(is_running), Tabs, file:open(File, [write])) of ok -> @@ -81,8 +120,6 @@ dump_tables(File, Tabs) -> String = io_lib:format("Can't store dump in ~p at node ~p: ~p", [filename:absname(File), node(), Reason]), case Reason of - mnesia_not_running -> - {error, {cannot_dump, String}}; table_does_not_exist -> {error, {table_does_not_exist, String}}; _ -> @@ -90,7 +127,7 @@ dump_tables(File, Tabs) -> end end. --spec set_master(Node :: atom() | string()) -> {'error', io_lib:chars()} | {'ok', []}. +-spec set_master(Node :: atom() | string()) -> {error, io_lib:chars()} | {ok, []}. set_master("self") -> set_master(node()); set_master(NodeString) when is_list(NodeString) -> @@ -107,38 +144,6 @@ set_master(Node) when is_atom(Node) -> {error, String} end. --spec backup_mnesia(file:name()) -> {'cannot_backup', io_lib:chars()} | {'ok', []}. -backup_mnesia(Path) -> - case mnesia:backup(Path) of - ok -> - {ok, ""}; - {error, Reason} -> - String = io_lib:format("Can't store backup in ~p at node ~p: ~p", - [filename:absname(Path), node(), Reason]), - {cannot_backup, String} - end. - --spec restore_mnesia(file:name()) -> {'cannot_restore', io_lib:chars()} - | {'file_not_found', io_lib:chars()} - | {'ok', []} - | {'table_not_exists', io_lib:chars()}. -restore_mnesia(Path) -> - ErrorString=lists:flatten( io_lib:format("Can't restore backup from ~p at node ~p: ", - [filename:absname(Path), node()])), - case mnesia_api:restore(Path) of - {atomic, _} -> - {ok, ""}; - {aborted, {no_exists, Table}} -> - String = io_lib:format("~sTable ~p does not exist.", [ErrorString, Table]), - {table_not_exists, String}; - {aborted, enoent} -> - String = ErrorString ++ "File not found.", - {file_not_found, String}; - {aborted, Reason} -> - String = io_lib:format("~s~p", [ErrorString, Reason]), - {cannot_restore, String} - end. - %% @doc Mnesia database restore %% This function is called from ejabberd_ctl, ejabberd_web_admin and %% mod_configure/adhoc @@ -191,8 +196,8 @@ get_local_tables() -> Tabs. -spec dump_to_textfile(any(), any(), - {'error', atom()} | {'ok', pid() | {'file_descriptor', atom() | tuple(), _}} - ) -> 'ok' | {'error', atom()} | {file_error, atom()}. + {error, atom()} | {ok, pid() | {file_descriptor, atom() | tuple(), _}} + ) -> ok | {error, atom()} | {file_error, atom()}. dump_to_textfile(yes, Tabs, {ok, F}) -> try Defs = lists:map( @@ -206,13 +211,10 @@ dump_to_textfile(yes, Tabs, {ok, F}) -> catch _:_ -> {error, table_does_not_exist} end; -dump_to_textfile(_, _, {ok, F}) -> - file:close(F), - {error, mnesia_not_running}; dump_to_textfile(_, _, {error, Reason}) -> {file_error, Reason}. --spec dump_tab(pid(), atom()) -> 'ok'. +-spec dump_tab(pid(), atom()) -> ok. dump_tab(F, T) -> W = mnesia:table_info(T, wild_pattern), {atomic, All} = mnesia:transaction( @@ -220,7 +222,7 @@ dump_tab(F, T) -> lists:foreach( fun(Term) -> io:format(F, "~p.~n", [setelement(1, Term, T)]) end, All). --spec load_mnesia(file:name()) -> {'cannot_load', io_lib:chars()} | {'ok', []}. +-spec load_mnesia(file:name()) -> {cannot_load, io_lib:chars()} | {ok, []}. load_mnesia(Path) -> case mnesia:load_textfile(Path) of {atomic, ok} -> @@ -232,7 +234,7 @@ load_mnesia(Path) -> end. -spec install_fallback_mnesia(file:name()) -> - {'cannot_fallback', io_lib:chars()} | {'ok', []}. + {cannot_fallback, io_lib:chars()} | {ok, []}. install_fallback_mnesia(Path) -> case mnesia:install_fallback(Path) of ok ->