File 6111-kernel-Introduce-global-disconnect-0.patch of Package erlang
From d8f636f6a19022cafdb0539f150755d3e7586d76 Mon Sep 17 00:00:00 2001
From: Rickard Green <rickard@erlang.org>
Date: Wed, 31 Aug 2022 18:41:44 +0200
Subject: [PATCH] [kernel] Introduce global:disconnect/0
---
lib/kernel/doc/src/global.xml | 68 +++++++
lib/kernel/src/global.erl | 43 ++++-
lib/kernel/src/net_kernel.erl | 41 +++--
lib/kernel/test/global_SUITE.erl | 235 +++++++++++++++++++++++--
lib/kernel/test/global_group_SUITE.erl | 197 ++++++++++++++++++++-
5 files changed, 554 insertions(+), 30 deletions(-)
diff --git a/lib/kernel/doc/src/global.xml b/lib/kernel/doc/src/global.xml
index d30f721448..e401b8f32d 100644
--- a/lib/kernel/doc/src/global.xml
+++ b/lib/kernel/doc/src/global.xml
@@ -141,6 +141,74 @@
</desc>
</func>
+ <func>
+ <name name="disconnect" arity="0" since=""/>
+ <fsummary>Disconnect from all other nodes known to global</fsummary>
+ <desc>
+ <p>
+ Disconnect from all other nodes known to <c>global</c>. A list
+ of node names (in an unspecified order) is returned which corresponds
+ to the nodes that were disconnected. All disconnect operations
+ performed have completed when <c>global:disconnect/0</c> returns.
+ </p>
+
+ <p>
+ The disconnects will be made in such a way that only the current
+ node will be removed from the cluster of <c>global</c> nodes. If
+ <seeerl marker="#prevent_overlapping_partitions">
+ <c>prevent_overlapping_partitions</c></seeerl> is enabled and
+ you disconnect, from other nodes in the cluster of <c>global</c>
+ nodes, by other means, <c>global</c> on the other nodes may
+ partition the remaining nodes in order to ensure that no
+ overlapping partitions appear. Even if
+ <c>prevent_overlapping_partitions</c> is disabled, you should
+ preferably use <c>global:disconnect/0</c> in order to remove
+ current node from a cluster of <c>global</c> nodes, since you
+ otherwise likely <em>will</em> create overlapping partitions which
+ might <seeerl marker="#prevent_overlapping_partitions">cause
+ problems</seeerl>.
+ </p>
+
+ <p>
+ Note that if the node is going to be halted, there is <em>no</em>
+ need to remove it from a cluster of <c>global</c> nodes explicitly by
+ calling <c>global:disconnect/0</c> before halting it. The removal
+ from the cluster is taken care of automatically when the node
+ halts regardless of whether <c>prevent_overlapping_partitions</c> is
+ enabled or not.
+ </p>
+
+ <p>
+ If current node has been configured to be part of a
+ <seeerl marker="global_group"><i>global group</i></seeerl>, only
+ connected and/or synchronized nodes in that group are known to
+ <c>global</c>, so <c>global:disconnect/0</c> will <em>only</em>
+ disconnect from those nodes. If current node is <em>not</em> part of
+ a <i>global group</i>, all
+ <seemfa marker="erts:erlang#nodes/0">connected visible nodes</seemfa>
+ will be known to <c>global</c>, so <c>global:disconnect/0</c> will
+ disconnect from all those nodes.
+ </p>
+ <p>
+ Note that information about connected nodes does not instantaneously
+ reach <c>global</c>, so the caller might see a node part of the
+ result returned by
+ <seemfa marker="erts:erlang#nodes/0"><c>nodes()</c></seemfa> while
+ it still is not known to <c>global</c>. The disconnect operation
+ will, however, still not cause any overlapping partitions when
+ <c>prevent_overlapping_partitions</c> is enabled. If
+ <c>prevent_overlapping_partitions</c> is disabled, overlapping
+ partitions might form in this case.
+ </p>
+ <p>
+ Note that when <c>prevent_overlapping_partitions</c> is enabled,
+ you may see warning reports on other nodes when they detect that
+ current node has disconnected. These are in this case completely
+ harmless and can be ignored.
+ </p>
+ </desc>
+ </func>
+
<func>
<name name="notify_all_name" arity="3" since=""/>
<fsummary>Name resolving function that notifies both pids.</fsummary>
diff --git a/lib/kernel/src/global.erl b/lib/kernel/src/global.erl
index 503a8cd1ae..2c7d42e501 100644
--- a/lib/kernel/src/global.erl
+++ b/lib/kernel/src/global.erl
@@ -36,7 +36,8 @@
set_lock/1, set_lock/2, set_lock/3,
del_lock/1, del_lock/2,
trans/2, trans/3, trans/4,
- random_exit_name/3, random_notify_name/3, notify_all_name/3]).
+ random_exit_name/3, random_notify_name/3, notify_all_name/3,
+ disconnect/0]).
%% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
@@ -471,6 +472,11 @@ trans(Id, Fun, Nodes, Retries) ->
info() ->
gen_server:call(global_name_server, info, infinity).
+-spec disconnect() -> [node()].
+
+disconnect() ->
+ gen_server:call(global_name_server, disconnect, infinity).
+
%%%-----------------------------------------------------------------
%%% Call-back functions from gen_server
%%%-----------------------------------------------------------------
@@ -744,6 +750,39 @@ handle_call(get_names_ext, _From, S) ->
handle_call(info, _From, S) ->
{reply, S, S};
+handle_call(disconnect, _From, #state{known = Known} = S0) ->
+ %% Disconnect from all nodes global knows of without
+ %% sending any lost_connection messages...
+ Disconnect = fun (N, Ns) ->
+ ?trace({'####', disconnect, {node,N}}),
+ net_kernel:async_disconnect(N),
+ [N|Ns]
+ end,
+ Nodes = maps:fold(fun (N, _, Ns) when is_atom(N) ->
+ Disconnect(N, Ns);
+ ({pending, N}, _, Ns) when is_atom(N) ->
+ case is_map_key(N, Known)
+ orelse is_map_key({removing, N}, Known) of
+ true -> Ns;
+ false -> Disconnect(N, Ns)
+ end;
+ ({removing, N}, _, Ns) when is_atom(N) ->
+ case is_map_key(N, Known)
+ orelse is_map_key({pending, N}, Known) of
+ true -> Ns;
+ false -> Disconnect(N, Ns)
+ end;
+ (_, _, Ns) ->
+ Ns
+ end, [], Known),
+ S1 = lists:foldl(fun (N, SAcc0) ->
+ receive {nodedown, N} -> ok end,
+ ?trace({'####', nodedown, {node,N}}),
+ SAcc1 = trace_message(SAcc0, {nodedown, N}, []),
+ handle_nodedown(N, SAcc1, ignore_node)
+ end, S0, Nodes),
+ {reply, Nodes, S1};
+
%% "High level trace". For troubleshooting only.
handle_call(high_level_trace_start, _From, S) ->
S#state.the_locker ! {do_trace, true},
@@ -2294,7 +2333,7 @@ pid_locks(Ref) ->
ref_is_locking(Ref, PidRefs)].
ref_is_locking(Ref, PidRefs) ->
- lists:keyfind(Ref, 2, PidRefs) =/= false.
+ lists:keyfind(Ref, 2, PidRefs) =/= false.
handle_nodedown(Node, #state{synced = Syncs,
known = Known0} = S, What) ->
diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl
index 8dfae3a505..c1f86ead0d 100644
--- a/lib/kernel/src/net_kernel.erl
+++ b/lib/kernel/src/net_kernel.erl
@@ -72,7 +72,7 @@
epmd_module/0,
dist_listen/0]).
--export([disconnect/1, passive_cnct/1]).
+-export([disconnect/1, async_disconnect/1, passive_cnct/1]).
-export([hidden_connect_node/1]).
-export([set_net_ticktime/1, set_net_ticktime/2, get_net_ticktime/0]).
@@ -299,6 +299,9 @@ passive_cnct(Node) ->
disconnect(Node) -> request({disconnect, Node}).
+async_disconnect(Node) ->
+ gen_server:cast(net_kernel, {async_disconnect, Node}).
+
%% Should this node publish itself on Node?
publish_on_node(Node) when is_atom(Node) ->
request({publish_on_node, Node}).
@@ -552,7 +555,7 @@ handle_call({disconnect, Node}, From, State) when Node =:= node() ->
async_reply({reply, false, State}, From);
handle_call({disconnect, Node}, From, State) ->
verbose({disconnect, Node}, 1, State),
- {Reply, State1} = do_disconnect(Node, State),
+ {Reply, State1} = do_disconnect(Node, State, false),
async_reply({reply, Reply, State1}, From);
%%
@@ -714,6 +717,13 @@ handle_call(_Msg, _From, State) ->
%% handle_cast.
%% ------------------------------------------------------------
+handle_cast({async_disconnect, Node}, State) when Node =:= node() ->
+ {noreply, State};
+handle_cast({async_disconnect, Node}, State) ->
+ verbose({async_disconnect, Node}, 1, State),
+ {_Reply, State1} = do_disconnect(Node, State, true),
+ {noreply, State1};
+
handle_cast(_, State) ->
{noreply,State}.
@@ -920,7 +930,7 @@ handle_info({From,registered_send,To,Mess},State) ->
handle_info({From,badcookie,_To,_Mess}, State) ->
error_logger:error_msg("~n** Got OLD cookie from ~w~n",
[getnode(From)]),
- {_Reply, State1} = do_disconnect(getnode(From), State),
+ {_Reply, State1} = do_disconnect(getnode(From), State, false),
{noreply,State1};
%%
@@ -1344,23 +1354,30 @@ mk_monitor_nodes_error(_Flag, Opts) ->
% -------------------------------------------------------------
-do_disconnect(Node, State) ->
+do_disconnect(Node, State, Async) ->
case ets:lookup(sys_dist, Node) of
[Conn] when Conn#connection.state =:= up ->
- disconnect_ctrlr(Conn#connection.ctrlr, State);
+ disconnect_ctrlr(Conn#connection.ctrlr, State, Async);
[Conn] when Conn#connection.state =:= up_pending ->
- disconnect_ctrlr(Conn#connection.ctrlr, State);
+ disconnect_ctrlr(Conn#connection.ctrlr, State, Async);
_ ->
{false, State}
end.
-disconnect_ctrlr(Ctrlr, State) ->
+disconnect_ctrlr(Ctrlr, S0, Async) ->
exit(Ctrlr, disconnect),
- receive
- {'EXIT',Ctrlr,Reason} ->
- {_,State1} = handle_exit(Ctrlr, Reason, State),
- {true, State1}
- end.
+ S2 = case Async of
+ true ->
+ S0;
+ false ->
+ receive
+ {'EXIT',Ctrlr,Reason} ->
+ {_,S1} = handle_exit(Ctrlr, Reason, S0),
+ S1
+ end
+ end,
+ {true, S2}.
+
%%
%%
diff --git a/lib/kernel/test/global_SUITE.erl b/lib/kernel/test/global_SUITE.erl
index c0c9f4b912..05119fb378 100644
--- a/lib/kernel/test/global_SUITE.erl
+++ b/lib/kernel/test/global_SUITE.erl
@@ -47,7 +47,8 @@
ring_line/1,
flaw1/1
lost_connection/1,
- lost_connection2/1
+ lost_connection2/1,
+ global_disconnect/1
]).
-export([global_load/3, lock_global/2, lock_global2/2]).
@@ -136,7 +137,7 @@ all() ->
re_register_name, name_exit, external_nodes, many_nodes,
sync_0, global_groups_change, register_1, both_known_1,
lost_unregister, mass_death, garbage_messages, flaw1,
- lost_connection, lost_connection2
+ lost_connection, lost_connection2, global_disconnect
]
end.
@@ -172,14 +173,9 @@ init_per_testcase(Case, Config0) when is
"~n Monitors: ~p",
[Config0, erlang:nodes(), pi(links), pi(monitors)]),
- ok = gen_server:call(global_name_server,
- high_level_trace_start,
- infinity),
+ start_node_tracker(Config),
- %% Make sure that everything is dead and done. Otherwise there are problems
- %% on platforms on which it takes a long time to shut down a node.
- stop_nodes(nodes()),
- timer:sleep(1000),
+ ok = gen_server:call(global_name_server, high_level_trace_start,infinity),
Config1 = [{?TESTCASE, Case}, {registered, registered()} | Config0],
@@ -217,7 +213,7 @@ end_per_testcase(_Case, Config) ->
"~n Monitors: ~p",
[erlang:nodes(), pi(links), pi(monitors)]),
- ok.
+ stop_node_tracker(Config). %% Needs to be last and produce return value...
%%% General comments:
%%% One source of problems with failing tests can be that the nodes from the
@@ -3943,6 +3939,7 @@ start_node_rel(Name0, Rel, Config) ->
record_started_node(Res).
record_started_node({ok, Node}) ->
+ node_started(Node),
case erase(?nodes_tag) of
undefined -> ok;
Nodes -> put(?nodes_tag, [Node | Nodes])
@@ -3967,12 +3964,15 @@ stop_nodes(Nodes) ->
lists:foreach(fun(Node) -> stop_node(Node) end, Nodes).
stop_node(Node) ->
- test_server:stop_node(Node).
+ Res = test_server:stop_node(Node),
+ node_stopped(Node),
+ Res.
stop() ->
lists:foreach(fun(Node) ->
- test_server:stop_node(Node)
+ test_server:stop_node(Node),
+ node_stopped(Node)
end, nodes()).
%% Tests that locally loaded nodes do not loose contact with other nodes.
@@ -4386,6 +4386,104 @@ flaw1_test(Config) ->
init_condition(Config),
ok.
+global_disconnect(Config) when is_list(Config) ->
+ Timeout = 30,
+ ct:timetrap({seconds,Timeout}),
+
+ [] = nodes(connected),
+
+ {ok, H1} = start_hidden_node(h1, Config),
+ {ok, H2} = start_hidden_node(h2, Config),
+ {ok, Cp1} = start_node(cp1, peer, Config),
+ {ok, Cp2} = start_node(cp2, peer, Config),
+ {ok, Cp3} = start_node(cp3, peer, Config),
+
+ ThisNode = node(),
+ HNodes = lists:sort([H1, H2]),
+ OtherGNodes = lists:sort([Cp1, Cp2, Cp3]),
+ AllGNodes = lists:sort([ThisNode|OtherGNodes]),
+
+ lists:foreach(fun (Node) -> pong = net_adm:ping(Node) end, OtherGNodes),
+
+ wait_for_ready_net(Config),
+
+ ok = rfcall(
+ H2,
+ fun () ->
+ lists:foreach(fun (Node) ->
+ pong = net_adm:ping(Node)
+ end, OtherGNodes)
+ end),
+
+ lists:foreach(
+ fun (Node) ->
+ AllGNodes = rfcall(
+ H1,
+ fun () ->
+ rfcall(
+ Node,
+ fun () ->
+ lists:sort([node()|nodes()])
+ end)
+ end),
+ HNodes = rfcall(
+ H1,
+ fun () ->
+ rfcall(
+ Node,
+ fun () ->
+ lists:sort(nodes(hidden))
+ end)
+ end)
+ end, AllGNodes),
+
+ OtherGNodes = lists:sort(global:disconnect()),
+
+ GNodesAfterDisconnect = nodes(),
+
+ HNodes = lists:sort(nodes(hidden)),
+
+ lists:foreach(fun (Node) ->
+ false = lists:member(Node, GNodesAfterDisconnect)
+ end,
+ OtherGNodes),
+
+ %% Wait a while giving the other nodes time to react to the disconnects
+ %% before we check that everything is as expected...
+ receive after 2000 -> ok end,
+
+ lists:foreach(
+ fun (Node) ->
+ OtherGNodes = rfcall(
+ H1,
+ fun () ->
+ rfcall(
+ Node,
+ fun () ->
+ lists:sort([node()|nodes()])
+ end)
+ end),
+ HNodes = rfcall(
+ H1,
+ fun () ->
+ rfcall(
+ Node,
+ fun () ->
+ lists:sort(nodes(hidden))
+ end)
+ end)
+ end, OtherGNodes),
+
+ stop_node(Cp1),
+ stop_node(Cp2),
+ stop_node(Cp3),
+ stop_node(H1),
+ stop_node(H2),
+
+ ok.
+
+%% ---
+
wait_for_ready_net(Config) ->
{Pid, MRef} = spawn_monitor(fun() ->
wait_for_ready_net(?NODES, Config)
@@ -4723,3 +4821,117 @@ handle_call(_Query, State) -> {ok, {erro
terminate(_Reason, State) ->
State.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+%%% Keep track of started nodes so we can kill them if test
+%%% cases fail to stop them. This typically happens on test case
+%%% failure with peer nodes, and if left might mess up following
+%%% test-cases.
+%%%
+%%% This can be removed when the suite is converted to use CT_PEER.
+%%% This can however at earliest be made in OTP 25.
+%%%
+
+node_started(Node) ->
+ _ = global_SUITE_node_tracker ! {node_started, Node},
+ ok.
+
+node_stopped(Node) ->
+ _ = global_SUITE_node_tracker ! {node_stopped, Node},
+ ok.
+
+start_node_tracker(Config) ->
+ case whereis(global_SUITE_node_tracker) of
+ undefined ->
+ ok;
+ _ ->
+ try
+ stop_node_tracker(Config),
+ ok
+ catch
+ _:_ ->
+ ok
+ end
+ end,
+ _ = spawn(fun () ->
+ _ = register(global_SUITE_node_tracker, self()),
+ node_tracker_loop(#{})
+ end),
+ ok.
+
+node_tracker_loop(Nodes) ->
+ receive
+ {node_started, Node} ->
+ node_tracker_loop(Nodes#{Node => alive});
+ {node_stopped, Node} ->
+ node_tracker_loop(Nodes#{Node => stopped});
+ stop ->
+ Fact = try
+ test_server:timetrap_scale_factor()
+ catch _:_ -> 1
+ end,
+ Tmo = 1000*Fact,
+ lists:foreach(
+ fun (N) ->
+ case maps:get(N, Nodes) of
+ stopped ->
+ ok;
+ alive ->
+ ct:pal("WARNING: The node ~p was not "
+ "stopped by the test case!", [N])
+ end,
+ %% We try to kill every node, even those reported as
+ %% stopped since they might have failed at stopping...
+ case rpc:call(N, erlang, halt, [], Tmo) of
+ {badrpc,nodedown} ->
+ ok;
+ {badrpc,timeout} ->
+ ct:pal("WARNING: Failed to kill node: ~p~n"
+ " Disconnecting it, but it may "
+ "still be alive!", [N]),
+ erlang:disconnect_node(N),
+ ok;
+ Unexpected ->
+ ct:pal("WARNING: Failed to kill node: ~p~n"
+ " Got response: ~p~n"
+ " Disconnecting it, but it may "
+ "still be alive!", [N, Unexpected]),
+ erlang:disconnect_node(N),
+ ok
+ end
+ end, maps:keys(Nodes))
+ end.
+
+stop_node_tracker(Config) ->
+ NTRes = case whereis(global_SUITE_node_tracker) of
+ undefined ->
+ {fail, missing_node_tracker};
+ NT when is_port(NT) ->
+ NTMon = erlang:monitor(port, NT),
+ exit(NT, kill),
+ receive {'DOWN', NT, port, NT, _} -> ok end,
+ {fail, {port_node_tracker, NT}};
+ NT when is_pid(NT) ->
+ NTMon = erlang:monitor(process, NT),
+ NT ! stop,
+ receive
+ {'DOWN', NTMon, process, NT, normal} ->
+ ok;
+ {'DOWN', NTMon, process, NT, Reason} ->
+ {fail, {node_tracker_failed, Reason}}
+ end
+ end,
+ case NTRes of
+ ok ->
+ ok;
+ NTFailure ->
+ case proplists:get_value(tc_status, Config) of
+ ok ->
+ %% Fail test case with info about node tracker...
+ NTFailure;
+ _ ->
+ %% Don't fail due to node tracker...
+ ct:pal("WARNING: Node tracker failure: ~p", [NTFailure]),
+ ok
+ end
+ end.
diff --git a/lib/kernel/test/global_group_SUITE.erl b/lib/kernel/test/global_group_SUITE.erl
index 594ee6b537..531d414bb7 100644
--- a/lib/kernel/test/global_group_SUITE.erl
+++ b/lib/kernel/test/global_group_SUITE.erl
@@ -23,7 +23,8 @@
-export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2,
init_per_suite/1, end_per_suite/1]).
-export([start_gg_proc/1, no_gg_proc/1, no_gg_proc_sync/1, compatible/1,
- one_grp/1, one_grp_x/1, two_grp/1, hidden_groups/1, test_exit/1]).
+ one_grp/1, one_grp_x/1, two_grp/1, hidden_groups/1, test_exit/1,
+ global_disconnect/1]).
-export([init/1, init/2, init2/2, start_proc/1, start_proc_rereg/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
@@ -42,7 +43,8 @@ suite() ->
all() ->
[start_gg_proc, no_gg_proc, no_gg_proc_sync, compatible,
- one_grp, one_grp_x, two_grp, test_exit, hidden_groups].
+ one_grp, one_grp_x, two_grp, test_exit, hidden_groups,
+ global_disconnect].
groups() ->
[].
@@ -55,7 +57,6 @@ end_per_group(_GroupName, Config) ->
init_per_suite(Config) ->
-
%% Copied from test_server_ctrl ln 647, we have to do this here as
%% the test_server only does this when run without common_test
global:sync(),
@@ -81,10 +81,10 @@ end_per_suite(_Config) ->
-define(TESTCASE, testcase_name).
-define(testcase, proplists:get_value(?TESTCASE, Config)).
-init_per_testcase(Case, Config) ->
+init_per_testcase(_Case, Config) ->
Config.
-end_per_testcase(_Func, _Config) ->
+end_per_testcase(_Case, _Config) ->
ok.
%%-----------------------------------------------------------------
@@ -1173,6 +1173,186 @@ test_exit(Config) when is_list(Config) ->
ok.
+global_disconnect(Config) when is_list(Config) ->
+ Dir = proplists:get_value(priv_dir, Config),
+
+ [NProxy,Ncp1,Ncp2,Ncp3,Ncpx,Ncpy,Ncpz,Nh]
+ = node_names([proxy,cp1,cp2,cp3,cpx,cpy,cpz,h], Config),
+
+ Host = from($@, atom_to_list(node())),
+
+ WriteConf = fun (File,
+ [S1, S2, S3],
+ [NC11, NC12, NC13],
+ [NC21, NC22, NC23]) ->
+ {ok, Fd} = file:open(filename:join(Dir, File), [write]),
+ io:format(Fd,
+ "[{kernel,~n"
+ " [~n"
+ " {sync_nodes_optional, ['~s@~s','~s@~s','~s@~s']},~n"
+ " {sync_nodes_timeout, 1000},~n"
+ " {global_groups,~n"
+ " [{nc1, ['~s@~s','~s@~s','~s@~s']},~n"
+ " {nc2, ['~s@~s','~s@~s','~s@~s']}]}~n"
+ " ]~n"
+ " }].~n",
+ [S1, Host, S2, Host, S3, Host,
+ NC11, Host, NC12, Host, NC13, Host,
+ NC21, Host, NC22, Host, NC23, Host]),
+ file:close(Fd)
+ end,
+
+ WriteConf("nc1.config", [Ncp1,Ncp2,Ncp3], [Ncp1,Ncp2,Ncp3], [Ncpx,Ncpy,Ncpz]),
+ WriteConf("nc2.config", [Ncpx,Ncpy,Ncpz], [Ncp1,Ncp2,Ncp3], [Ncpx,Ncpy,Ncpz]),
+
+ NC1Args = "-config " ++ filename:join(Dir, "nc1"),
+ NC2Args = "-config " ++ filename:join(Dir, "nc2"),
+ Pa = " -pa " ++ filename:dirname(code:which(?MODULE)),
+
+ Nodes = lists:foldl(fun ({Name, Args}, Ns) ->
+ case test_server:start_node(Name,
+ peer,
+ [{args, Args ++ Pa}]) of
+ {ok, N} ->
+ [N|Ns];
+ Err ->
+ stop_nodes(Ns),
+ error({Name, Args, Err})
+ end
+ end,
+ [],
+ [{Ncp1, NC1Args},
+ {Ncp2, NC1Args},
+ {Ncp3, NC1Args},
+ {Ncpx, NC2Args},
+ {Ncpy, NC2Args},
+ {Ncpz, NC2Args},
+ {Nh, "-hidden"},
+ {NProxy, "-hidden"}]),
+ try
+
+ [P, H, Cpz, Cpy, Cpx, Cp3, Cp2, Cp1] = Nodes,
+
+ %% RPC() - An rpc via a hidden proxy node...
+ RPC = fun (N, M, F, A) ->
+ rpc:call(P, rpc, call, [N, M, F, A])
+ end,
+
+ %% The groups
+ NC1Nodes = lists:sort([Cp1, Cp2, Cp3]),
+ NC2Nodes = lists:sort([Cpx, Cpy, Cpz]),
+
+ %% Wait with disconnect from group nodes a while, so we
+ %% don't have any ongoing communication that brings up
+ %% the connections again...
+ receive after 500 -> ok end,
+
+ %% Disconnect test_server from the global group nodes...
+ lists:foreach(fun (N) -> erlang:disconnect_node(N) end, NC1Nodes++NC2Nodes),
+
+ %% wait some more to ensure that global group nodes have synced...
+ receive after 500 -> ok end,
+
+ %% check the global group names
+ {nc1, [nc2]} = RPC(Cp1, global_group, global_groups, []),
+ {nc1, [nc2]} = RPC(Cp2, global_group, global_groups, []),
+ {nc1, [nc2]} = RPC(Cp3, global_group, global_groups, []),
+ {nc2, [nc1]} = RPC(Cpx, global_group, global_groups, []),
+ {nc2, [nc1]} = RPC(Cpy, global_group, global_groups, []),
+ {nc2, [nc1]} = RPC(Cpz, global_group, global_groups, []),
+
+ %% check the global group nodes
+ NC1Nodes = lists:sort(RPC(Cp1, global_group, own_nodes, [])),
+ NC1Nodes = lists:sort(RPC(Cp2, global_group, own_nodes, [])),
+ NC1Nodes = lists:sort(RPC(Cp3, global_group, own_nodes, [])),
+ NC2Nodes = lists:sort(RPC(Cpx, global_group, own_nodes, [])),
+ NC2Nodes = lists:sort(RPC(Cpy, global_group, own_nodes, [])),
+ NC2Nodes = lists:sort(RPC(Cpz, global_group, own_nodes, [])),
+
+ %% Set up connections that should *not* be affected
+ %% by our global:disconnect() call made later (this since
+ %% these connections are not internal in the group). One
+ %% hidden and one visible connection. We don't use the
+ %% proxy node for the hidden connection since it will be
+ %% brought up by our rpc calls if it should have been
+ %% taken down.
+ pong = RPC(H, net_adm, ping, [Cp1]),
+ pong = RPC(Cpx, net_adm, ping, [Cp1]),
+
+ %% Verify that the connections have been set up as
+ %% expected...
+ HiddenNodes = lists:sort([P, H]),
+
+ Cp1ConnectedNodes = lists:sort([P, H, Cpx, Cp2, Cp3]),
+ Cp1VisibleNodes = Cp1ConnectedNodes -- HiddenNodes,
+ Cp2ConnectedNodes = lists:sort([P, Cp1, Cp3]),
+ Cp2VisibleNodes = Cp2ConnectedNodes -- HiddenNodes,
+ Cp3ConnectedNodes = lists:sort([P, Cp1, Cp2]),
+ Cp3VisibleNodes = Cp3ConnectedNodes -- HiddenNodes,
+
+ CpxConnectedNodes = lists:sort([P, Cp1, Cpy, Cpz]),
+ CpxVisibleNodes = CpxConnectedNodes -- HiddenNodes,
+ CpyConnectedNodes = lists:sort([P, Cpx, Cpz]),
+ CpyVisibleNodes = CpyConnectedNodes -- HiddenNodes,
+ CpzConnectedNodes = lists:sort([P, Cpx, Cpy]),
+ CpzVisibleNodes = CpzConnectedNodes -- HiddenNodes,
+
+ Cp1ConnectedNodes = lists:sort(RPC(Cp1, erlang, nodes, [connected])),
+ Cp1VisibleNodes = lists:sort(RPC(Cp1, erlang, nodes, [])),
+ Cp2ConnectedNodes = lists:sort(RPC(Cp2, erlang, nodes, [connected])),
+ Cp2VisibleNodes = lists:sort(RPC(Cp2, erlang, nodes, [])),
+ Cp3ConnectedNodes = lists:sort(RPC(Cp3, erlang, nodes, [connected])),
+ Cp3VisibleNodes = lists:sort(RPC(Cp3, erlang, nodes, [])),
+
+ CpxConnectedNodes = lists:sort(RPC(Cpx, erlang, nodes, [connected])),
+ CpxVisibleNodes = lists:sort(RPC(Cpx, erlang, nodes, [])),
+ CpyConnectedNodes = lists:sort(RPC(Cpy, erlang, nodes, [connected])),
+ CpyVisibleNodes = lists:sort(RPC(Cpy, erlang, nodes, [])),
+ CpzConnectedNodes = lists:sort(RPC(Cpz, erlang, nodes, [connected])),
+ CpzVisibleNodes = lists:sort(RPC(Cpz, erlang, nodes, [])),
+
+ %% Expected disconnects made by global on Cp1...
+ Cp1DisconnectNodes = lists:sort([Cp2, Cp3]),
+
+ %% Perform the global:disconnect() on Cp1...
+ Cp1DisconnectNodes = lists:sort(RPC(Cp1, global, disconnect, [])),
+
+ %% Wait a while giving the other nodes time to react to the disconnects
+ %% before we check that everything is as expected...
+ receive after 2000 -> ok end,
+
+ %% Verify that only the connections Cp1-Cp2 and Cp1-Cp3 were
+ %% taken down...
+ Cp1PostConnectedNodes = Cp1ConnectedNodes -- Cp1DisconnectNodes,
+ Cp1PostVisibleNodes = Cp1PostConnectedNodes -- HiddenNodes,
+ Cp2PostConnectedNodes = Cp2ConnectedNodes -- [Cp1],
+ Cp2PostVisibleNodes = Cp2PostConnectedNodes -- HiddenNodes,
+ Cp3PostConnectedNodes = Cp3ConnectedNodes -- [Cp1],
+ Cp3PostVisibleNodes = Cp3PostConnectedNodes -- HiddenNodes,
+
+ Cp1PostConnectedNodes = lists:sort(RPC(Cp1, erlang, nodes, [connected])),
+ Cp1PostVisibleNodes = lists:sort(RPC(Cp1, erlang, nodes, [])),
+ Cp2PostConnectedNodes = lists:sort(RPC(Cp2, erlang, nodes, [connected])),
+ Cp2PostVisibleNodes = lists:sort(RPC(Cp2, erlang, nodes, [])),
+ Cp3PostConnectedNodes = lists:sort(RPC(Cp3, erlang, nodes, [connected])),
+ Cp3PostVisibleNodes = lists:sort(RPC(Cp3, erlang, nodes, [])),
+
+ CpxConnectedNodes = lists:sort(RPC(Cpx, erlang, nodes, [connected])),
+ CpxVisibleNodes = lists:sort(RPC(Cpx, erlang, nodes, [])),
+ CpyConnectedNodes = lists:sort(RPC(Cpy, erlang, nodes, [connected])),
+ CpyVisibleNodes = lists:sort(RPC(Cpy, erlang, nodes, [])),
+ CpzConnectedNodes = lists:sort(RPC(Cpz, erlang, nodes, [connected])),
+ CpzVisibleNodes = lists:sort(RPC(Cpz, erlang, nodes, []))
+
+ after
+
+ stop_nodes(Nodes)
+
+ end,
+
+ ok.
+
+%%%%% End of test-cases %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start_node(Name, Config) ->
Pa=filename:dirname(code:which(?MODULE)),
@@ -1213,6 +1393,11 @@ node_name(Name, Config) ->
stop_node(Node) ->
test_server:stop_node(Node).
+stop_nodes([]) ->
+ ok;
+stop_nodes([N|Ns]) ->
+ try stop_node(N) catch _:_ -> ok end,
+ stop_nodes(Ns).
wait_for_ready_net() ->
Nodes = lists:sort(?NODES),
@@ -1348,4 +1536,3 @@ loop_until_true(Fun) ->
_ ->
loop_until_true(Fun)
end.
-
--
2.35.3