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

openSUSE Build Service is sponsored by