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