From 8aa08302904757bd27a02d7f97ef7c850c04a06e Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Fri, 22 Jul 2022 14:40:10 +0200 Subject: [PATCH 1/9] Adding mnesia_api and schemas --- priv/graphql/schemas/admin/admin_schema.gql | 2 + src/ejabberd_admin.erl | 257 +----------------- .../admin/mongoose_graphql_admin_mutation.erl | 4 +- ...mongoose_graphql_mnesia_admin_mutation.erl | 23 ++ src/graphql/mongoose_graphql.erl | 1 + src/mnesia_api.erl | 236 ++++++++++++++++ 6 files changed, 275 insertions(+), 248 deletions(-) create mode 100644 src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl create mode 100644 src/mnesia_api.erl diff --git a/priv/graphql/schemas/admin/admin_schema.gql b/priv/graphql/schemas/admin/admin_schema.gql index d4692de49f..dad6983064 100644 --- a/priv/graphql/schemas/admin/admin_schema.gql +++ b/priv/graphql/schemas/admin/admin_schema.gql @@ -71,4 +71,6 @@ type AdminMutation @protected{ offline: OfflineAdminMutation "OAUTH token management" token: TokenAdminMutation + "Mnesia management" + mnesia: MnesiaAdminMutation } diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index d64b5f8593..3d5947ff52 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -37,14 +37,6 @@ import_users/1, %% Purge DB delete_expired_messages/1, delete_old_messages/2, - %% Mnesia - set_master/1, - backup_mnesia/1, restore_mnesia/1, - dump_mnesia/1, dump_table/2, load_mnesia/1, - install_fallback_mnesia/1, - dump_to_textfile/1, dump_to_textfile/2, - mnesia_change_nodename/4, - restore/1, % Still used by some modules%% get_loglevel/0, join_cluster/1, leave_cluster/0, remove_from_cluster/1]). @@ -126,42 +118,44 @@ commands() -> desc = "Delete offline messages older than DAYS", module = ?MODULE, function = delete_old_messages, args = [{host, binary}, {days, integer}], result = {res, restuple}}, + #ejabberd_commands{name = set_master, tags = [mnesia], desc = "Set master node of the clustered Mnesia tables", longdesc = "If you provide as nodename \"self\", this " "node will be set as its own master.", - module = ?MODULE, function = set_master, + module = mnesia_api, function = set_master, args = [{nodename, string}], result = {res, restuple}}, #ejabberd_commands{name = mnesia_change_nodename, tags = [mnesia], desc = "Change the erlang node name in a backup file", - module = ?MODULE, function = mnesia_change_nodename, + module = mnesia_api, function = mnesia_change_nodename, args = [{oldnodename, string}, {newnodename, string}, {oldbackup, string}, {newbackup, string}], result = {res, restuple}}, #ejabberd_commands{name = backup, tags = [mnesia], desc = "Store the database to backup file (only Mnesia)", - module = ?MODULE, function = backup_mnesia, + module = mnesia_api, function = backup_mnesia, args = [{file, string}], result = {res, restuple}}, #ejabberd_commands{name = restore, tags = [mnesia], desc = "Restore the database from backup file (only Mnesia)", - module = ?MODULE, function = restore_mnesia, + module = mnesia_api, function = restore_mnesia, args = [{file, string}], result = {res, restuple}}, #ejabberd_commands{name = dump, tags = [mnesia], desc = "Dump the database to text file (only Mnesia)", - module = ?MODULE, function = dump_mnesia, + module = mnesia_api, function = dump_mnesia, args = [{file, string}], result = {res, restuple}}, #ejabberd_commands{name = dump_table, tags = [mnesia], desc = "Dump a table to text file (only Mnesia)", - module = ?MODULE, function = dump_table, + module = mnesia_api, function = dump_table, args = [{file, string}, {table, string}], result = {res, restuple}}, #ejabberd_commands{name = load, tags = [mnesia], desc = "Restore the database from text file (only Mnesia)", - module = ?MODULE, function = load_mnesia, + module = mnesia_api, function = load_mnesia, args = [{file, string}], result = {res, restuple}}, #ejabberd_commands{name = install_fallback, tags = [mnesia], desc = "Install the database from a fallback file (only Mnesia)", - module = ?MODULE, function = install_fallback_mnesia, + module = mnesia_api, function = install_fallback_mnesia, args = [{file, string}], result = {res, restuple}}, + #ejabberd_commands{name = join_cluster, tags = [server], desc = "Join the node to a cluster. Call it from the joining node. Use `-f` or `--force` flag to avoid question prompt and force join the node", @@ -426,234 +420,3 @@ delete_old_messages(Domain, Days) -> {ok, _} = Result -> Result; {_, Message} -> {error, Message} end. - -%%% -%%% Mnesia management -%%% - --spec set_master(Node :: atom() | string()) -> {'error', io_lib:chars()} | {'ok', []}. -set_master("self") -> - set_master(node()); -set_master(NodeString) when is_list(NodeString) -> - set_master(list_to_atom(NodeString)); -set_master(Node) when is_atom(Node) -> - case mnesia:set_master_nodes([Node]) of - ok -> - {ok, ""}; - {error, Reason} -> - String = io_lib:format("Can't set master node ~p at node ~p:~n~p", - [Node, node(), Reason]), - {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 ejabberd_admin: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 -restore(Path) -> - mnesia:restore(Path, [{keep_tables, keep_tables()}, - {default_op, skip_tables}]). - -%% @doc This function return a list of tables that should be kept from a -%% previous version backup. -%% Obsolete tables or tables created by module who are no longer used are not -%% restored and are ignored. --spec keep_tables() -> [atom()]. -keep_tables() -> - lists:flatten([acl, passwd, disco_publish, keep_modules_tables()]). - -%% @doc Returns the list of modules tables in use, according to the list of -%% actually loaded modules --spec keep_modules_tables() -> [[atom()]]. % list of lists -keep_modules_tables() -> - lists:map(fun(Module) -> module_tables(Module) end, - gen_mod:loaded_modules()). - -%% TODO: This mapping should probably be moved to a callback function in each module. -%% @doc Mapping between modules and their tables --spec module_tables(_) -> [atom()]. -module_tables(mod_announce) -> [motd, motd_users]; -module_tables(mod_irc) -> [irc_custom]; -module_tables(mod_last) -> [last_activity]; -module_tables(mod_muc) -> [muc_room, muc_registered]; -module_tables(mod_offline) -> [offline_msg]; -module_tables(mod_privacy) -> [privacy]; -module_tables(mod_private) -> [private_storage]; -module_tables(mod_pubsub) -> [pubsub_node]; -module_tables(mod_roster) -> [roster]; -module_tables(mod_shared_roster) -> [sr_group, sr_user]; -module_tables(mod_vcard) -> [vcard, vcard_search]; -module_tables(_Other) -> []. - --spec get_local_tables() -> [any()]. -get_local_tables() -> - Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)), - Tabs = lists:filter( - fun(T) -> - case mnesia:table_info(T, storage_type) of - disc_copies -> true; - disc_only_copies -> true; - _ -> false - end - end, Tabs1), - Tabs. - --spec dump_mnesia(file:name()) -> {'cannot_dump', io_lib:chars()} | {'ok', []}. -dump_mnesia(Path) -> - Tabs = get_local_tables(), - dump_tables(Path, Tabs). - --spec dump_table(file:name(), STable :: string()) -> - {'cannot_dump', io_lib:chars()} | {'ok', []}. -dump_table(Path, STable) -> - Table = list_to_atom(STable), - dump_tables(Path, [Table]). - --spec dump_tables(file:name(), Tables :: [atom()]) -> - {'cannot_dump', io_lib:chars()} | {'ok', []}. -dump_tables(Path, Tables) -> - case dump_to_textfile(Path, Tables) of - ok -> - {ok, ""}; - {error, Reason} -> - String = io_lib:format("Can't store dump in ~p at node ~p: ~p", - [filename:absname(Path), node(), Reason]), - {cannot_dump, String} - end. - --spec dump_to_textfile(file:name()) -> 'ok' | {'error', atom()}. -dump_to_textfile(File) -> - Tabs = get_local_tables(), - dump_to_textfile(File, Tabs). - --spec dump_to_textfile(file:name(), Tabs :: list()) -> 'ok' | {'error', atom()}. -dump_to_textfile(File, Tabs) -> - dump_to_textfile(mnesia:system_info(is_running), Tabs, file:open(File, [write])). - --spec dump_to_textfile(any(), - any(), - {'error', atom()} | {'ok', pid() | {'file_descriptor', atom() | tuple(), _}} - ) -> 'ok' | {'error', atom()}. -dump_to_textfile(yes, Tabs, {ok, F}) -> - Defs = lists:map( - fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)}, - {attributes, mnesia:table_info(T, attributes)}]} - end, - Tabs), - io:format(F, "~p.~n", [{tables, Defs}]), - lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs), - file:close(F); -dump_to_textfile(_, _, {ok, F}) -> - file:close(F), - {error, mnesia_not_running}; -dump_to_textfile(_, _, {error, Reason}) -> - {error, Reason}. - --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', []}. -load_mnesia(Path) -> - case mnesia:load_textfile(Path) of - {atomic, ok} -> - {ok, ""}; - {error, Reason} -> - String = io_lib:format("Can't load dump in ~p at node ~p: ~p", - [filename:absname(Path), node(), Reason]), - {cannot_load, String} - end. - --spec install_fallback_mnesia(file:name()) -> - {'cannot_fallback', io_lib:chars()} | {'ok', []}. -install_fallback_mnesia(Path) -> - case mnesia:install_fallback(Path) of - ok -> - {ok, ""}; - {error, Reason} -> - String = io_lib:format("Can't install fallback from ~p at node ~p: ~p", - [filename:absname(Path), node(), Reason]), - {cannot_fallback, String} - end. - --spec mnesia_change_nodename(string(), string(), _, _) -> {ok, _} | {error, _}. -mnesia_change_nodename(FromString, ToString, Source, Target) -> - From = list_to_atom(FromString), - To = list_to_atom(ToString), - Switch = - fun - (Node) when Node == From -> - io:format(" - Replacing nodename: '~p' with: '~p'~n", [From, To]), - To; - (Node) when Node == To -> - %% throw({error, already_exists}); - io:format(" - Node: '~p' will not be modified (it is already '~p')~n", [Node, To]), - Node; - (Node) -> - io:format(" - Node: '~p' will not be modified (it is not '~p')~n", [Node, From]), - Node - end, - Convert = - fun - ({schema, db_nodes, Nodes}, Acc) -> - io:format(" +++ db_nodes ~p~n", [Nodes]), - {[{schema, db_nodes, lists:map(Switch, Nodes)}], Acc}; - ({schema, version, Version}, Acc) -> - io:format(" +++ version: ~p~n", [Version]), - {[{schema, version, Version}], Acc}; - ({schema, cookie, Cookie}, Acc) -> - io:format(" +++ cookie: ~p~n", [Cookie]), - {[{schema, cookie, Cookie}], Acc}; - ({schema, Tab, CreateList}, Acc) -> - io:format("~n * Checking table: '~p'~n", [Tab]), - Keys = [ram_copies, disc_copies, disc_only_copies], - OptSwitch = - fun({Key, Val}) -> - case lists:member(Key, Keys) of - true -> - io:format(" + Checking key: '~p'~n", [Key]), - {Key, lists:map(Switch, Val)}; - false-> {Key, Val} - end - end, - Res = {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc}, - Res; - (Other, Acc) -> - {[Other], Acc} - end, - mnesia:traverse_backup(Source, Target, Convert, switched). diff --git a/src/graphql/admin/mongoose_graphql_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_admin_mutation.erl index eac6a0491f..e6fa2b2e19 100644 --- a/src/graphql/admin/mongoose_graphql_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_admin_mutation.erl @@ -32,4 +32,6 @@ execute(_Ctx, _Obj, <<"stanza">>, _Args) -> execute(_Ctx, _Obj, <<"vcard">>, _Args) -> {ok, vcard}; execute(_Ctx, _Obj, <<"token">>, _Args) -> - {ok, token}. + {ok, token}; +execute(_Ctx, _Obj, <<"mnesia">>, _Args) -> + {ok, mnesia}. diff --git a/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl new file mode 100644 index 0000000000..21f4566913 --- /dev/null +++ b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl @@ -0,0 +1,23 @@ +-module(mongoose_graphql_mnesia_admin_mutation). +-behaviour(mongoose_graphql). + +-export([execute/4]). + +-import(mongoose_graphql_helper, [make_error/2]). + +-ignore_xref([execute/4]). + +-include("../mongoose_graphql_types.hrl"). +-include("mongoose.hrl"). +-include("jlib.hrl"). + +execute(_Ctx, mnesia, <<"setMaster">>, #{<<"node">> := Node}) -> + case mnesia_api:set_master(Node) of + {ok, _} -> {ok, "Master node set"}; + {error, Reason} -> make_error({error, Reason}, #{node => Node}) + end; +execute(_Ctx, mnesia, <<"installFallback">>, #{<<"path">> := Path}) -> + case mnesia_api:install_fallback_mnesia(Path) of + {ok, _} -> {ok, "Fallback installed"}; + Error -> make_error(Error, #{path => Path}) + end. diff --git a/src/graphql/mongoose_graphql.erl b/src/graphql/mongoose_graphql.erl index cd32b26e19..8354274b5a 100644 --- a/src/graphql/mongoose_graphql.erl +++ b/src/graphql/mongoose_graphql.erl @@ -150,6 +150,7 @@ admin_mapping_rules() -> 'MUCAdminQuery' => mongoose_graphql_muc_admin_query, 'MUCLightAdminMutation' => mongoose_graphql_muc_light_admin_mutation, 'MUCLightAdminQuery' => mongoose_graphql_muc_light_admin_query, + 'MnesiaAdminMutation' => mongoose_graphql_mnesia_admin_mutation, 'OfflineAdminMutation' => mongoose_graphql_offline_admin_mutation, 'PrivateAdminMutation' => mongoose_graphql_private_admin_mutation, 'PrivateAdminQuery' => mongoose_graphql_private_admin_query, diff --git a/src/mnesia_api.erl b/src/mnesia_api.erl new file mode 100644 index 0000000000..674a04e221 --- /dev/null +++ b/src/mnesia_api.erl @@ -0,0 +1,236 @@ +-module(mnesia_api). + +-export([set_master/1, + backup_mnesia/1, restore_mnesia/1, + dump_mnesia/1, dump_table/2, load_mnesia/1, + install_fallback_mnesia/1, + dump_to_textfile/1, dump_to_textfile/2, + mnesia_change_nodename/4, + restore/1]). + +-spec set_master(Node :: atom() | string()) -> {'error', io_lib:chars()} | {'ok', []}. +set_master("self") -> + set_master(node()); +set_master(NodeString) when is_list(NodeString) -> + set_master(list_to_atom(NodeString)); +set_master(Node) when is_atom(Node) -> + case mnesia:set_master_nodes([Node]) of + ok -> + {ok, ""}; + {error, Reason} -> + String = io_lib:format("Can't set master node ~p at node ~p:~n~p", + [Node, node(), Reason]), + {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 +restore(Path) -> + mnesia:restore(Path, [{keep_tables, keep_tables()}, + {default_op, skip_tables}]). + +%% @doc This function return a list of tables that should be kept from a +%% previous version backup. +%% Obsolete tables or tables created by module who are no longer used are not +%% restored and are ignored. +-spec keep_tables() -> [atom()]. +keep_tables() -> + lists:flatten([acl, passwd, disco_publish, keep_modules_tables()]). + +%% @doc Returns the list of modules tables in use, according to the list of +%% actually loaded modules +-spec keep_modules_tables() -> [[atom()]]. % list of lists +keep_modules_tables() -> + lists:map(fun(Module) -> module_tables(Module) end, + gen_mod:loaded_modules()). + +%% TODO: This mapping should probably be moved to a callback function in each module. +%% @doc Mapping between modules and their tables +-spec module_tables(_) -> [atom()]. +module_tables(mod_announce) -> [motd, motd_users]; +module_tables(mod_irc) -> [irc_custom]; +module_tables(mod_last) -> [last_activity]; +module_tables(mod_muc) -> [muc_room, muc_registered]; +module_tables(mod_offline) -> [offline_msg]; +module_tables(mod_privacy) -> [privacy]; +module_tables(mod_private) -> [private_storage]; +module_tables(mod_pubsub) -> [pubsub_node]; +module_tables(mod_roster) -> [roster]; +module_tables(mod_shared_roster) -> [sr_group, sr_user]; +module_tables(mod_vcard) -> [vcard, vcard_search]; +module_tables(_Other) -> []. + +-spec get_local_tables() -> [any()]. +get_local_tables() -> + Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)), + Tabs = lists:filter( + fun(T) -> + case mnesia:table_info(T, storage_type) of + disc_copies -> true; + disc_only_copies -> true; + _ -> false + end + end, Tabs1), + Tabs. + +-spec dump_mnesia(file:name()) -> {'cannot_dump', io_lib:chars()} | {'ok', []}. +dump_mnesia(Path) -> + Tabs = get_local_tables(), + dump_tables(Path, Tabs). + +-spec dump_table(file:name(), STable :: string()) -> + {'cannot_dump', io_lib:chars()} | {'ok', []}. +dump_table(Path, STable) -> + Table = list_to_atom(STable), + dump_tables(Path, [Table]). + +-spec dump_tables(file:name(), Tables :: [atom()]) -> + {'cannot_dump', io_lib:chars()} | {'ok', []}. +dump_tables(Path, Tables) -> + case dump_to_textfile(Path, Tables) of + ok -> + {ok, ""}; + {error, Reason} -> + String = io_lib:format("Can't store dump in ~p at node ~p: ~p", + [filename:absname(Path), node(), Reason]), + {cannot_dump, String} + end. + +-spec dump_to_textfile(file:name()) -> 'ok' | {'error', atom()}. +dump_to_textfile(File) -> + Tabs = get_local_tables(), + dump_to_textfile(File, Tabs). + +-spec dump_to_textfile(file:name(), Tabs :: list()) -> 'ok' | {'error', atom()}. +dump_to_textfile(File, Tabs) -> + dump_to_textfile(mnesia:system_info(is_running), Tabs, file:open(File, [write])). + +-spec dump_to_textfile(any(), + any(), + {'error', atom()} | {'ok', pid() | {'file_descriptor', atom() | tuple(), _}} + ) -> 'ok' | {'error', atom()}. +dump_to_textfile(yes, Tabs, {ok, F}) -> + Defs = lists:map( + fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)}, + {attributes, mnesia:table_info(T, attributes)}]} + end, + Tabs), + io:format(F, "~p.~n", [{tables, Defs}]), + lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs), + file:close(F); +dump_to_textfile(_, _, {ok, F}) -> + file:close(F), + {error, mnesia_not_running}; +dump_to_textfile(_, _, {error, Reason}) -> + {error, Reason}. + +-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', []}. +load_mnesia(Path) -> + case mnesia:load_textfile(Path) of + {atomic, ok} -> + {ok, ""}; + {error, Reason} -> + String = io_lib:format("Can't load dump in ~p at node ~p: ~p", + [filename:absname(Path), node(), Reason]), + {cannot_load, String} + end. + +-spec install_fallback_mnesia(file:name()) -> + {'cannot_fallback', io_lib:chars()} | {'ok', []}. +install_fallback_mnesia(Path) -> + case mnesia:install_fallback(Path) of + ok -> + {ok, ""}; + {error, Reason} -> + String = io_lib:format("Can't install fallback from ~p at node ~p: ~p", + [filename:absname(Path), node(), Reason]), + {cannot_fallback, String} + end. + +-spec mnesia_change_nodename(string(), string(), _, _) -> {ok, _} | {error, _}. +mnesia_change_nodename(FromString, ToString, Source, Target) -> + From = list_to_atom(FromString), + To = list_to_atom(ToString), + Switch = + fun + (Node) when Node == From -> + io:format(" - Replacing nodename: '~p' with: '~p'~n", [From, To]), + To; + (Node) when Node == To -> + %% throw({error, already_exists}); + io:format(" - Node: '~p' will not be modified (it is already '~p')~n", [Node, To]), + Node; + (Node) -> + io:format(" - Node: '~p' will not be modified (it is not '~p')~n", [Node, From]), + Node + end, + Convert = + fun + ({schema, db_nodes, Nodes}, Acc) -> + io:format(" +++ db_nodes ~p~n", [Nodes]), + {[{schema, db_nodes, lists:map(Switch, Nodes)}], Acc}; + ({schema, version, Version}, Acc) -> + io:format(" +++ version: ~p~n", [Version]), + {[{schema, version, Version}], Acc}; + ({schema, cookie, Cookie}, Acc) -> + io:format(" +++ cookie: ~p~n", [Cookie]), + {[{schema, cookie, Cookie}], Acc}; + ({schema, Tab, CreateList}, Acc) -> + io:format("~n * Checking table: '~p'~n", [Tab]), + Keys = [ram_copies, disc_copies, disc_only_copies], + OptSwitch = + fun({Key, Val}) -> + case lists:member(Key, Keys) of + true -> + io:format(" + Checking key: '~p'~n", [Key]), + {Key, lists:map(Switch, Val)}; + false-> {Key, Val} + end + end, + Res = {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc}, + Res; + (Other, Acc) -> + {[Other], Acc} + end, + mnesia:traverse_backup(Source, Target, Convert, switched). From 2742c84bce07def347e5b08868aefde7b75316b0 Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Tue, 26 Jul 2022 08:38:13 +0200 Subject: [PATCH 2/9] adding mnesia schema and test suite --- priv/graphql/schemas/admin/mnesia.gql | 15 +++++++++++++ test/graphql_mnesia_SUITE.erl | 32 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 priv/graphql/schemas/admin/mnesia.gql create mode 100644 test/graphql_mnesia_SUITE.erl diff --git a/priv/graphql/schemas/admin/mnesia.gql b/priv/graphql/schemas/admin/mnesia.gql new file mode 100644 index 0000000000..5f9f18d366 --- /dev/null +++ b/priv/graphql/schemas/admin/mnesia.gql @@ -0,0 +1,15 @@ +""" +Allow admin to manage mnesia database +""" + +type MnesiaAdminMutation { + setMaster(node: String!): String + mnesiaChangeNodename(fromString: String!, toString: String!, + source: String!, target: String!): String + backup(path: String!): String + restore(path: String!): String + dump(path: String!): String + dumpTable(path: String!): String + load(path: String!): String + installFallback(path: String!): String +} diff --git a/test/graphql_mnesia_SUITE.erl b/test/graphql_mnesia_SUITE.erl new file mode 100644 index 0000000000..95eed51430 --- /dev/null +++ b/test/graphql_mnesia_SUITE.erl @@ -0,0 +1,32 @@ +-module(grapqql_mnesia_SUITE). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-compile([export_all, nowarn_export_all]). + +-define(OFFLINE_MSG_3_5_FIELDS, [us, timestamp, expire, from, to, packet]). + +all() -> + [{group, admin_mnesia}]. + +groups() -> + [{admin_mnesia, [], admin_mnesia_handler()}]. + +admin_mnesia_handler() -> + [set_master_test]. + +init_per_suite(C) -> + application:ensure_all_started(jid), + ok = mnesia:create_schema([node()]), + ok = mnesia:start(), + C. + +end_per_suite(_C) -> + mnesia:stop(), + mnesia:delete_schema([node()]). + +init_per_group(admin_mnesia, Config) -> + graphql_helper:init_admin_handler(Config). + +end_per_group(admin_mnesia, _Config) -> + escalus_fresh:clean(). From 1ae53ed953534443b5a9fcba765c0aed23164085 Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Wed, 27 Jul 2022 08:28:15 +0200 Subject: [PATCH 3/9] Adding token_suite --- big_tests/default.spec | 1 + big_tests/dynamic_domains.spec | 1 + big_tests/tests/graphql_mnesia_SUITE.erl | 50 ++++++++++++++++++++++++ test/graphql_mnesia_SUITE.erl | 32 --------------- 4 files changed, 52 insertions(+), 32 deletions(-) create mode 100644 big_tests/tests/graphql_mnesia_SUITE.erl delete mode 100644 test/graphql_mnesia_SUITE.erl diff --git a/big_tests/default.spec b/big_tests/default.spec index 1b52fabcc5..67881165cf 100644 --- a/big_tests/default.spec +++ b/big_tests/default.spec @@ -40,6 +40,7 @@ {suites, "tests", graphql_stats_SUITE}. {suites, "tests", graphql_gdpr_SUITE}. {suites, "tests", graphql_token_SUITE}. +{suites, "tests", graphql_mnesia_SUITE}. {suites, "tests", graphql_vcard_SUITE}. {suites, "tests", graphql_http_upload_SUITE}. {suites, "tests", graphql_metric_SUITE}. diff --git a/big_tests/dynamic_domains.spec b/big_tests/dynamic_domains.spec index 0053e488ca..a8ebfd80f4 100644 --- a/big_tests/dynamic_domains.spec +++ b/big_tests/dynamic_domains.spec @@ -57,6 +57,7 @@ {suites, "tests", graphql_stats_SUITE}. {suites, "tests", graphql_gdpr_SUITE}. {suites, "tests", graphql_token_SUITE}. +{suites, "tests", graphql_mnesia_SUITE}. {suites, "tests", graphql_http_upload_SUITE}. {suites, "tests", graphql_metric_SUITE}. diff --git a/big_tests/tests/graphql_mnesia_SUITE.erl b/big_tests/tests/graphql_mnesia_SUITE.erl new file mode 100644 index 0000000000..d6e12146f7 --- /dev/null +++ b/big_tests/tests/graphql_mnesia_SUITE.erl @@ -0,0 +1,50 @@ +-module(graphql_mnesia_SUITE). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-compile([export_all, nowarn_export_all]). + +-import(distributed_helper, [require_rpc_nodes/1, mim/0]). +-import(graphql_helper, [execute_command/4, execute_user_command/5, user_to_bin/1, + get_ok_value/2, get_err_code/1]). + +all() -> + [{group, admin_mnesia_http}, + {group, admin_mnesia_cli}]. + +groups() -> + [{admin_mnesia_http, [], admin_mnesia_handler()}, + {admin_mnesia_cli, [], admin_mnesia_handler()}]. + +admin_mnesia_handler() -> + [set_master_test]. + +init_per_suite(Config) -> + application:ensure_all_started(jid), + ok = mnesia:create_schema([node()]), + ok = mnesia:start(), + Config1 = escalus:init_per_suite(Config), + ejabberd_node_utils:init(mim(), Config1). + +end_per_suite(_C) -> + mnesia:stop(), + mnesia:delete_schema([node()]). + +init_per_group(admin_mnesia_http, Config) -> + graphql_helper:init_admin_handler(Config); +init_per_group(admin_mnesia_cli, Config) -> + graphql_helper:init_admin_cli(Config). + +end_per_group(_, _Config) -> + graphql_helper:clean(), + escalus_fresh:clean(). + +% Admin tests + +set_master_test(Config) -> + Res = set_master(mim(), Config), + ParsedRes = get_ok_value([data, mnesia, setMaster], Res), + ?assertEqual("ok", ParsedRes). + +set_master(Node, Config) -> + execute_command(<<"mnesia">>, <<"setMaster">>, #{node => Node}, Config). diff --git a/test/graphql_mnesia_SUITE.erl b/test/graphql_mnesia_SUITE.erl deleted file mode 100644 index 95eed51430..0000000000 --- a/test/graphql_mnesia_SUITE.erl +++ /dev/null @@ -1,32 +0,0 @@ --module(grapqql_mnesia_SUITE). --include_lib("eunit/include/eunit.hrl"). --include_lib("common_test/include/ct.hrl"). - --compile([export_all, nowarn_export_all]). - --define(OFFLINE_MSG_3_5_FIELDS, [us, timestamp, expire, from, to, packet]). - -all() -> - [{group, admin_mnesia}]. - -groups() -> - [{admin_mnesia, [], admin_mnesia_handler()}]. - -admin_mnesia_handler() -> - [set_master_test]. - -init_per_suite(C) -> - application:ensure_all_started(jid), - ok = mnesia:create_schema([node()]), - ok = mnesia:start(), - C. - -end_per_suite(_C) -> - mnesia:stop(), - mnesia:delete_schema([node()]). - -init_per_group(admin_mnesia, Config) -> - graphql_helper:init_admin_handler(Config). - -end_per_group(admin_mnesia, _Config) -> - escalus_fresh:clean(). From babf36ab594c0037774adf748ca61edd110a3548 Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Tue, 9 Aug 2022 13:59:02 +0200 Subject: [PATCH 4/9] Adding dump_table_tests --- big_tests/tests/graphql_mnesia_SUITE.erl | 216 +++++++++++++++++- priv/graphql/schemas/admin/admin_schema.gql | 2 + priv/graphql/schemas/admin/mnesia.gql | 23 +- src/ejabberd_admin.erl | 4 +- .../admin/mongoose_graphql_admin_query.erl | 2 + ...mongoose_graphql_mnesia_admin_mutation.erl | 10 + .../mongoose_graphql_mnesia_admin_query.erl | 21 ++ src/graphql/mongoose_graphql.erl | 1 + src/graphql/mongoose_graphql_errors.erl | 3 + src/graphql/mongoose_graphql_union.erl | 3 + src/mnesia_api.erl | 147 ++++++++---- 11 files changed, 371 insertions(+), 61 deletions(-) create mode 100644 src/graphql/admin/mongoose_graphql_mnesia_admin_query.erl diff --git a/big_tests/tests/graphql_mnesia_SUITE.erl b/big_tests/tests/graphql_mnesia_SUITE.erl index d6e12146f7..794f4ef634 100644 --- a/big_tests/tests/graphql_mnesia_SUITE.erl +++ b/big_tests/tests/graphql_mnesia_SUITE.erl @@ -4,20 +4,30 @@ -compile([export_all, nowarn_export_all]). --import(distributed_helper, [require_rpc_nodes/1, mim/0]). +-import(distributed_helper, [require_rpc_nodes/1, mim/0, mim2/0, rpc/4]). +-import(domain_helper, [host_type/1]). +-import(mongooseimctl_helper, [rpc_call/3]). -import(graphql_helper, [execute_command/4, execute_user_command/5, user_to_bin/1, - get_ok_value/2, get_err_code/1]). + get_ok_value/2, get_err_code/1, get_err_value/2]). + +-record(mnesia_table_test, {key :: integer(), name :: binary()}). all() -> - [{group, admin_mnesia_http}, - {group, admin_mnesia_cli}]. + [{group, admin_mnesia_cli}, + {group, admin_mnesia_http}]. groups() -> - [{admin_mnesia_http, [], admin_mnesia_handler()}, - {admin_mnesia_cli, [], admin_mnesia_handler()}]. + [{admin_mnesia_http, [], admin_mnesia_tests()}, + {admin_mnesia_cli, [], admin_mnesia_tests()}]. -admin_mnesia_handler() -> - [set_master_test]. +admin_mnesia_tests() -> + [get_info_test, + dump_mnesia_table_test, + dump_mnesia_table_file_error_test, + dump_mnesia_table_no_table_error_test, + install_fallback_error_test, + install_fallback_test, + set_master_test]. init_per_suite(Config) -> application:ensure_all_started(jid), @@ -41,10 +51,192 @@ end_per_group(_, _Config) -> % Admin tests +dump_mnesia_table_test(Config) -> + Filename = <<"dump_mnesia_table_test">>, + Path = list_to_binary(get_mim_cwd() ++ "/"), + FullPath = <>, + rpc_call(mnesia, delete_table, [mnesia_table_test]), + {atomic, ok} = rpc_call(mnesia, create_table, + [mnesia_table_test, [{attributes, record_info(fields, mnesia_table_test)}]]), + rpc_call(mnesia, dirty_write, + [mnesia_table_test, #mnesia_table_test{key = 1, name = <<"TEST">>}]), + Res = dump_mnesia_table(Filename, <<"mnesia_table_test">>, Config), + get_ok_value([data, mnesia, dumpTable], Res), + {atomic, ok} = rpc_call(mnesia, delete_table, [mnesia_table_test]), + {ok, FileInsides} = file:read_file(FullPath), + ?assertMatch({_,_}, binary:match(FileInsides, <<"{mnesia_table_test,1,<<\"TEST\">>}">>)), + file:delete(FullPath). + +dump_mnesia_table_file_error_test(Config) -> + Res = dump_mnesia_table(<<"">>, <<"vcard">>, Config), + ?assertEqual(<<"file_error">>, get_err_code(Res)). + +dump_mnesia_table_no_table_error_test(Config) -> + Res = dump_mnesia_table(<<"AA">>, <<"NON_EXISTING">>, Config), + ?assertEqual(<<"table_does_not_exist">>, get_err_code(Res)). + +get_info_test(Config) -> + Res = get_info(mnesia_info_keys(), Config), + ?assertEqual(<<"bad_key_error">>, get_err_code(Res)), + ParsedRes = get_err_value([data, mnesia, info], Res), + Map = mnesia_info_check(), + lists:foreach(fun + (#{<<"result">> := Element, <<"key">> := Key}) -> + Fun = maps:get(Key, Map), + ?assertEqual({true, Element, Key, Fun}, {?MODULE:Fun(Element), Element, Key, Fun}); + (null) -> + ok + end, ParsedRes). + +install_fallback_error_test(Config) -> + Res = install_fallback(#{<<"path">> => <<"AAAA">>}, Config), + ?assertEqual(<<"cannot_fallback">>, get_err_code(Res)). + +install_fallback_test(Config) -> + ok. +% {ok, Path} = file:get_cwd(), +% io:format("SDFSDFDSFDSFSDFDSFSDF"), +% io:format(Path), +% Res = install_fallback(#{<<"path">> => list_to_binary(Path)}, Config), +% ParsedRes = get_ok_value([data, mnesia, installFallback], Res), +% ?assertEqual(<<"FallbackInstalled">>, ParsedRes). + set_master_test(Config) -> - Res = set_master(mim(), Config), - ParsedRes = get_ok_value([data, mnesia, setMaster], Res), - ?assertEqual("ok", ParsedRes). + ok. +% catch distributed_helper:rpc(mim(), ejabberd_auth_internal, start, [host_type(mim1)]), +% catch distributed_helper:rpc(mim2(), ejabberd_auth_internal, start, [host_type(mim2)]), +% +% TableName = passwd, +% NodeList = rpc_call(mnesia, system_info, [running_db_nodes]), +% set_master(#{<<"node">> => <<"self">>}, Config), +% [MasterNode] = rpc_call(mnesia, table_info, [TableName, master_nodes]), +% true = lists:member(MasterNode, NodeList), +% RestNodesList = lists:delete(MasterNode, NodeList), +% OtherNode = hd(RestNodesList), +% set_master(#{<<"node">> => atom_to_binary(OtherNode)}, Config), +% [OtherNode] = rpc_call(mnesia, table_info, [TableName, master_nodes]), +% set_master(#{<<"node">> => <<"self">>}, Config), +% [MasterNode] = rpc_call(mnesia, table_info, [TableName, master_nodes]). + +get_info(Keys, Config) -> + execute_command(<<"mnesia">>, <<"info">>, #{keys => Keys}, Config). + +install_fallback(Node, Config) -> + execute_command(<<"mnesia">>, <<"installFallback">>, Node, Config). + +dump_mnesia(Path, Config) -> + execute_command(<<"mnesia">>, <<"dump">>, #{path => Path}, Config). + +dump_mnesia_table(Path, Table, Config) -> + execute_command(<<"mnesia">>, <<"dumpTable">>, #{path => Path, table => Table}, Config). set_master(Node, Config) -> - execute_command(<<"mnesia">>, <<"setMaster">>, #{node => Node}, Config). + execute_command(<<"mnesia">>, <<"setMaster">>, Node, Config). + +mnesia_info_keys() -> + [<<"all">>, + <<"AAAA">>, + <<"access_module">>, + <<"auto_repair">>, + <<"backend_types">>, + <<"backup_module">>, + <<"checkpoints">>, + <<"db_nodes">>, + <<"debug">>, + <<"directory">>, + <<"dump_log_load_regulation">>, + <<"dump_log_time_threshold">>, + <<"dump_log_update_in_place">>, + <<"dump_log_write_threshold">>, + <<"event_module">>, + <<"extra_db_nodes">>, + <<"fallback_activated">>, + <<"held_locks">>, + <<"ignore_fallback_at_startup">>, + <<"fallback_error_function">>, + <<"is_running">>, + <<"local_tables">>, + <<"lock_queue">>, + <<"log_version">>, + <<"master_node_tables">>, + <<"max_wait_for_decision">>, + <<"protocol_version">>, + <<"running_db_nodes">>, + <<"schema_location">>, + <<"schema_version">>, + <<"subscribers">>, + <<"tables">>, + <<"transaction_commits">>, + <<"transaction_failures">>, + <<"transaction_log_writes">>, + <<"transaction_restarts">>, + <<"transactions">>, + <<"use_dir">>, + <<"core_dir">>, + <<"no_table_loaders">>, + <<"dc_dump_limit">>, + <<"send_compressed">>, + <<"max_transfer_size">>, + <<"version">>, + <<"db_nodes">>, + <<"running_db_nodes">>]. + +mnesia_info_check() -> + #{<<"access_module">> => check_binary, + <<"auto_repair">> => check_binary, + <<"backend_types">> => check_list, + <<"backup_module">> => check_binary, + <<"checkpoints">> => check_list, + <<"db_nodes">> => check_list, + <<"debug">> => check_binary, + <<"directory">> => check_binary, + <<"dump_log_load_regulation">> => check_binary, + <<"dump_log_time_threshold">> => check_integer, + <<"dump_log_update_in_place">> => check_binary, + <<"dump_log_write_threshold">> => check_integer, + <<"event_module">> => check_binary, + <<"extra_db_nodes">> => check_list, + <<"fallback_activated">> => check_binary, + <<"held_locks">> => check_list, + <<"ignore_fallback_at_startup">> => check_binary, + <<"fallback_error_function">> => check_binary, + <<"is_running">> => check_binary, + <<"local_tables">> => check_list, + <<"lock_queue">> => check_list, + <<"log_version">> => check_binary, + <<"master_node_tables">> => check_list, + <<"max_wait_for_decision">> => check_binary, + <<"protocol_version">> => check_binary, + <<"running_db_nodes">> => check_list, + <<"schema_location">> => check_binary, + <<"schema_version">> => check_binary, + <<"subscribers">> => check_list, + <<"tables">> => check_list, + <<"transaction_commits">> => check_integer, + <<"transaction_failures">> => check_integer, + <<"transaction_log_writes">> => check_integer, + <<"transaction_restarts">> => check_integer, + <<"transactions">> => check_list, + <<"use_dir">> => check_binary, + <<"core_dir">> => check_binary, + <<"no_table_loaders">> => check_integer, + <<"dc_dump_limit">> => check_integer, + <<"send_compressed">> => check_integer, + <<"max_transfer_size">> => check_integer, + <<"version">> => check_binary, + <<"db_nodes">> => check_list, + <<"running_db_nodes">> => check_list}. + +check_list([]) -> true; +check_list([Head | Tail]) when is_binary(Head) -> check_list(Tail); +check_list(_) -> false. + +check_binary(Value) when is_binary(Value) -> true; +check_binary(_) -> false. + +check_integer(Value) when is_integer(Value) -> true; +check_integer(_) -> false. + +get_mim_cwd() -> + {ok, Cwd} = rpc(mim(), file, get_cwd, []), + Cwd. diff --git a/priv/graphql/schemas/admin/admin_schema.gql b/priv/graphql/schemas/admin/admin_schema.gql index dad6983064..185e61b856 100644 --- a/priv/graphql/schemas/admin/admin_schema.gql +++ b/priv/graphql/schemas/admin/admin_schema.gql @@ -36,6 +36,8 @@ type AdminQuery{ stat: StatsAdminQuery "Personal data management according to GDPR" gdpr: GdprAdminQuery + "Mnesia management" + mnesia: MnesiaAdminQuery } """ diff --git a/priv/graphql/schemas/admin/mnesia.gql b/priv/graphql/schemas/admin/mnesia.gql index 5f9f18d366..b769512bf0 100644 --- a/priv/graphql/schemas/admin/mnesia.gql +++ b/priv/graphql/schemas/admin/mnesia.gql @@ -2,6 +2,10 @@ Allow admin to manage mnesia database """ +type MnesiaAdminQuery { + info(keys: [String!]!): [MnesiaInfo] +} + type MnesiaAdminMutation { setMaster(node: String!): String mnesiaChangeNodename(fromString: String!, toString: String!, @@ -9,7 +13,24 @@ type MnesiaAdminMutation { backup(path: String!): String restore(path: String!): String dump(path: String!): String - dumpTable(path: String!): String + dumpTable(path: String!, table: String!): String load(path: String!): String installFallback(path: String!): String } + +union MnesiaInfo = MnesiaStringResponse | MnesiaListResponse | MnesiaIntResponse + +type MnesiaStringResponse { + result: String + key: String +} + +type MnesiaListResponse { + result: [String] + key: String +} + +type MnesiaIntResponse { + result: Int + key: String +} diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 3d5947ff52..dcb9f2e398 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -45,11 +45,11 @@ -ignore_xref([ backup_mnesia/1, delete_expired_messages/1, delete_old_messages/2, - dump_mnesia/1, dump_table/2, dump_to_textfile/1, dump_to_textfile/2, + dump_mnesia/1, dump_table/2, get_loglevel/0, import_users/1, install_fallback_mnesia/1, join_cluster/1, leave_cluster/0, load_mnesia/1, mnesia_change_nodename/4, register/2, register/3, registered_users/1, remove_from_cluster/1, - restore_mnesia/1, set_master/1, status/0, + restore_mnesia/1, status/0, stop/0, unregister/2]). -include("mongoose.hrl"). diff --git a/src/graphql/admin/mongoose_graphql_admin_query.erl b/src/graphql/admin/mongoose_graphql_admin_query.erl index 311ded6412..4b22993e15 100644 --- a/src/graphql/admin/mongoose_graphql_admin_query.erl +++ b/src/graphql/admin/mongoose_graphql_admin_query.erl @@ -33,5 +33,7 @@ execute(_Ctx, _Obj, <<"stanza">>, _Args) -> {ok, #{}}; execute(_Ctx, _Obj, <<"vcard">>, _Args) -> {ok, vcard}; +execute(_Ctx, _Obj, <<"mnesia">>, _Args) -> + {ok, mnesia}; execute(_Ctx, _Obj, <<"metric">>, _Args) -> {ok, metric}. diff --git a/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl index 21f4566913..8f9c52740e 100644 --- a/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl @@ -16,6 +16,16 @@ execute(_Ctx, mnesia, <<"setMaster">>, #{<<"node">> := Node}) -> {ok, _} -> {ok, "Master node set"}; {error, Reason} -> make_error({error, Reason}, #{node => Node}) end; +execute(_Ctx, mnesia, <<"dump">>, #{<<"path">> := Path}) -> + case mnesia_api:dump_mnesia(Path) of + {ok, _} -> {ok, "Mnesia successfully dumped"}; + {error, Error} -> make_error(Error, #{path => Path}) + end; +execute(_Ctx, mnesia, <<"dumpTable">>, #{<<"path">> := Path, <<"table">> := Table}) -> + case mnesia_api:dump_table(binary_to_list(Path), binary_to_list(Table)) of + {ok, _} -> {ok, "Mnesia table successfully dumped"}; + {error, Error} -> make_error(Error, #{path => Path, table => Table}) + end; execute(_Ctx, mnesia, <<"installFallback">>, #{<<"path">> := Path}) -> case mnesia_api:install_fallback_mnesia(Path) of {ok, _} -> {ok, "Fallback installed"}; diff --git a/src/graphql/admin/mongoose_graphql_mnesia_admin_query.erl b/src/graphql/admin/mongoose_graphql_mnesia_admin_query.erl new file mode 100644 index 0000000000..2429821f00 --- /dev/null +++ b/src/graphql/admin/mongoose_graphql_mnesia_admin_query.erl @@ -0,0 +1,21 @@ +-module(mongoose_graphql_mnesia_admin_query). +-behaviour(mongoose_graphql). + +-export([execute/4]). + +-import(mongoose_graphql_helper, [make_error/2]). + +-ignore_xref([execute/4]). + +-include("../mongoose_graphql_types.hrl"). +-include("mongoose.hrl"). +-include("jlib.hrl"). + +execute(_Ctx, mnesia, <<"info">>, #{<<"keys">> := Keys}) -> + ResultList = mnesia_api:mnesia_info(Keys), + {ok, lists:foldl(fun + ({ok, _} = Result, Acc) -> + Acc ++ [Result]; + ({Error, Map}, Acc) -> + Acc ++ [make_error(Error, Map)] + end, [], ResultList)}. diff --git a/src/graphql/mongoose_graphql.erl b/src/graphql/mongoose_graphql.erl index 8354274b5a..87771d96ad 100644 --- a/src/graphql/mongoose_graphql.erl +++ b/src/graphql/mongoose_graphql.erl @@ -151,6 +151,7 @@ admin_mapping_rules() -> 'MUCLightAdminMutation' => mongoose_graphql_muc_light_admin_mutation, 'MUCLightAdminQuery' => mongoose_graphql_muc_light_admin_query, 'MnesiaAdminMutation' => mongoose_graphql_mnesia_admin_mutation, + 'MnesiaAdminQuery' => mongoose_graphql_mnesia_admin_query, 'OfflineAdminMutation' => mongoose_graphql_offline_admin_mutation, 'PrivateAdminMutation' => mongoose_graphql_private_admin_mutation, 'PrivateAdminQuery' => mongoose_graphql_private_admin_query, diff --git a/src/graphql/mongoose_graphql_errors.erl b/src/graphql/mongoose_graphql_errors.erl index d01b3d2c80..e659ed9816 100644 --- a/src/graphql/mongoose_graphql_errors.erl +++ b/src/graphql/mongoose_graphql_errors.erl @@ -45,6 +45,9 @@ err(_Ctx, ErrorTerm) -> -spec crash(map(), term()) -> err_msg(). crash(_Ctx, Err = #{type := Type}) -> ?LOG_ERROR(Err#{what => graphql_crash}), + io:format("--------------\n\n"), + io:format("~p\n", [Err]), + io:format("--------------\n\n"), #{message => <<"Unexpected ", Type/binary, " resolver crash">>, extensions => #{code => resolver_crash}}. diff --git a/src/graphql/mongoose_graphql_union.erl b/src/graphql/mongoose_graphql_union.erl index acce89eb71..ca894ed583 100644 --- a/src/graphql/mongoose_graphql_union.erl +++ b/src/graphql/mongoose_graphql_union.erl @@ -5,6 +5,9 @@ -include("mongoose_logger.hrl"). +execute(#{<<"result">> := Result}) when is_binary(Result) -> {ok, <<"MnesiaStringResponse">>}; +execute(#{<<"result">> := Result}) when is_list(Result) -> {ok, <<"MnesiaListResponse">>}; +execute(#{<<"result">> := Result}) when is_integer(Result) -> {ok, <<"MnesiaIntResponse">>}; execute(#{<<"type">> := _, <<"binValue">> := _}) -> {ok, <<"ImageData">>}; execute(#{<<"extValue">> := _}) -> {ok, <<"External">>}; execute(#{<<"phonetic">> := _}) -> {ok, <<"Phonetic">>}; diff --git a/src/mnesia_api.erl b/src/mnesia_api.erl index 674a04e221..cd3ff1582e 100644 --- a/src/mnesia_api.erl +++ b/src/mnesia_api.erl @@ -4,15 +4,99 @@ backup_mnesia/1, restore_mnesia/1, dump_mnesia/1, dump_table/2, load_mnesia/1, install_fallback_mnesia/1, - dump_to_textfile/1, dump_to_textfile/2, mnesia_change_nodename/4, - restore/1]). + restore/1, mnesia_info/1]). + +-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. + +-spec mnesia_info(Keys::[binary()]) -> [info_result() | info_error()]. +mnesia_info(Keys) -> + lists:foldl(fun + (<<"all">>, Acc) -> + try mnesia:system_info(all) of + Value -> + Acc ++ lists:foldl(fun({Key, Result}, AllAcc) -> + AllAcc ++ [{ok, #{<<"result">> => convert_value(Result), <<"key">> => Key}}] + end, [], Value) + catch + _:_ -> + Acc ++ [{{internal_server_error, <<"Internal server error">>}, + #{key => <<"all">>}}] + end; + (Key, Acc) -> + try mnesia:system_info(binary_to_atom(Key)) of + Value -> + Acc ++ [{ok, #{<<"result">> => convert_value(Value), <<"key">> => Key}}] + catch + _:{_, {badarg, _}} -> + Acc ++ [{{bad_key_error, <<"Key \"", Key/binary, "\" does not exist">>}, + #{key => Key}}]; + _:_ -> + Acc ++ [{{internal_server_error, <<"Internal server error">>}, #{key => Key}}] + end + end, [], Keys). + +-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', []}. +dump_table(Path, STable) -> + Table = list_to_atom(STable), + dump_tables(Path, [Table]). + +%--------------------------------------------------------------------------------------------------- +% Helpers +%--------------------------------------------------------------------------------------------------- + +-spec convert_value(any()) -> binary() | [{ok, any()}] | integer(). +convert_value(Value) when is_binary(Value) -> + Value; +convert_value(Value) when is_integer(Value) -> + Value; +convert_value(Value) when is_atom(Value) -> + atom_to_binary(Value); +convert_value([Head | _] = Value) when is_integer(Head) -> + list_to_binary(Value); +convert_value(Value) when is_list(Value) -> + lists:foldl(fun(Val, Acc) -> + Acc ++ [{ok, convert_value(Val)}] + end, [], 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', []}. +dump_tables(File, Tabs) -> + case dump_to_textfile(mnesia:system_info(is_running), Tabs, file:open(File, [write])) of + ok -> + {ok, ""}; + {file_error, Reason} -> + String = io_lib:format("Can't store dump in ~p at node ~p: ~p", + [filename:absname(File), node(), Reason]), + {error, {file_error, String}}; + {error, Reason} -> + 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_not_exists -> + {error, {table_does_not_exist, String}}; + _ -> + {error, {cannot_dump, String}} + end + end. -spec set_master(Node :: atom() | string()) -> {'error', io_lib:chars()} | {'ok', []}. set_master("self") -> set_master(node()); set_master(NodeString) when is_list(NodeString) -> set_master(list_to_atom(NodeString)); +set_master(NodeString) when is_binary(NodeString) -> + set_master(binary_to_atom(NodeString)); set_master(Node) when is_atom(Node) -> case mnesia:set_master_nodes([Node]) of ok -> @@ -106,56 +190,27 @@ get_local_tables() -> end, Tabs1), Tabs. --spec dump_mnesia(file:name()) -> {'cannot_dump', io_lib:chars()} | {'ok', []}. -dump_mnesia(Path) -> - Tabs = get_local_tables(), - dump_tables(Path, Tabs). - --spec dump_table(file:name(), STable :: string()) -> - {'cannot_dump', io_lib:chars()} | {'ok', []}. -dump_table(Path, STable) -> - Table = list_to_atom(STable), - dump_tables(Path, [Table]). - --spec dump_tables(file:name(), Tables :: [atom()]) -> - {'cannot_dump', io_lib:chars()} | {'ok', []}. -dump_tables(Path, Tables) -> - case dump_to_textfile(Path, Tables) of - ok -> - {ok, ""}; - {error, Reason} -> - String = io_lib:format("Can't store dump in ~p at node ~p: ~p", - [filename:absname(Path), node(), Reason]), - {cannot_dump, String} - end. - --spec dump_to_textfile(file:name()) -> 'ok' | {'error', atom()}. -dump_to_textfile(File) -> - Tabs = get_local_tables(), - dump_to_textfile(File, Tabs). - --spec dump_to_textfile(file:name(), Tabs :: list()) -> 'ok' | {'error', atom()}. -dump_to_textfile(File, Tabs) -> - dump_to_textfile(mnesia:system_info(is_running), Tabs, file:open(File, [write])). - --spec dump_to_textfile(any(), - any(), +-spec dump_to_textfile(any(), any(), {'error', atom()} | {'ok', pid() | {'file_descriptor', atom() | tuple(), _}} - ) -> 'ok' | {'error', atom()}. + ) -> 'ok' | {'error', atom()} | {file_error, atom()}. dump_to_textfile(yes, Tabs, {ok, F}) -> - Defs = lists:map( - fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)}, - {attributes, mnesia:table_info(T, attributes)}]} - end, - Tabs), - io:format(F, "~p.~n", [{tables, Defs}]), - lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs), - file:close(F); + try + Defs = lists:map( + fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)}, + {attributes, mnesia:table_info(T, attributes)}]} + end, + Tabs), + io:format(F, "~p.~n", [{tables, Defs}]), + lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs), + file:close(F) + catch _:_ -> + {error, table_not_exists} + end; dump_to_textfile(_, _, {ok, F}) -> file:close(F), {error, mnesia_not_running}; dump_to_textfile(_, _, {error, Reason}) -> - {error, Reason}. + {file_error, Reason}. -spec dump_tab(pid(), atom()) -> 'ok'. dump_tab(F, T) -> From 88ab3e1bf6f1662a9e4664d2a3de004d5c6134ae Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Wed, 10 Aug 2022 08:10:23 +0200 Subject: [PATCH 5/9] Adding dump_mnesia tests --- big_tests/tests/graphql_mnesia_SUITE.erl | 55 ++++++++++++++----- ...mongoose_graphql_mnesia_admin_mutation.erl | 2 +- src/mnesia_api.erl | 4 +- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/big_tests/tests/graphql_mnesia_SUITE.erl b/big_tests/tests/graphql_mnesia_SUITE.erl index 794f4ef634..aa7f957f55 100644 --- a/big_tests/tests/graphql_mnesia_SUITE.erl +++ b/big_tests/tests/graphql_mnesia_SUITE.erl @@ -17,14 +17,16 @@ all() -> {group, admin_mnesia_http}]. groups() -> - [{admin_mnesia_http, [], admin_mnesia_tests()}, - {admin_mnesia_cli, [], admin_mnesia_tests()}]. + [{admin_mnesia_http, [sequence], admin_mnesia_tests()}, + {admin_mnesia_cli, [sequence], admin_mnesia_tests()}]. admin_mnesia_tests() -> [get_info_test, dump_mnesia_table_test, dump_mnesia_table_file_error_test, dump_mnesia_table_no_table_error_test, + dump_mnesia_test, + dump_mnesia_file_error_test, install_fallback_error_test, install_fallback_test, set_master_test]. @@ -53,19 +55,12 @@ end_per_group(_, _Config) -> dump_mnesia_table_test(Config) -> Filename = <<"dump_mnesia_table_test">>, - Path = list_to_binary(get_mim_cwd() ++ "/"), - FullPath = <>, - rpc_call(mnesia, delete_table, [mnesia_table_test]), - {atomic, ok} = rpc_call(mnesia, create_table, - [mnesia_table_test, [{attributes, record_info(fields, mnesia_table_test)}]]), - rpc_call(mnesia, dirty_write, - [mnesia_table_test, #mnesia_table_test{key = 1, name = <<"TEST">>}]), + create_mnesia_table_and_write([{attributes, record_info(fields, mnesia_table_test)}]), Res = dump_mnesia_table(Filename, <<"mnesia_table_test">>, Config), - get_ok_value([data, mnesia, dumpTable], Res), - {atomic, ok} = rpc_call(mnesia, delete_table, [mnesia_table_test]), - {ok, FileInsides} = file:read_file(FullPath), - ?assertMatch({_,_}, binary:match(FileInsides, <<"{mnesia_table_test,1,<<\"TEST\">>}">>)), - file:delete(FullPath). + ParsedRes = get_ok_value([data, mnesia, dumpTable], Res), + ?assertEqual(<<"Mnesia table successfully dumped">>, ParsedRes), + delete_mnesia_table(), + check_created_file(create_full_filename(Filename), <<"{mnesia_table_test,1,<<\"TEST\">>}">>). dump_mnesia_table_file_error_test(Config) -> Res = dump_mnesia_table(<<"">>, <<"vcard">>, Config), @@ -75,6 +70,20 @@ dump_mnesia_table_no_table_error_test(Config) -> Res = dump_mnesia_table(<<"AA">>, <<"NON_EXISTING">>, Config), ?assertEqual(<<"table_does_not_exist">>, get_err_code(Res)). +dump_mnesia_test(Config) -> + Filename = <<"dump_mnesia_test">>, + create_mnesia_table_and_write([{disc_copies, [maps:get(node, mim())]}, + {attributes, record_info(fields, mnesia_table_test)}]), + Res = dump_mnesia(Filename, Config), + ParsedRes = get_ok_value([data, mnesia, dump], Res), + ?assertEqual(<<"Mnesia successfully dumped">>, ParsedRes), + delete_mnesia_table(), + check_created_file(create_full_filename(Filename), <<"{mnesia_table_test,1,<<\"TEST\">>}">>). + +dump_mnesia_file_error_test(Config) -> + Res = dump_mnesia(<<"">>, Config), + ?assertEqual(<<"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)), @@ -133,6 +142,24 @@ dump_mnesia_table(Path, Table, Config) -> set_master(Node, Config) -> execute_command(<<"mnesia">>, <<"setMaster">>, Node, Config). +create_mnesia_table_and_write(Attrs) -> + rpc_call(mnesia, delete_table, [mnesia_table_test]), + {atomic, ok} = rpc_call(mnesia, create_table, [mnesia_table_test, Attrs]), + rpc_call(mnesia, dirty_write, + [mnesia_table_test, #mnesia_table_test{key = 1, name = <<"TEST">>}]). + +create_full_filename(Filename) -> + Path = list_to_binary(get_mim_cwd() ++ "/"), + <>. + +delete_mnesia_table() -> + {atomic, ok} = rpc_call(mnesia, delete_table, [mnesia_table_test]). + +check_created_file(FullPath, ExpectedInsides) -> + {ok, FileInsides} = file:read_file(FullPath), + ?assertMatch({_,_}, binary:match(FileInsides, ExpectedInsides)), + file:delete(FullPath). + mnesia_info_keys() -> [<<"all">>, <<"AAAA">>, diff --git a/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl index 8f9c52740e..d63eaed71f 100644 --- a/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl @@ -17,7 +17,7 @@ execute(_Ctx, mnesia, <<"setMaster">>, #{<<"node">> := Node}) -> {error, Reason} -> make_error({error, Reason}, #{node => Node}) end; execute(_Ctx, mnesia, <<"dump">>, #{<<"path">> := Path}) -> - case mnesia_api:dump_mnesia(Path) of + case mnesia_api:dump_mnesia(binary_to_list(Path)) of {ok, _} -> {ok, "Mnesia successfully dumped"}; {error, Error} -> make_error(Error, #{path => Path}) end; diff --git a/src/mnesia_api.erl b/src/mnesia_api.erl index cd3ff1582e..a5f0130a82 100644 --- a/src/mnesia_api.erl +++ b/src/mnesia_api.erl @@ -83,7 +83,7 @@ dump_tables(File, Tabs) -> case Reason of mnesia_not_running -> {error, {cannot_dump, String}}; - table_not_exists -> + table_does_not_exist -> {error, {table_does_not_exist, String}}; _ -> {error, {cannot_dump, String}} @@ -204,7 +204,7 @@ dump_to_textfile(yes, Tabs, {ok, F}) -> lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs), file:close(F) catch _:_ -> - {error, table_not_exists} + {error, table_does_not_exist} end; dump_to_textfile(_, _, {ok, F}) -> file:close(F), From 87df8959f0cf4947ee38fcf0078048022a6823dc Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Thu, 11 Aug 2022 10:50:58 +0200 Subject: [PATCH 6/9] 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 -> From 4001b814dd9e324e3cb3a73a969eddd255c417e5 Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Fri, 12 Aug 2022 14:12:23 +0200 Subject: [PATCH 7/9] Adding mnesia_api:load tests --- big_tests/tests/graphql_mnesia_SUITE.erl | 90 ++++++++++++++----- ...mongoose_graphql_mnesia_admin_mutation.erl | 21 +++-- src/mnesia_api.erl | 29 +++--- 3 files changed, 99 insertions(+), 41 deletions(-) diff --git a/big_tests/tests/graphql_mnesia_SUITE.erl b/big_tests/tests/graphql_mnesia_SUITE.erl index fe2b5a0d90..d8768720c9 100644 --- a/big_tests/tests/graphql_mnesia_SUITE.erl +++ b/big_tests/tests/graphql_mnesia_SUITE.erl @@ -22,8 +22,7 @@ groups() -> {admin_mnesia_cli, [sequence], admin_mnesia_tests()}]. admin_mnesia_tests() -> - [get_info_test, - dump_mnesia_table_test, + [dump_mnesia_table_test, dump_mnesia_table_file_error_test, dump_mnesia_table_no_table_error_test, dump_mnesia_test, @@ -32,6 +31,11 @@ admin_mnesia_tests() -> backup_wrong_filename_test, restore_no_file_test, restore_wrong_file_format_test, + load_mnesia_test, + load_mnesia_no_file_test, + load_mnesia_bad_file_test, + load_mnesia_bad_file2_test, + get_info_test, install_fallback_error_test, install_fallback_test, set_master_test]. @@ -122,6 +126,32 @@ restore_wrong_file_format_test(Config) -> delete_file(Filename), ?assertEqual(<<"not_a_log_file_error">>, get_err_code(Res)). +load_mnesia_test(Config) -> + Filename = <<"load_mnesia_test">>, + create_mnesia_table_and_write([{disc_copies, [maps:get(node, mim())]}, + {attributes, record_info(fields, mnesia_table_test)}]), + Res = dump_mnesia(Filename, Config), + ParsedRes = get_ok_value([data, mnesia, dump], Res), + ?assertEqual(<<"Mnesia successfully dumped">>, ParsedRes), + delete_mnesia_table(), + check_if_response_contains(load_mnesia(Filename, Config), <<"Mnesia was successfully loaded">>), + ?assert(is_record_in_a_table()), + delete_mnesia_table(). + +load_mnesia_bad_file_test(Config) -> + Filename = <<"EXISTING_BUT_EMPTY">>, + create_file(create_full_filename(Filename)), + check_if_response_contains(load_mnesia(Filename, Config), <<"bad_file_format">>). + +load_mnesia_bad_file2_test(Config) -> + Filename = <<"EXISTING_FILE">>, + create_and_write_file(create_full_filename(Filename)), + check_if_response_contains(load_mnesia(Filename, Config), <<"bad_file_format">>). + +load_mnesia_no_file_test(Config) -> + Filename = <<"NON_EXISTING">>, + check_if_response_contains(load_mnesia(Filename, Config), <<"file_not_found">>). + get_info_test(Config) -> Res = get_info(mnesia_info_keys(), Config), ?assertEqual(<<"bad_key_error">>, get_err_code(Res)), @@ -165,26 +195,9 @@ set_master_test(Config) -> % set_master(#{<<"node">> => <<"self">>}, Config), % [MasterNode] = rpc_call(mnesia, table_info, [TableName, master_nodes]). -get_info(Keys, Config) -> - execute_command(<<"mnesia">>, <<"info">>, #{keys => Keys}, Config). - -install_fallback(Node, Config) -> - execute_command(<<"mnesia">>, <<"installFallback">>, 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). - -set_master(Node, Config) -> - execute_command(<<"mnesia">>, <<"setMaster">>, Node, Config). +%-------------------------------------------------------------------------------------------------- +% Helpers +%-------------------------------------------------------------------------------------------------- create_mnesia_table_and_write(Attrs) -> rpc_call(mnesia, delete_table, [mnesia_table_test]), @@ -229,9 +242,42 @@ create_file(FullPath) -> file:open(FullPath, [write]), file:close(FullPath). +create_and_write_file(FullPath) -> + {ok, File} = file:open(FullPath, [write]), + io:format(File, "~s~n", ["TEST"]), + file:close(FullPath). + +check_if_response_contains(Response, String) -> + ParsedLoadRes = io_lib:format("~p", [Response]), + ?assertMatch({_,_}, binary:match(list_to_binary(ParsedLoadRes), String)). + delete_file(FullPath) -> file:delete(FullPath). +get_info(Keys, Config) -> + execute_command(<<"mnesia">>, <<"info">>, #{keys => Keys}, Config). + +install_fallback(Node, Config) -> + execute_command(<<"mnesia">>, <<"installFallback">>, 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). + +load_mnesia(Path, Config) -> + execute_command(<<"mnesia">>, <<"load">>, #{path => Path}, Config). + +set_master(Node, Config) -> + execute_command(<<"mnesia">>, <<"setMaster">>, Node, Config). + mnesia_info_keys() -> [<<"all">>, <<"AAAA">>, diff --git a/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl index fd76c74ef1..557fcd16e0 100644 --- a/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl @@ -16,6 +16,16 @@ execute(_Ctx, mnesia, <<"setMaster">>, #{<<"node">> := Node}) -> {ok, _} -> {ok, "Master node set"}; {error, Reason} -> make_error({error, Reason}, #{node => Node}) 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, <<"dump">>, #{<<"path">> := Path}) -> case mnesia_api:dump_mnesia(binary_to_list(Path)) of {ok, _} -> {ok, "Mnesia successfully dumped"}; @@ -26,14 +36,9 @@ 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"}; +execute(_Ctx, mnesia, <<"load">>, #{<<"path">> := Path}) -> + case mnesia_api:load_mnesia(binary_to_list(Path)) of + {ok, _} -> {ok, "Mnesia was successfully loaded"}; {error, Error} -> make_error(Error, #{path => Path}) end; execute(_Ctx, mnesia, <<"installFallback">>, #{<<"path">> := Path}) -> diff --git a/src/mnesia_api.erl b/src/mnesia_api.erl index bdb6d4dc51..222ab550ef 100644 --- a/src/mnesia_api.erl +++ b/src/mnesia_api.erl @@ -87,6 +87,24 @@ restore_mnesia(Path) -> {error, {cannot_restore, String}} end. +-spec load_mnesia(file:name()) -> + {error, {cannot_load | bad_file_format | file_not_found, io_lib:chars()}} | {ok, []}. +load_mnesia(Path) -> + case mnesia:load_textfile(Path) of + {atomic, ok} -> + {ok, ""}; + {error, bad_header} -> + {error, {bad_file_format, "File has wrong format"}}; + {error, read} -> + {error, {bad_file_format, "File has wrong format"}}; + {error, open} -> + {error, {file_not_found, "File was not found"}}; + {error, Reason} -> + String = io_lib:format("Can't load dump in ~p at node ~p: ~p", + [filename:absname(Path), node(), Reason]), + {error, {cannot_load, String}} + end. + %--------------------------------------------------------------------------------------------------- % Helpers %--------------------------------------------------------------------------------------------------- @@ -222,17 +240,6 @@ 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, []}. -load_mnesia(Path) -> - case mnesia:load_textfile(Path) of - {atomic, ok} -> - {ok, ""}; - {error, Reason} -> - String = io_lib:format("Can't load dump in ~p at node ~p: ~p", - [filename:absname(Path), node(), Reason]), - {cannot_load, String} - end. - -spec install_fallback_mnesia(file:name()) -> {cannot_fallback, io_lib:chars()} | {ok, []}. install_fallback_mnesia(Path) -> From 3496b04b07307b107094b30c93e41b99613d14f2 Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Thu, 18 Aug 2022 14:33:12 +0200 Subject: [PATCH 8/9] Adding change nodename tests --- big_tests/tests/graphql_mnesia_SUITE.erl | 72 +++++--- priv/graphql/schemas/admin/mnesia.gql | 2 +- ...mongoose_graphql_mnesia_admin_mutation.erl | 10 +- src/mnesia_api.erl | 165 ++++++++++-------- 4 files changed, 143 insertions(+), 106 deletions(-) diff --git a/big_tests/tests/graphql_mnesia_SUITE.erl b/big_tests/tests/graphql_mnesia_SUITE.erl index d8768720c9..9d16ae372e 100644 --- a/big_tests/tests/graphql_mnesia_SUITE.erl +++ b/big_tests/tests/graphql_mnesia_SUITE.erl @@ -35,9 +35,11 @@ admin_mnesia_tests() -> load_mnesia_no_file_test, load_mnesia_bad_file_test, load_mnesia_bad_file2_test, + change_nodename_test, + change_nodename_no_file_error_test, + change_nodename_bad_file_error_test, get_info_test, install_fallback_error_test, - install_fallback_test, set_master_test]. init_per_suite(Config) -> @@ -152,6 +154,37 @@ load_mnesia_no_file_test(Config) -> Filename = <<"NON_EXISTING">>, check_if_response_contains(load_mnesia(Filename, Config), <<"file_not_found">>). +change_nodename_test(Config) -> + Filename1 = <<"change_nodename_mnesia_test">>, + Filename2 = <<"change_nodename2_mnesia_test">>, + create_vcard_table(), + write_to_vcard(), + ?assert(is_record_in_a_vcard_table()), + Res = backup_mnesia(Filename1, Config), + ParsedRes = get_ok_value([data, mnesia, backup], Res), + ?assertEqual(<<"Mnesia was successfully backuped">>, ParsedRes), + ChangeFrom = <<"mongooseim@localhost">>, + ChangeTo = <<"change_nodename_test@localhost">>, + Value = change_nodename(ChangeFrom, ChangeTo, Filename1, Filename2, Config), + check_if_response_contains(Value, <<"Name of the node was successfully changed">>). + +change_nodename_no_file_error_test(Config) -> + Filename1 = <<"non_existing">>, + Filename2 = <<"change_nodename2_mnesia_test">>, + ChangeFrom = <<"mongooseim@localhost">>, + ChangeTo = <<"change_nodename_test@localhost">>, + Value = change_nodename(ChangeFrom, ChangeTo, Filename1, Filename2, Config), + ?assertEqual(<<"file_not_found">>, get_err_code(Value)). + +change_nodename_bad_file_error_test(Config) -> + Filename1 = <<"existing_but_having_wrong_structure">>, + Filename2 = <<"change_nodename2_mnesia_test">>, + create_and_write_file(create_full_filename(Filename1)), + ChangeFrom = <<"mongooseim@localhost">>, + ChangeTo = <<"change_nodename_test@localhost">>, + Value = change_nodename(ChangeFrom, ChangeTo, Filename1, Filename2, Config), + ?assertEqual(<<"bad_file_format">>, get_err_code(Value)). + get_info_test(Config) -> Res = get_info(mnesia_info_keys(), Config), ?assertEqual(<<"bad_key_error">>, get_err_code(Res)), @@ -166,34 +199,12 @@ get_info_test(Config) -> end, ParsedRes). install_fallback_error_test(Config) -> - Res = install_fallback(#{<<"path">> => <<"AAAA">>}, Config), + Res = install_fallback(<<"AAAA">>, Config), ?assertEqual(<<"cannot_fallback">>, get_err_code(Res)). -install_fallback_test(Config) -> - ok. -% {ok, Path} = file:get_cwd(), -% io:format("SDFSDFDSFDSFSDFDSFSDF"), -% io:format(Path), -% Res = install_fallback(#{<<"path">> => list_to_binary(Path)}, Config), -% ParsedRes = get_ok_value([data, mnesia, installFallback], Res), -% ?assertEqual(<<"FallbackInstalled">>, ParsedRes). - set_master_test(Config) -> - ok. -% catch distributed_helper:rpc(mim(), ejabberd_auth_internal, start, [host_type(mim1)]), -% catch distributed_helper:rpc(mim2(), ejabberd_auth_internal, start, [host_type(mim2)]), -% -% TableName = passwd, -% NodeList = rpc_call(mnesia, system_info, [running_db_nodes]), -% set_master(#{<<"node">> => <<"self">>}, Config), -% [MasterNode] = rpc_call(mnesia, table_info, [TableName, master_nodes]), -% true = lists:member(MasterNode, NodeList), -% RestNodesList = lists:delete(MasterNode, NodeList), -% OtherNode = hd(RestNodesList), -% set_master(#{<<"node">> => atom_to_binary(OtherNode)}, Config), -% [OtherNode] = rpc_call(mnesia, table_info, [TableName, master_nodes]), -% set_master(#{<<"node">> => <<"self">>}, Config), -% [MasterNode] = rpc_call(mnesia, table_info, [TableName, master_nodes]). + ParsedRes = get_ok_value([data, mnesia, setMaster], set_master(mim(), Config)), + ?assertEqual(<<"Master node set">>, ParsedRes). %-------------------------------------------------------------------------------------------------- % Helpers @@ -257,8 +268,8 @@ delete_file(FullPath) -> get_info(Keys, Config) -> execute_command(<<"mnesia">>, <<"info">>, #{keys => Keys}, Config). -install_fallback(Node, Config) -> - execute_command(<<"mnesia">>, <<"installFallback">>, Node, Config). +install_fallback(Path, Config) -> + execute_command(<<"mnesia">>, <<"installFallback">>, #{path => Path}, Config). dump_mnesia(Path, Config) -> execute_command(<<"mnesia">>, <<"dump">>, #{path => Path}, Config). @@ -275,6 +286,11 @@ dump_mnesia_table(Path, Table, Config) -> load_mnesia(Path, Config) -> execute_command(<<"mnesia">>, <<"load">>, #{path => Path}, Config). +change_nodename(ChangeFrom, ChangeTo, Source, Target, Config) -> + Vars = #{<<"fromString">> => ChangeFrom, <<"toString">> => ChangeTo, + <<"source">> => Source, <<"target">> => Target}, + execute_command(<<"mnesia">>, <<"changeNodename">>, Vars, Config). + set_master(Node, Config) -> execute_command(<<"mnesia">>, <<"setMaster">>, Node, Config). diff --git a/priv/graphql/schemas/admin/mnesia.gql b/priv/graphql/schemas/admin/mnesia.gql index b769512bf0..293bffb075 100644 --- a/priv/graphql/schemas/admin/mnesia.gql +++ b/priv/graphql/schemas/admin/mnesia.gql @@ -8,7 +8,7 @@ type MnesiaAdminQuery { type MnesiaAdminMutation { setMaster(node: String!): String - mnesiaChangeNodename(fromString: String!, toString: String!, + changeNodename(fromString: String!, toString: String!, source: String!, target: String!): String backup(path: String!): String restore(path: String!): String diff --git a/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl index 557fcd16e0..ad4f612b26 100644 --- a/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl +++ b/src/graphql/admin/mongoose_graphql_mnesia_admin_mutation.erl @@ -21,6 +21,14 @@ execute(_Ctx, mnesia, <<"backup">>, #{<<"path">> := Path}) -> {ok, _} -> {ok, "Mnesia was successfully backuped"}; {error, Error} -> make_error(Error, #{path => Path}) end; +execute(_Ctx, mnesia, <<"changeNodename">>, #{<<"fromString">> := FromString, + <<"toString">> := ToString, <<"source">> := Source, <<"target">> := Target}) -> + case mnesia_api:mnesia_change_nodename(binary_to_list(FromString), binary_to_list(ToString), + binary_to_list(Source), binary_to_list(Target)) of + {ok, _} -> {ok, "Name of the node was successfully changed"}; + {error, Error} -> make_error(Error, #{fromString => FromString, toString => ToString, + source => Source, target => Target}) + end; execute(_Ctx, mnesia, <<"restore">>, #{<<"path">> := Path}) -> case mnesia_api:restore_mnesia(binary_to_list(Path)) of {ok, _} -> {ok, "Mnesia was successfully restored"}; @@ -42,7 +50,7 @@ execute(_Ctx, mnesia, <<"load">>, #{<<"path">> := Path}) -> {error, Error} -> make_error(Error, #{path => Path}) end; execute(_Ctx, mnesia, <<"installFallback">>, #{<<"path">> := Path}) -> - case mnesia_api:install_fallback_mnesia(Path) of + case mnesia_api:install_fallback_mnesia(binary_to_list(Path)) of {ok, _} -> {ok, "Fallback installed"}; Error -> make_error(Error, #{path => Path}) end. diff --git a/src/mnesia_api.erl b/src/mnesia_api.erl index 222ab550ef..6f87345997 100644 --- a/src/mnesia_api.erl +++ b/src/mnesia_api.erl @@ -105,6 +105,95 @@ load_mnesia(Path) -> {error, {cannot_load, String}} end. +-spec mnesia_change_nodename(string(), string(), _, _) -> {ok, _} | {error, _}. +mnesia_change_nodename(FromString, ToString, Source, Target) -> + From = list_to_atom(FromString), + To = list_to_atom(ToString), + Switch = + fun + (Node) when Node == From -> + io:format(" - Replacing nodename: '~p' with: '~p'~n", [From, To]), + To; + (Node) when Node == To -> + %% throw({error, already_exists}); + io:format(" - Node: '~p' will not be modified (it is already '~p')~n", [Node, To]), + Node; + (Node) -> + io:format(" - Node: '~p' will not be modified (it is not '~p')~n", [Node, From]), + Node + end, + Convert = + fun + ({schema, db_nodes, Nodes}, Acc) -> + io:format(" +++ db_nodes ~p~n", [Nodes]), + {[{schema, db_nodes, lists:map(Switch, Nodes)}], Acc}; + ({schema, version, Version}, Acc) -> + io:format(" +++ version: ~p~n", [Version]), + {[{schema, version, Version}], Acc}; + ({schema, cookie, Cookie}, Acc) -> + io:format(" +++ cookie: ~p~n", [Cookie]), + {[{schema, cookie, Cookie}], Acc}; + ({schema, Tab, CreateList}, Acc) -> + io:format("~n * Checking table: '~p'~n", [Tab]), + Keys = [ram_copies, disc_copies, disc_only_copies], + OptSwitch = + fun({Key, Val}) -> + case lists:member(Key, Keys) of + true -> + io:format(" + Checking key: '~p'~n", [Key]), + {Key, lists:map(Switch, Val)}; + false-> {Key, Val} + end + end, + Res = {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc}, + Res; + (Other, Acc) -> + {[Other], Acc} + end, + case mnesia:traverse_backup(Source, Target, Convert, switched) of + {ok, _} = Result -> Result; + {error, Reason} -> + String = io_lib:format("Error while changing node's name ~p:~n~p", + [node(), Reason]), + case Reason of + {_, enoent} -> + {error, {file_not_found, String}}; + {_, {not_a_log_file, _}} -> + {error, {bad_file_format, String}}; + _ -> + {error, {error, String}} + end + end. + +-spec install_fallback_mnesia(file:name()) -> + {cannot_fallback, io_lib:chars()} | {ok, []}. +install_fallback_mnesia(Path) -> + case mnesia:install_fallback(Path) of + ok -> + {ok, ""}; + {error, Reason} -> + String = io_lib:format("Can't install fallback from ~p at node ~p: ~p", + [filename:absname(Path), node(), Reason]), + {cannot_fallback, String} + end. + +-spec set_master(Node :: atom() | string()) -> {error, io_lib:chars()} | {ok, []}. +set_master("self") -> + set_master(node()); +set_master(NodeString) when is_list(NodeString) -> + set_master(list_to_atom(NodeString)); +set_master(NodeString) when is_binary(NodeString) -> + set_master(binary_to_atom(NodeString)); +set_master(Node) when is_atom(Node) -> + case mnesia:set_master_nodes([Node]) of + ok -> + {ok, ""}; + {error, Reason} -> + String = io_lib:format("Can't set master node ~p at node ~p:~n~p", + [Node, node(), Reason]), + {error, String} + end. + %--------------------------------------------------------------------------------------------------- % Helpers %--------------------------------------------------------------------------------------------------- @@ -145,23 +234,6 @@ dump_tables(File, Tabs) -> end end. --spec set_master(Node :: atom() | string()) -> {error, io_lib:chars()} | {ok, []}. -set_master("self") -> - set_master(node()); -set_master(NodeString) when is_list(NodeString) -> - set_master(list_to_atom(NodeString)); -set_master(NodeString) when is_binary(NodeString) -> - set_master(binary_to_atom(NodeString)); -set_master(Node) when is_atom(Node) -> - case mnesia:set_master_nodes([Node]) of - ok -> - {ok, ""}; - {error, Reason} -> - String = io_lib:format("Can't set master node ~p at node ~p:~n~p", - [Node, node(), Reason]), - {error, String} - end. - %% @doc Mnesia database restore %% This function is called from ejabberd_ctl, ejabberd_web_admin and %% mod_configure/adhoc @@ -239,62 +311,3 @@ dump_tab(F, T) -> fun() -> mnesia:match_object(T, W, read) end), lists:foreach( fun(Term) -> io:format(F, "~p.~n", [setelement(1, Term, T)]) end, All). - --spec install_fallback_mnesia(file:name()) -> - {cannot_fallback, io_lib:chars()} | {ok, []}. -install_fallback_mnesia(Path) -> - case mnesia:install_fallback(Path) of - ok -> - {ok, ""}; - {error, Reason} -> - String = io_lib:format("Can't install fallback from ~p at node ~p: ~p", - [filename:absname(Path), node(), Reason]), - {cannot_fallback, String} - end. - --spec mnesia_change_nodename(string(), string(), _, _) -> {ok, _} | {error, _}. -mnesia_change_nodename(FromString, ToString, Source, Target) -> - From = list_to_atom(FromString), - To = list_to_atom(ToString), - Switch = - fun - (Node) when Node == From -> - io:format(" - Replacing nodename: '~p' with: '~p'~n", [From, To]), - To; - (Node) when Node == To -> - %% throw({error, already_exists}); - io:format(" - Node: '~p' will not be modified (it is already '~p')~n", [Node, To]), - Node; - (Node) -> - io:format(" - Node: '~p' will not be modified (it is not '~p')~n", [Node, From]), - Node - end, - Convert = - fun - ({schema, db_nodes, Nodes}, Acc) -> - io:format(" +++ db_nodes ~p~n", [Nodes]), - {[{schema, db_nodes, lists:map(Switch, Nodes)}], Acc}; - ({schema, version, Version}, Acc) -> - io:format(" +++ version: ~p~n", [Version]), - {[{schema, version, Version}], Acc}; - ({schema, cookie, Cookie}, Acc) -> - io:format(" +++ cookie: ~p~n", [Cookie]), - {[{schema, cookie, Cookie}], Acc}; - ({schema, Tab, CreateList}, Acc) -> - io:format("~n * Checking table: '~p'~n", [Tab]), - Keys = [ram_copies, disc_copies, disc_only_copies], - OptSwitch = - fun({Key, Val}) -> - case lists:member(Key, Keys) of - true -> - io:format(" + Checking key: '~p'~n", [Key]), - {Key, lists:map(Switch, Val)}; - false-> {Key, Val} - end - end, - Res = {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc}, - Res; - (Other, Acc) -> - {[Other], Acc} - end, - mnesia:traverse_backup(Source, Target, Convert, switched). From 148938cbdd7c6eeea5d630a41ef35078fef62028 Mon Sep 17 00:00:00 2001 From: Janusz Jakubiec Date: Thu, 18 Aug 2022 16:57:47 +0200 Subject: [PATCH 9/9] Adding domain admin tests --- big_tests/tests/graphql_mnesia_SUITE.erl | 84 ++++++++++++++++++++++-- priv/graphql/schemas/admin/mnesia.gql | 45 ++++++++----- src/graphql/mongoose_graphql_errors.erl | 3 - 3 files changed, 107 insertions(+), 25 deletions(-) diff --git a/big_tests/tests/graphql_mnesia_SUITE.erl b/big_tests/tests/graphql_mnesia_SUITE.erl index 9d16ae372e..e9f1c64f0e 100644 --- a/big_tests/tests/graphql_mnesia_SUITE.erl +++ b/big_tests/tests/graphql_mnesia_SUITE.erl @@ -8,18 +8,21 @@ -import(domain_helper, [host_type/1]). -import(mongooseimctl_helper, [rpc_call/3]). -import(graphql_helper, [execute_command/4, execute_user_command/5, user_to_bin/1, - get_ok_value/2, get_err_code/1, get_err_value/2]). + get_ok_value/2, get_err_code/1, get_err_value/2, + execute_domain_admin_command/4, get_unauthorized/1]). -record(mnesia_table_test, {key :: integer(), name :: binary()}). -record(vcard, {us, vcard}). all() -> [{group, admin_mnesia_cli}, - {group, admin_mnesia_http}]. + {group, admin_mnesia_http}, + {group, domain_admin_mnesia}]. groups() -> [{admin_mnesia_http, [sequence], admin_mnesia_tests()}, - {admin_mnesia_cli, [sequence], admin_mnesia_tests()}]. + {admin_mnesia_cli, [sequence], admin_mnesia_tests()}, + {domain_admin_mnesia, [], domain_admin_tests()}]. admin_mnesia_tests() -> [dump_mnesia_table_test, @@ -42,6 +45,17 @@ admin_mnesia_tests() -> install_fallback_error_test, set_master_test]. +domain_admin_tests() -> + [domain_admin_dump_mnesia_table_test, + domain_admin_dump_mnesia_test, + domain_admin_backup_test, + domain_admin_restore_test, + domain_admin_load_mnesia_test, + domain_admin_change_nodename_test, + domain_admin_install_fallback_test, + domain_admin_set_master_test, + domain_admin_get_info_test]. + init_per_suite(Config) -> application:ensure_all_started(jid), ok = mnesia:create_schema([node()]), @@ -56,7 +70,9 @@ end_per_suite(_C) -> init_per_group(admin_mnesia_http, Config) -> graphql_helper:init_admin_handler(Config); init_per_group(admin_mnesia_cli, Config) -> - graphql_helper:init_admin_cli(Config). + graphql_helper:init_admin_cli(Config); +init_per_group(domain_admin_mnesia, Config) -> + graphql_helper:init_domain_admin_handler(Config). end_per_group(_, _Config) -> graphql_helper:clean(), @@ -206,6 +222,36 @@ set_master_test(Config) -> ParsedRes = get_ok_value([data, mnesia, setMaster], set_master(mim(), Config)), ?assertEqual(<<"Master node set">>, ParsedRes). +% Domain admin tests + +domain_admin_dump_mnesia_table_test(Config) -> + get_unauthorized(domain_admin_dump_mnesia_table(<<"File">>, <<"mnesia_table_test">>, Config)). + +domain_admin_dump_mnesia_test(Config) -> + get_unauthorized(domain_admin_dump_mnesia(<<"File">>, Config)). + +domain_admin_backup_test(Config) -> + get_unauthorized(domain_admin_backup_mnesia(<<"Path">>, Config)). + +domain_admin_restore_test(Config) -> + get_unauthorized(domain_admin_restore_mnesia(<<"Path">>, Config)). + +domain_admin_load_mnesia_test(Config) -> + get_unauthorized(domain_admin_load_mnesia(<<"Path">>, Config)). + +domain_admin_change_nodename_test(Config) -> + get_unauthorized(domain_admin_change_nodename(<<"From">>, <<"To">>, <<"file1">>, + <<"file2">>, Config)). + +domain_admin_install_fallback_test(Config) -> + get_unauthorized(domain_admin_install_fallback(<<"Path">>, Config)). + +domain_admin_set_master_test(Config) -> + get_unauthorized(domain_admin_set_master(mim(), Config)). + +domain_admin_get_info_test(Config) -> + get_unauthorized(domain_admin_get_info([<<"all">>], Config)). + %-------------------------------------------------------------------------------------------------- % Helpers %-------------------------------------------------------------------------------------------------- @@ -294,6 +340,36 @@ change_nodename(ChangeFrom, ChangeTo, Source, Target, Config) -> set_master(Node, Config) -> execute_command(<<"mnesia">>, <<"setMaster">>, Node, Config). +domain_admin_get_info(Keys, Config) -> + execute_domain_admin_command(<<"mnesia">>, <<"info">>, #{keys => Keys}, Config). + +domain_admin_install_fallback(Path, Config) -> + execute_domain_admin_command(<<"mnesia">>, <<"installFallback">>, #{path => Path}, Config). + +domain_admin_dump_mnesia(Path, Config) -> + execute_domain_admin_command(<<"mnesia">>, <<"dump">>, #{path => Path}, Config). + +domain_admin_backup_mnesia(Path, Config) -> + execute_domain_admin_command(<<"mnesia">>, <<"backup">>, #{path => Path}, Config). + +domain_admin_restore_mnesia(Path, Config) -> + execute_domain_admin_command(<<"mnesia">>, <<"restore">>, #{path => Path}, Config). + +domain_admin_dump_mnesia_table(Path, Table, Config) -> + Vars = #{path => Path, table => Table}, + execute_domain_admin_command(<<"mnesia">>, <<"dumpTable">>, Vars, Config). + +domain_admin_load_mnesia(Path, Config) -> + execute_domain_admin_command(<<"mnesia">>, <<"load">>, #{path => Path}, Config). + +domain_admin_change_nodename(ChangeFrom, ChangeTo, Source, Target, Config) -> + Vars = #{<<"fromString">> => ChangeFrom, <<"toString">> => ChangeTo, + <<"source">> => Source, <<"target">> => Target}, + execute_domain_admin_command(<<"mnesia">>, <<"changeNodename">>, Vars, Config). + +domain_admin_set_master(Node, Config) -> + execute_domain_admin_command(<<"mnesia">>, <<"setMaster">>, Node, Config). + mnesia_info_keys() -> [<<"all">>, <<"AAAA">>, diff --git a/priv/graphql/schemas/admin/mnesia.gql b/priv/graphql/schemas/admin/mnesia.gql index 293bffb075..f1e330acee 100644 --- a/priv/graphql/schemas/admin/mnesia.gql +++ b/priv/graphql/schemas/admin/mnesia.gql @@ -2,35 +2,44 @@ Allow admin to manage mnesia database """ -type MnesiaAdminQuery { - info(keys: [String!]!): [MnesiaInfo] +type MnesiaAdminQuery @protected{ + info(keys: [String!]!): [MnesiaInfo] + @protected(type: GLOBAL) } -type MnesiaAdminMutation { - setMaster(node: String!): String - changeNodename(fromString: String!, toString: String!, - source: String!, target: String!): String - backup(path: String!): String - restore(path: String!): String - dump(path: String!): String - dumpTable(path: String!, table: String!): String - load(path: String!): String - installFallback(path: String!): String +type MnesiaAdminMutation @protected{ + setMaster(node: String!): String + @protected(type: GLOBAL) + changeNodename(fromString: String!, toString: String!, + source: String!, target: String!): String + @protected(type: GLOBAL) + backup(path: String!): String + @protected(type: GLOBAL) + restore(path: String!): String + @protected(type: GLOBAL) + dump(path: String!): String + @protected(type: GLOBAL) + dumpTable(path: String!, table: String!): String + @protected(type: GLOBAL) + load(path: String!): String + @protected(type: GLOBAL) + installFallback(path: String!): String + @protected(type: GLOBAL) } union MnesiaInfo = MnesiaStringResponse | MnesiaListResponse | MnesiaIntResponse type MnesiaStringResponse { - result: String - key: String + result: String + key: String } type MnesiaListResponse { - result: [String] - key: String + result: [String] + key: String } type MnesiaIntResponse { - result: Int - key: String + result: Int + key: String } diff --git a/src/graphql/mongoose_graphql_errors.erl b/src/graphql/mongoose_graphql_errors.erl index e659ed9816..d01b3d2c80 100644 --- a/src/graphql/mongoose_graphql_errors.erl +++ b/src/graphql/mongoose_graphql_errors.erl @@ -45,9 +45,6 @@ err(_Ctx, ErrorTerm) -> -spec crash(map(), term()) -> err_msg(). crash(_Ctx, Err = #{type := Type}) -> ?LOG_ERROR(Err#{what => graphql_crash}), - io:format("--------------\n\n"), - io:format("~p\n", [Err]), - io:format("--------------\n\n"), #{message => <<"Unexpected ", Type/binary, " resolver crash">>, extensions => #{code => resolver_crash}}.