./ct_report/coverage/mongoose_cluster.COVER.html

1 -module(mongoose_cluster).
2
3 %% This is a library module for cluster management: joining / leaving a cluster.
4
5 -export([join/1, leave/0, remove_from_cluster/1, is_node_alive/1]).
6
7 -export([all_cluster_nodes/0, other_cluster_nodes/0]).
8
9 -ignore_xref([all_cluster_nodes/0]).
10
11 -include("mongoose.hrl").
12
13 -dialyzer({[no_match, no_return], set_extra_db_nodes/1}).
14
15 %%
16 %% API
17 %%
18
19 %% @doc Join a cluster designated by ClusterMember.
20 %% This drops all current connections and discards all persistent
21 %% data from Mnesia. Use with caution!
22 %% Next time the node starts, it will connect to other members automatically.
23 -spec join(node()) -> ok.
24 join(ClusterMember) ->
25 35 node_trans(fun() -> do_join(ClusterMember) end).
26
27 do_join(ClusterMember) ->
28 35 ?LOG_NOTICE(#{what => cluster_join,
29 text => <<"Stop mongooseim to join the cluster">>,
30 35 member => ClusterMember}),
31 35 with_app_stopped(mongooseim,
32 fun () ->
33 35 check_networking(ClusterMember),
34 34 unsafe_join(node(), ClusterMember)
35 end).
36
37 %% @doc Leave cluster.
38 %% This drops all current connections and discards all persistent
39 %% data from Mnesia. Use with caution!
40 %% Next time the node starts, it will NOT connect to previous members.
41 %% Remaining members will remove this node from the cluster Mnesia schema.
42 -spec leave() -> ok.
43 leave() ->
44 34 node_trans(fun() -> do_leave() end).
45
46 do_leave() ->
47 34 ?LOG_NOTICE(#{what => cluster_leave,
48 34 text => <<"Stop mongooseim to leave the cluster">>}),
49 34 with_app_stopped(mongooseim,
50 fun () ->
51 34 catch mnesia:stop(),
52 34 detach_nodes(mnesia_nodes()),
53 34 delete_mnesia(),
54 34 ok = mnesia:start()
55 end).
56
57 %% @doc Remove dead node from the cluster.
58 %% The removing node must be down
59 -spec remove_from_cluster(node()) -> ok.
60 remove_from_cluster(Node) ->
61 2 node_trans(fun() -> do_remove_from_cluster(Node) end).
62
63 do_remove_from_cluster(Node) ->
64 2 NodeAlive = is_node_alive(Node),
65 2 NodeAlive andalso error({node_is_alive, Node}),
66 2 remove_dead_from_cluster(Node).
67
68 -spec all_cluster_nodes() -> [node()].
69 all_cluster_nodes() ->
70
:-(
[node() | other_cluster_nodes()].
71
72 -spec other_cluster_nodes() -> [node()].
73 other_cluster_nodes() ->
74 58 lists:filter(fun is_mongooseim_node/1, nodes()).
75
76 %%
77 %% Helpers
78 %%
79
80 remove_dead_from_cluster(DeadNode) ->
81 2 ?LOG_INFO(#{what => cluster_remove_dead_node_from_cluster,
82 text => <<"Removing dead member node from the cluster">>,
83 2 member => DeadNode}),
84 2 case mnesia:del_table_copy(schema, DeadNode) of
85 {atomic, ok} ->
86 2 ok;
87 {aborted, R} ->
88
:-(
error({del_table_copy_schema, R})
89 end.
90
91 is_node_alive(Node) ->
92 7 try check_networking(Node) of
93 true ->
94 3 true
95 catch
96 error:_ ->
97 4 false
98 end.
99
100 -spec is_mongooseim_node(node()) -> boolean().
101 is_mongooseim_node(Node) ->
102 116 Apps = rpc:call(Node, application, which_applications, []),
103 116 lists:keymember(mongooseim, 1, Apps).
104
105 is_app_running(App) ->
106 69 lists:keymember(App, 1, application:which_applications()).
107
108 check_networking(ClusterMember) ->
109 42 ok == wait_for_pong(ClusterMember) orelse error(pang, [ClusterMember]).
110
111 unsafe_join(Node, ClusterMember) ->
112 34 delete_mnesia(),
113 34 ok = mnesia:start(),
114 34 set_extra_db_nodes(ClusterMember),
115 34 true = lists:member(ClusterMember, mnesia:system_info(running_db_nodes)),
116 34 ok = change_schema_type(Node),
117 34 Tables = [ {T, table_type(ClusterMember, T)}
118 34 || T <- mnesia:system_info(tables),
119 345 T /= schema ],
120 34 Copied = [ {Table, mnesia:add_table_copy(T, Node, Type)}
121 34 || {T, Type} = Table <- Tables ],
122 34 lists:foreach(fun check_if_successful_copied/1, Copied),
123 34 ok.
124
125 set_extra_db_nodes(ClusterMember) ->
126 34 case mnesia:change_config(extra_db_nodes, [ClusterMember]) of
127 {ok, [ClusterMember]} ->
128 34 ok;
129 Other ->
130
:-(
error(#{reason => set_extra_db_nodes_failed,
131 result => Other,
132 cluster_member => ClusterMember})
133 end.
134
135 check_if_successful_copied(TableEl) ->
136 311 case TableEl of
137 {_, {atomic, ok}} ->
138 284 ok;
139 {_, {aborted, {already_exists, _, _}}} ->
140 27 ok;
141 Other ->
142
:-(
error({add_table_copy_error, TableEl, Other})
143 end.
144
145 change_schema_type(Node) ->
146 34 case mnesia:change_table_copy_type(schema, Node, disc_copies) of
147 {atomic, ok} ->
148 31 ok;
149 {aborted, {already_exists, _, _, _}} ->
150 3 ok;
151 {aborted, R} ->
152
:-(
{error, R}
153 end.
154
155 table_type(ClusterMember, T) ->
156 311 try rpc:call(ClusterMember, mnesia, table_info, [T, storage_type]) of
157 Type when Type =:= disc_copies;
158 Type =:= ram_copies;
159 311 Type =:= disc_only_copies -> Type
160 catch
161
:-(
E:R -> error({cant_get_storage_type, {T, E, R}}, [T])
162 end.
163
164 %% This will remove all your Mnesia data!
165 %% You've been warned.
166 delete_mnesia() ->
167 68 catch mnesia:stop(),
168 68 Dir = mnesia:system_info(directory),
169 68 case application:get_env(mnesia, dir, undefined) of
170 68 undefined -> ok;
171 Dir ->
172 %% Both settings match, OK!
173
:-(
ok;
174 AppEnvDir ->
175
:-(
?LOG_NOTICE(#{what => mnesia_configuration,
176 text => <<"mnesia:system_info(directory) and application:get_env(mnesia, dir) "
177 "returned different paths. mnesia_dir and env_mnesia_dir are different.">>,
178
:-(
mnesia_dir => Dir, env_mnesia_dir => AppEnvDir}),
179
:-(
ok
180 end,
181 68 ok = rmrf(Dir),
182 68 ?LOG_NOTICE(#{what => mnesia_deleted,
183 text => <<"Mnesia schema and files deleted.">>,
184 68 mnesia_dir => Dir}),
185 68 ok.
186
187 wait_for_pong(Node) ->
188 42 wait_for_pong(net_adm:ping(Node), Node, 5, 100).
189
190 wait_for_pong(pong, _Node, _Retries, _Interval) ->
191 37 ok;
192 wait_for_pong(pang, _Node, 0, _Interval) ->
193 5 timeout;
194 wait_for_pong(pang, Node, Retries, Interval) ->
195 25 timer:sleep(Interval),
196 25 wait_for_pong(net_adm:ping(Node), Node, Retries - 1, Interval).
197
198 rmrf(Dir) ->
199 267 case file:list_dir(Dir) of
200
:-(
{error, enoent} -> ok;
201 {error, enotdir} ->
202 199 ok = file:delete(Dir);
203 {ok, Dirs} ->
204 68 [ ok = rmrf(filename:join(Dir, Sub)) || Sub <- Dirs],
205 68 ok
206 end.
207
208 detach_nodes(Nodes) ->
209 34 Node = node(),
210 34 {_, []} = rpc:multicall(Nodes, mnesia, del_table_copy, [schema, Node]).
211
212 mnesia_nodes() ->
213 34 mnesia:system_info(db_nodes) -- [node()].
214
215 with_app_stopped(App, F) ->
216 69 Running = is_app_running(App),
217 69 Running andalso application:stop(App),
218 69 try
219 69 F()
220 after
221 69 Running andalso application:start(App)
222 end.
223
224 node_trans(F) ->
225 71 global:trans({{mongoose_cluster_op, node()}, self()}, F).
Line Hits Source