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