Skip to content

Commit

Permalink
Adding backup and restore tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Janusz Jakubiec authored and Janusz Jakubiec committed Aug 12, 2022
1 parent 7302ae4 commit 095d5e3
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 47 deletions.
72 changes: 72 additions & 0 deletions big_tests/tests/graphql_mnesia_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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].
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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).

Expand All @@ -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() ++ "/"),
<<Path/binary, Filename/binary>>.
Expand All @@ -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() ->
Expand Down
10 changes: 10 additions & 0 deletions src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
Expand Down
96 changes: 49 additions & 47 deletions src/mnesia_api.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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) ->
Expand Down Expand Up @@ -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
%---------------------------------------------------------------------------------------------------
Expand All @@ -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 ->
Expand All @@ -81,16 +120,14 @@ 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}};
_ ->
{error, {cannot_dump, String}}
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) ->
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -206,21 +211,18 @@ 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(
fun() -> mnesia:match_object(T, W, read) end),
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} ->
Expand All @@ -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 ->
Expand Down

0 comments on commit 095d5e3

Please sign in to comment.