1%% ``Licensed under the Apache License, Version 2.0 (the "License");
2%% you may not use this file except in compliance with the License.
3%% You may obtain a copy of the License at
4%%
5%%     http://www.apache.org/licenses/LICENSE-2.0
6%%
7%% Unless required by applicable law or agreed to in writing, software
8%% distributed under the License is distributed on an "AS IS" BASIS,
9%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10%% See the License for the specific language governing permissions and
11%% limitations under the License.
12%%
13%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
14%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
15%% AB. All Rights Reserved.''
16%%
17%%     $Id$
18%%
19-module(bup).
20-export([
21         change_node_name/5,
22         view/2,
23         test/0,
24         test/1
25        ]).
26
27-export([
28	 count/1,
29	 display/1
30        ]).
31
32%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
33%% Management of backups, a few demos
34
35%0
36change_node_name(Mod, From, To, Source, Target) ->
37    Switch =
38        fun(Node) when Node == From -> To;
39           (Node) when Node == To -> throw({error, already_exists});
40           (Node) -> Node
41        end,
42    Convert =
43        fun({schema, db_nodes, Nodes}, Acc) ->
44                {[{schema, db_nodes, lists:map(Switch,Nodes)}], Acc};
45           ({schema, version, Version}, Acc) ->
46                {[{schema, version, Version}], Acc};
47           ({schema, cookie, Cookie}, Acc) ->
48                {[{schema, cookie, Cookie}], Acc};
49           ({schema, Tab, CreateList}, Acc) ->
50                Keys = [ram_copies, disc_copies, disc_only_copies],
51                OptSwitch =
52                    fun({Key, Val}) ->
53                            case lists:member(Key, Keys) of
54                                true -> {Key, lists:map(Switch, Val)};
55                                false-> {Key, Val}
56                            end
57                    end,
58                {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc};
59           (Other, Acc) ->
60                {[Other], Acc}
61        end,
62    mnesia:traverse_backup(Source, Mod, Target, Mod, Convert, switched).
63
64view(Source, Mod) ->
65    View = fun(Item, Acc) ->
66                   io:format("~p.~n",[Item]),
67                   {[Item], Acc + 1}
68           end,
69    mnesia:traverse_backup(Source, Mod, dummy, read_only, View, 0).
70%0
71
72-record(bup_rec, {key, val}).
73
74%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
75%% Test change of node name
76%%
77%% Assume db_nodes to be current node and on all other nodes but one
78%% Create new schema,  start Mnesia on all db_nodes
79%% Create a table of disc_copies type which is replicated to all db_nodes
80%% Perform a backup and change current node to unused node in backup
81%% Start Mnesia on all nodes according to the new set of db_nodes
82test() ->
83    test(nodes()).
84
85test(Nodes)->
86    AllNodes = (Nodes -- [node()]) ++ [node()],
87    case length(AllNodes)  of
88        Length when Length > 1 ->
89	    OldBup = "old.BUP",
90	    NewBup = "new.BUP",
91            Res = (catch test2(AllNodes, OldBup, NewBup)),
92	    case Res of
93                {'EXIT', Reason} ->
94		    file:delete(OldBup),
95		    file:delete(NewBup),
96		    {error, Reason};
97                ok ->
98		    ok = count(NewBup),
99		    file:delete(OldBup),
100		    file:delete(NewBup),
101		    ok
102            end;
103        _ ->
104            {error,{"Must run on at least one other node",AllNodes}}
105    end.
106
107test2(AllNodes, OldBup, NewBup) ->
108    ThisNode = node(),
109    OtherNode = hd(AllNodes -- [ThisNode]),
110    OldNodes = AllNodes -- [OtherNode],
111    NewNodes = AllNodes -- [ThisNode],
112    Mod = mnesia_backup, % Assume local files
113    file:delete(OldBup),
114    file:delete(NewBup),
115
116    %% Create old backup
117    rpc:multicall(AllNodes, mnesia, lkill, []),
118    ok = mnesia:delete_schema(AllNodes),
119    ok = mnesia:create_schema(OldNodes),
120    rpc:multicall(OldNodes, mnesia, start, []),
121    rpc:multicall(OldNodes, mnesia, wait_for_tables, [[schema], infinity]),
122
123    CreateList = [{disc_copies, OldNodes},
124                  {attributes, record_info(fields, bup_rec)}],
125    {atomic, ok} = mnesia:create_table(bup_rec, CreateList),
126    rpc:multicall(OldNodes, mnesia, wait_for_tables, [[bup_rec], infinity]),
127    OldRecs = [#bup_rec{key = I, val = I * I} || I <- lists:seq(1, 10)],
128    lists:foreach(fun(R) -> ok = mnesia:dirty_write(R) end,OldRecs),
129    ok = mnesia:backup(OldBup, Mod),
130    ok = mnesia:dirty_write(#bup_rec{key = 4711, val = 4711}),
131    rpc:multicall(OldNodes, mnesia, stop, []),
132    {ok,_} = view(OldBup, Mod),
133
134    %% Change node name
135    {ok,_} = change_node_name(Mod, ThisNode, OtherNode, OldBup, NewBup),
136    ok = rpc:call(OtherNode, mnesia, install_fallback, [NewBup, Mod]),
137    {_NewStartRes,[]} = rpc:multicall(NewNodes, mnesia, start, []),
138    rpc:call(OtherNode, mnesia, wait_for_tables, [[bup_rec], infinity]),
139    Wild = rpc:call(OtherNode, mnesia, table_info, [bup_rec, wild_pattern]),
140    NewRecs = rpc:call(OtherNode, mnesia, dirty_match_object, [Wild]),
141    rpc:multicall(NewNodes, mnesia, stop, []),
142    {ok,_} = view(NewBup, Mod),
143
144    %% Sanity test
145    case {lists:sort(OldRecs), lists:sort(NewRecs)} of
146        {Same, Same} -> ok
147    end.
148
149%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
150
151-record(state, {counter_tab, size_tab, acc_size = 0, n_records = 0}).
152
153
154%% Iterates over a backup file and shows some statistics
155%% The identity of ets table containing the counters is not removed
156count(BupFile) ->
157    CounterTab = ets:new(?MODULE, [set, public]),
158    SizeTab = ets:new(?MODULE, [set, public]),
159    Mod = mnesia:system_info(backup_module),
160    State = #state{counter_tab = CounterTab, size_tab = SizeTab},
161    case mnesia:traverse_backup(BupFile, Mod, dummy, read_only, fun incr/2, State) of
162	{ok, State2} ->
163	    Res = display(State2),
164	    ets:delete(CounterTab),
165	    ets:delete(SizeTab),
166	    Res;
167	{error, Reason} ->
168	    ets:delete(CounterTab),
169	    ets:delete(SizeTab),
170	    {error, Reason}
171    end.
172
173incr(Rec, State) ->
174    Tab = element(1, Rec),
175    Key = element(2, Rec),
176    Oid = {Tab, Key},
177    incr_counter(State#state.counter_tab, Oid),
178    Size = size(term_to_binary(Rec)),
179    max_size(State#state.size_tab, Tab, Key, Size),
180    AccSize = State#state.acc_size,
181    N = State#state.n_records,
182    State2 = State#state{acc_size = AccSize + Size, n_records = N + 1},
183    {[Rec], State2}.
184
185incr_counter(T, Counter) ->
186    case catch ets:update_counter(T, Counter, 1) of
187	{'EXIT', _} ->
188	    ets:insert(T, {Counter, 1});
189	_ ->
190	    ignore
191    end.
192
193max_size(T, Tab, Key, Size) ->
194    case catch ets:lookup_element(T, Tab, 2) of
195	{'EXIT', _} ->
196	    ets:insert(T, {Tab, Size, Key});
197	OldSize when OldSize < Size ->
198	    ets:insert(T, {Tab, Size, Key});
199	_ ->
200	    ignore
201    end.
202
203%% Displays the statistics found in the ets table
204display(State) ->
205    CounterTab = State#state.counter_tab,
206    Tabs = [T || {{_, T}, _} <- match_tab(CounterTab, schema)],
207    io:format("~w tables with totally: ~w records, ~w keys, ~w bytes~n",
208	      [length(Tabs),
209	       State#state.n_records,
210	       ets:info(CounterTab, size),
211	       State#state.acc_size]),
212    display(State, lists:sort(Tabs)).
213
214display(State, [Tab | Tabs]) ->
215    Counters = match_tab(State#state.counter_tab, Tab),
216    io:format("~-10w     records in table ~w~n", [length(Counters), Tab]),
217    Fun = fun({_Oid, Val}) when Val < 5 ->
218		  ignore;
219	     ({Oid, Val}) ->
220		  io:format("~-10w *** records with key ~w~n", [Val, Oid])
221	  end,
222    lists:foreach(Fun, Counters),
223    display_size(State#state.size_tab, Tab),
224    display(State, Tabs);
225display(_CounterTab, []) ->
226    ok.
227
228match_tab(T, Tab) ->
229    ets:match_object(T, {{Tab, '_'}, '_'}).
230
231display_size(T, Tab) ->
232    case catch ets:lookup(T, Tab) of
233	[] ->
234	    ignore;
235	[{_, Size, Key}] when Size > 1000 ->
236	    io:format("~-10w --- bytes occupied by largest record ~w~n",
237		      [Size, {Tab, Key}]);
238	[{_, _, _}] ->
239	    ignore
240    end.
241