1%% This Source Code Form is subject to the terms of the Mozilla Public 2%% License, v. 2.0. If a copy of the MPL was not distributed with this 3%% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4%% 5%% Copyright (c) 2007-2021 VMware, Inc. or its affiliates. All rights reserved. 6%% 7 8-module(rabbit_mnesia_rename). 9-include_lib("rabbit_common/include/rabbit.hrl"). 10 11-export([rename/2]). 12-export([maybe_finish/1]). 13 14-define(CONVERT_TABLES, [schema, rabbit_durable_queue]). 15 16%% Supports renaming the nodes in the Mnesia database. In order to do 17%% this, we take a backup of the database, traverse the backup 18%% changing node names and pids as we go, then restore it. 19%% 20%% That's enough for a standalone node, for clusters the story is more 21%% complex. We can take pairs of nodes From and To, but backing up and 22%% restoring the database changes schema cookies, so if we just do 23%% this on all nodes the cluster will refuse to re-form with 24%% "Incompatible schema cookies.". Therefore we do something similar 25%% to what we do for upgrades - the first node in the cluster to 26%% restart becomes the authority, and other nodes wipe their own 27%% Mnesia state and rejoin. They also need to tell Mnesia the old node 28%% is not coming back. 29%% 30%% If we are renaming nodes one at a time then the running cluster 31%% might not be aware that a rename has taken place, so after we wipe 32%% and rejoin we then update any tables (in practice just 33%% rabbit_durable_queue) which should be aware that we have changed. 34 35%%---------------------------------------------------------------------------- 36 37-spec rename(node(), [{node(), node()}]) -> 'ok'. 38 39rename(Node, NodeMapList) -> 40 try 41 %% Check everything is correct and figure out what we are 42 %% changing from and to. 43 {FromNode, ToNode, NodeMap} = prepare(Node, NodeMapList), 44 45 %% We backup and restore Mnesia even if other nodes are 46 %% running at the time, and defer the final decision about 47 %% whether to use our mutated copy or rejoin the cluster until 48 %% we restart. That means we might be mutating our copy of the 49 %% database while the cluster is running. *Do not* contact the 50 %% cluster while this is happening, we are likely to get 51 %% confused. 52 application:set_env(kernel, dist_auto_connect, never), 53 54 %% Take a copy we can restore from if we abandon the 55 %% rename. We don't restore from the "backup" since restoring 56 %% that changes schema cookies and might stop us rejoining the 57 %% cluster. 58 ok = rabbit_mnesia:copy_db(mnesia_copy_dir()), 59 60 %% And make the actual changes 61 become(FromNode), 62 take_backup(before_backup_name()), 63 convert_backup(NodeMap, before_backup_name(), after_backup_name()), 64 ok = rabbit_file:write_term_file(rename_config_name(), 65 [{FromNode, ToNode}]), 66 convert_config_files(NodeMap), 67 become(ToNode), 68 restore_backup(after_backup_name()), 69 ok 70 after 71 stop_mnesia() 72 end. 73 74prepare(Node, NodeMapList) -> 75 %% If we have a previous rename and haven't started since, give up. 76 case rabbit_file:is_dir(dir()) of 77 true -> exit({rename_in_progress, 78 "Restart node under old name to roll back"}); 79 false -> ok = rabbit_file:ensure_dir(mnesia_copy_dir()) 80 end, 81 82 %% Check we don't have two nodes mapped to the same node 83 {FromNodes, ToNodes} = lists:unzip(NodeMapList), 84 case length(FromNodes) - length(lists:usort(ToNodes)) of 85 0 -> ok; 86 _ -> exit({duplicate_node, ToNodes}) 87 end, 88 89 %% Figure out which node we are before and after the change 90 FromNode = case [From || {From, To} <- NodeMapList, 91 To =:= Node] of 92 [N] -> N; 93 [] -> Node 94 end, 95 NodeMap = dict:from_list(NodeMapList), 96 ToNode = case dict:find(FromNode, NodeMap) of 97 {ok, N2} -> N2; 98 error -> FromNode 99 end, 100 101 %% Check that we are in the cluster, all old nodes are in the 102 %% cluster, and no new nodes are. 103 Nodes = rabbit_nodes:all(), 104 case {FromNodes -- Nodes, ToNodes -- (ToNodes -- Nodes), 105 lists:member(Node, Nodes ++ ToNodes)} of 106 {[], [], true} -> ok; 107 {[], [], false} -> exit({i_am_not_involved, Node}); 108 {F, [], _} -> exit({nodes_not_in_cluster, F}); 109 {_, T, _} -> exit({nodes_already_in_cluster, T}) 110 end, 111 {FromNode, ToNode, NodeMap}. 112 113take_backup(Backup) -> 114 start_mnesia(), 115 %% We backup only local tables: in particular, this excludes the 116 %% connection tracking tables which have no local replica. 117 LocalTables = mnesia:system_info(local_tables), 118 {ok, Name, _Nodes} = mnesia:activate_checkpoint([ 119 {max, LocalTables} 120 ]), 121 ok = mnesia:backup_checkpoint(Name, Backup), 122 stop_mnesia(). 123 124restore_backup(Backup) -> 125 ok = mnesia:install_fallback(Backup, [{scope, local}]), 126 start_mnesia(), 127 stop_mnesia(), 128 rabbit_mnesia:force_load_next_boot(). 129 130-spec maybe_finish([node()]) -> 'ok'. 131 132maybe_finish(AllNodes) -> 133 case rabbit_file:read_term_file(rename_config_name()) of 134 {ok, [{FromNode, ToNode}]} -> finish(FromNode, ToNode, AllNodes); 135 _ -> ok 136 end. 137 138finish(FromNode, ToNode, AllNodes) -> 139 case node() of 140 ToNode -> 141 case rabbit_upgrade:nodes_running(AllNodes) of 142 [] -> finish_primary(FromNode, ToNode); 143 _ -> finish_secondary(FromNode, ToNode, AllNodes) 144 end; 145 FromNode -> 146 rabbit_log:info( 147 "Abandoning rename from ~s to ~s since we are still ~s", 148 [FromNode, ToNode, FromNode]), 149 [{ok, _} = file:copy(backup_of_conf(F), F) || F <- config_files()], 150 ok = rabbit_file:recursive_delete([rabbit_mnesia:dir()]), 151 ok = rabbit_file:recursive_copy( 152 mnesia_copy_dir(), rabbit_mnesia:dir()), 153 delete_rename_files(); 154 _ -> 155 %% Boot will almost certainly fail but we might as 156 %% well just log this 157 rabbit_log:info( 158 "Rename attempted from ~s to ~s but we are ~s - ignoring.", 159 [FromNode, ToNode, node()]) 160 end. 161 162finish_primary(FromNode, ToNode) -> 163 rabbit_log:info("Restarting as primary after rename from ~s to ~s", 164 [FromNode, ToNode]), 165 delete_rename_files(), 166 ok. 167 168finish_secondary(FromNode, ToNode, AllNodes) -> 169 rabbit_log:info("Restarting as secondary after rename from ~s to ~s", 170 [FromNode, ToNode]), 171 rabbit_upgrade:secondary_upgrade(AllNodes), 172 rename_in_running_mnesia(FromNode, ToNode), 173 delete_rename_files(), 174 ok. 175 176dir() -> rabbit_mnesia:dir() ++ "-rename". 177before_backup_name() -> dir() ++ "/backup-before". 178after_backup_name() -> dir() ++ "/backup-after". 179rename_config_name() -> dir() ++ "/pending.config". 180mnesia_copy_dir() -> dir() ++ "/mnesia-copy". 181 182delete_rename_files() -> ok = rabbit_file:recursive_delete([dir()]). 183 184start_mnesia() -> rabbit_misc:ensure_ok(mnesia:start(), cannot_start_mnesia), 185 rabbit_table:force_load(), 186 rabbit_table:wait_for_replicated(_Retry = false). 187stop_mnesia() -> stopped = mnesia:stop(). 188 189convert_backup(NodeMap, FromBackup, ToBackup) -> 190 mnesia:traverse_backup( 191 FromBackup, ToBackup, 192 fun 193 (Row, Acc) -> 194 case lists:member(element(1, Row), ?CONVERT_TABLES) of 195 true -> {[update_term(NodeMap, Row)], Acc}; 196 false -> {[Row], Acc} 197 end 198 end, switched). 199 200config_files() -> 201 [rabbit_node_monitor:running_nodes_filename(), 202 rabbit_node_monitor:cluster_status_filename()]. 203 204backup_of_conf(Path) -> 205 filename:join([dir(), filename:basename(Path)]). 206 207convert_config_files(NodeMap) -> 208 [convert_config_file(NodeMap, Path) || Path <- config_files()]. 209 210convert_config_file(NodeMap, Path) -> 211 {ok, Term} = rabbit_file:read_term_file(Path), 212 {ok, _} = file:copy(Path, backup_of_conf(Path)), 213 ok = rabbit_file:write_term_file(Path, update_term(NodeMap, Term)). 214 215lookup_node(OldNode, NodeMap) -> 216 case dict:find(OldNode, NodeMap) of 217 {ok, NewNode} -> NewNode; 218 error -> OldNode 219 end. 220 221mini_map(FromNode, ToNode) -> dict:from_list([{FromNode, ToNode}]). 222 223update_term(NodeMap, L) when is_list(L) -> 224 [update_term(NodeMap, I) || I <- L]; 225update_term(NodeMap, T) when is_tuple(T) -> 226 list_to_tuple(update_term(NodeMap, tuple_to_list(T))); 227update_term(NodeMap, Node) when is_atom(Node) -> 228 lookup_node(Node, NodeMap); 229update_term(NodeMap, Pid) when is_pid(Pid) -> 230 rabbit_misc:pid_change_node(Pid, lookup_node(node(Pid), NodeMap)); 231update_term(_NodeMap, Term) -> 232 Term. 233 234rename_in_running_mnesia(FromNode, ToNode) -> 235 All = rabbit_nodes:all(), 236 Running = rabbit_nodes:all_running(), 237 case {lists:member(FromNode, Running), lists:member(ToNode, All)} of 238 {false, true} -> ok; 239 {true, _} -> exit({old_node_running, FromNode}); 240 {_, false} -> exit({new_node_not_in_cluster, ToNode}) 241 end, 242 {atomic, ok} = mnesia:del_table_copy(schema, FromNode), 243 Map = mini_map(FromNode, ToNode), 244 {atomic, _} = transform_table(rabbit_durable_queue, Map), 245 ok. 246 247transform_table(Table, Map) -> 248 mnesia:sync_transaction( 249 fun () -> 250 mnesia:lock({table, Table}, write), 251 transform_table(Table, Map, mnesia:first(Table)) 252 end). 253 254transform_table(_Table, _Map, '$end_of_table') -> 255 ok; 256transform_table(Table, Map, Key) -> 257 [Term] = mnesia:read(Table, Key, write), 258 ok = mnesia:write(Table, update_term(Map, Term), write), 259 transform_table(Table, Map, mnesia:next(Table, Key)). 260 261become(BecomeNode) -> 262 error_logger:tty(false), 263 case net_adm:ping(BecomeNode) of 264 pong -> exit({node_running, BecomeNode}); 265 pang -> ok = net_kernel:stop(), 266 io:format(" * Impersonating node: ~s...", [BecomeNode]), 267 {ok, _} = start_distribution(BecomeNode), 268 io:format(" done~n", []), 269 Dir = mnesia:system_info(directory), 270 io:format(" * Mnesia directory : ~s~n", [Dir]) 271 end. 272 273start_distribution(Name) -> 274 rabbit_nodes:ensure_epmd(), 275 NameType = rabbit_nodes_common:name_type(Name), 276 net_kernel:start([Name, NameType]). 277