%% ``Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%%
%% $Id$
%%
-module(bup).
-export([
change_node_name/5,
view/2,
test/0,
test/1
]).
-export([
count/1,
display/1
]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Management of backups, a few demos
%0
change_node_name(Mod, From, To, Source, Target) ->
Switch =
fun(Node) when Node == From -> To;
(Node) when Node == To -> throw({error, already_exists});
(Node) -> Node
end,
Convert =
fun({schema, db_nodes, Nodes}, Acc) ->
{[{schema, db_nodes, lists:map(Switch,Nodes)}], Acc};
({schema, version, Version}, Acc) ->
{[{schema, version, Version}], Acc};
({schema, cookie, Cookie}, Acc) ->
{[{schema, cookie, Cookie}], Acc};
({schema, Tab, CreateList}, Acc) ->
Keys = [ram_copies, disc_copies, disc_only_copies],
OptSwitch =
fun({Key, Val}) ->
case lists:member(Key, Keys) of
true -> {Key, lists:map(Switch, Val)};
false-> {Key, Val}
end
end,
{[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc};
(Other, Acc) ->
{[Other], Acc}
end,
mnesia:traverse_backup(Source, Mod, Target, Mod, Convert, switched).
view(Source, Mod) ->
View = fun(Item, Acc) ->
io:format("~p.~n",[Item]),
{[Item], Acc + 1}
end,
mnesia:traverse_backup(Source, Mod, dummy, read_only, View, 0).
%0
-record(bup_rec, {key, val}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test change of node name
%%
%% Assume db_nodes to be current node and on all other nodes but one
%% Create new schema, start Mnesia on all db_nodes
%% Create a table of disc_copies type which is replicated to all db_nodes
%% Perform a backup and change current node to unused node in backup
%% Start Mnesia on all nodes according to the new set of db_nodes
test() ->
test(nodes()).
test(Nodes)->
AllNodes = (Nodes -- [node()]) ++ [node()],
case length(AllNodes) of
Length when Length > 1 ->
OldBup = "old.BUP",
NewBup = "new.BUP",
Res = (catch test2(AllNodes, OldBup, NewBup)),
case Res of
{'EXIT', Reason} ->
file:delete(OldBup),
file:delete(NewBup),
{error, Reason};
ok ->
ok = count(NewBup),
file:delete(OldBup),
file:delete(NewBup),
ok
end;
_ ->
{error,{"Must run on at least one other node",AllNodes}}
end.
test2(AllNodes, OldBup, NewBup) ->
ThisNode = node(),
OtherNode = hd(AllNodes -- [ThisNode]),
OldNodes = AllNodes -- [OtherNode],
NewNodes = AllNodes -- [ThisNode],
Mod = mnesia_backup, % Assume local files
file:delete(OldBup),
file:delete(NewBup),
%% Create old backup
rpc:multicall(AllNodes, mnesia, lkill, []),
ok = mnesia:delete_schema(AllNodes),
ok = mnesia:create_schema(OldNodes),
rpc:multicall(OldNodes, mnesia, start, []),
rpc:multicall(OldNodes, mnesia, wait_for_tables, [[schema], infinity]),
CreateList = [{disc_copies, OldNodes},
{attributes, record_info(fields, bup_rec)}],
{atomic, ok} = mnesia:create_table(bup_rec, CreateList),
rpc:multicall(OldNodes, mnesia, wait_for_tables, [[bup_rec], infinity]),
OldRecs = [#bup_rec{key = I, val = I * I} || I <- lists:seq(1, 10)],
lists:foreach(fun(R) -> ok = mnesia:dirty_write(R) end,OldRecs),
ok = mnesia:backup(OldBup, Mod),
ok = mnesia:dirty_write(#bup_rec{key = 4711, val = 4711}),
rpc:multicall(OldNodes, mnesia, stop, []),
{ok,_} = view(OldBup, Mod),
%% Change node name
{ok,_} = change_node_name(Mod, ThisNode, OtherNode, OldBup, NewBup),
ok = rpc:call(OtherNode, mnesia, install_fallback, [NewBup, Mod]),
{_NewStartRes,[]} = rpc:multicall(NewNodes, mnesia, start, []),
rpc:call(OtherNode, mnesia, wait_for_tables, [[bup_rec], infinity]),
Wild = rpc:call(OtherNode, mnesia, table_info, [bup_rec, wild_pattern]),
NewRecs = rpc:call(OtherNode, mnesia, dirty_match_object, [Wild]),
rpc:multicall(NewNodes, mnesia, stop, []),
{ok,_} = view(NewBup, Mod),
%% Sanity test
case {lists:sort(OldRecs), lists:sort(NewRecs)} of
{Same, Same} -> ok
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-record(state, {counter_tab, size_tab, acc_size = 0, n_records = 0}).
%% Iterates over a backup file and shows some statistics
%% The identity of ets table containing the counters is not removed
count(BupFile) ->
CounterTab = ets:new(?MODULE, [set, public]),
SizeTab = ets:new(?MODULE, [set, public]),
Mod = mnesia:system_info(backup_module),
State = #state{counter_tab = CounterTab, size_tab = SizeTab},
case mnesia:traverse_backup(BupFile, Mod, dummy, read_only, fun incr/2, State) of
{ok, State2} ->
Res = display(State2),
ets:delete(CounterTab),
ets:delete(SizeTab),
Res;
{error, Reason} ->
ets:delete(CounterTab),
ets:delete(SizeTab),
{error, Reason}
end.
incr(Rec, State) ->
Tab = element(1, Rec),
Key = element(2, Rec),
Oid = {Tab, Key},
incr_counter(State#state.counter_tab, Oid),
Size = size(term_to_binary(Rec)),
max_size(State#state.size_tab, Tab, Key, Size),
AccSize = State#state.acc_size,
N = State#state.n_records,
State2 = State#state{acc_size = AccSize + Size, n_records = N + 1},
{[Rec], State2}.
incr_counter(T, Counter) ->
case catch ets:update_counter(T, Counter, 1) of
{'EXIT', _} ->
ets:insert(T, {Counter, 1});
_ ->
ignore
end.
max_size(T, Tab, Key, Size) ->
case catch ets:lookup_element(T, Tab, 2) of
{'EXIT', _} ->
ets:insert(T, {Tab, Size, Key});
OldSize when OldSize < Size ->
ets:insert(T, {Tab, Size, Key});
_ ->
ignore
end.
%% Displays the statistics found in the ets table
display(State) ->
CounterTab = State#state.counter_tab,
Tabs = [T || {{_, T}, _} <- match_tab(CounterTab, schema)],
io:format("~w tables with totally: ~w records, ~w keys, ~w bytes~n",
[length(Tabs),
State#state.n_records,
ets:info(CounterTab, size),
State#state.acc_size]),
display(State, lists:sort(Tabs)).
display(State, [Tab | Tabs]) ->
Counters = match_tab(State#state.counter_tab, Tab),
io:format("~-10w records in table ~w~n", [length(Counters), Tab]),
Fun = fun({_Oid, Val}) when Val < 5 ->
ignore;
({Oid, Val}) ->
io:format("~-10w *** records with key ~w~n", [Val, Oid])
end,
lists:foreach(Fun, Counters),
display_size(State#state.size_tab, Tab),
display(State, Tabs);
display(_CounterTab, []) ->
ok.
match_tab(T, Tab) ->
ets:match_object(T, {{Tab, '_'}, '_'}).
display_size(T, Tab) ->
case catch ets:lookup(T, Tab) of
[] ->
ignore;
[{_, Size, Key}] when Size > 1000 ->
io:format("~-10w --- bytes occupied by largest record ~w~n",
[Size, {Tab, Key}]);
[{_, _, _}] ->
ignore
end.