Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions src/classy_partition.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ This module contains various algorithms for calculating network partitions.
""".

%% API:
-export([ bidi_link/3
-export([ unid_link/3
, bidi_link/3
, full_meshes/1
]).

Expand All @@ -29,6 +30,27 @@ This module contains various algorithms for calculating network partitions.
%% API functions
%%================================================================================

-doc """
Return @code{@{ok, Value@}} if node A is alive and appears in @code{ClusterInfo}.
Then the value indicates whether A is currently connected to B.

Error tuple is returned if A is unreachable.
""".
-spec unid_link(classy:cluster_info(), node(), node()) -> {ok, boolean()} | {error, _}.
unid_link(ClusterInfo, NodeA, NodeB) ->
maybe
#{infos := #{NodeA := #{peers := PeersA}}} ?= ClusterInfo,
case find_peer_by_nodeid(NodeB, PeersA) of
{_Site, #{connected := Conn}} ->
{ok, Conn};
undefined ->
{ok, false}
end
else
_ ->
{error, insufficient_data}
end.

-doc """
Return @code{@{ok, true@}} if nodes are mutually connected to each other,
or @code{@{ok, false@}} when either node considers the other disconnected.
Expand All @@ -40,6 +62,7 @@ then an error tuple is returned.
bidi_link(ClusterInfo, NodeA, NodeB) ->
case ClusterInfo of
#{infos := #{NodeA := A, NodeB := B}} ->
%% Simple case: both nodes are present in the results:
#{site := SiteA, peers := PeersA} = A,
#{site := SiteB, peers := PeersB} = B,
maybe
Expand All @@ -51,7 +74,15 @@ bidi_link(ClusterInfo, NodeA, NodeB) ->
{ok, false}
end;
_ ->
{error, insufficient_data}
%% We may still prove that bidi link *doesn't* exist if either node
%% reports the other as disconnected:
case unid_link(ClusterInfo, NodeA, NodeB) =:= {ok, false} orelse
unid_link(ClusterInfo, NodeB, NodeA) =:= {ok, false} of
true ->
{ok, false};
false ->
{error, insufficient_data}
end
end.

-doc """
Expand Down Expand Up @@ -122,6 +153,22 @@ sort_partitions(Partitions0) ->
} || I <- Partitions],
[I || {_, _, I} <- lists:sort(L)].

-spec find_peer_by_nodeid(node(), #{classy:site() => classy:peer_info()}) ->
{classy:site(), classy:peer_info()} | undefined.
find_peer_by_nodeid(Node, Infos) when is_map(Infos) ->
do_find_peer_by_nodeid(Node, maps:iterator(Infos)).

do_find_peer_by_nodeid(Node, It0) ->
case maps:next(It0) of
{Site, PeerInfo, It} ->
case PeerInfo of
#{node := Node} -> {Site, PeerInfo};
#{} -> do_find_peer_by_nodeid(Node, It)
end;
none ->
undefined
end.

-ifdef(TEST).

full_mesh_test() ->
Expand Down
51 changes: 49 additions & 2 deletions test/classy_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ t_091_node_of_site(_) ->
, fun events_on_all_sites/1
]).

t_092_bidi_link(_) ->
t_092_link_detect(_) ->
S1 = <<"s1">>,
S2 = <<"s2">>,
Sites = [S1, S2],
Expand All @@ -595,10 +595,25 @@ t_092_bidi_link(_) ->
N2 = create_start_site(S2, #{}),
#{cluster := Cluster} = ?ON(S1, classy_node:hello()),
CInfo1 = classy:info([N1, N2]),
%% Sites are disconnected:
%% Bidi:
?assertEqual(
{ok, false},
classy_partition:bidi_link(CInfo1, N1, N2),
CInfo1),
?assertEqual(
{ok, false},
classy_partition:bidi_link(CInfo1, N2, N1),
CInfo1),
%% Unid:
?assertEqual(
{ok, false},
classy_partition:unid_link(CInfo1, N1, N2),
CInfo1),
?assertEqual(
{ok, false},
classy_partition:unid_link(CInfo1, N2, N1),
CInfo1),
%% Join sites:
?ON(S2, classy:join_node(N1, join)),
wait_site_joined(Sites, Cluster, S2),
Expand All @@ -608,13 +623,45 @@ t_092_bidi_link(_) ->
{ok, true},
classy_partition:bidi_link(CInfo2, N1, N2),
CInfo2),
?assertEqual(
{ok, true},
classy_partition:bidi_link(CInfo2, N2, N1),
CInfo2),
%% Uni-d as well:
?assertEqual(
{ok, true},
classy_partition:unid_link(CInfo2, N1, N2),
CInfo2),
?assertEqual(
{ok, true},
classy_partition:unid_link(CInfo2, N2, N1),
CInfo2),
%% Stop one of the sites:
stop_site(S2),
?block_until(#{?snk_kind := classy_peer_disconnected, remote := S2}),
CInfo3 = classy:info([N1, N2]),
%% We can still derive that there's no bidirectional link:
?assertEqual(
{error, insufficient_data},
{ok, false},
classy_partition:bidi_link(CInfo3, N1, N2),
CInfo3),
?assertEqual(
{ok, false},
classy_partition:bidi_link(CInfo3, N2, N1),
CInfo3),
%% Unid:
?assertEqual(
{ok, false},
classy_partition:unid_link(CInfo3, N1, N2),
CInfo3),
?assertEqual(
{error, insufficient_data},
classy_partition:unid_link(CInfo3, N2, N1),
CInfo3),
%% Both sites are missing:
?assertEqual(
{error, insufficient_data},
classy_partition:bidi_link(CInfo3, 'missing1@badhost', 'missing2@badhost'),
CInfo3)
end,
[ fun no_unexpected_events/1
Expand Down
Loading