245 lines
8.5 KiB
Erlang
245 lines
8.5 KiB
Erlang
%% -------------------------------------------------------------------
|
|
%%
|
|
%% Machi: a small village of replicated files
|
|
%%
|
|
%% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved.
|
|
%%
|
|
%% This file is provided to you under the Apache License,
|
|
%% Version 2.0 (the "License"); you may not use this file
|
|
%% except in compliance with the License. You may obtain
|
|
%% a copy of the License at
|
|
%%
|
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
%%
|
|
%% Unless required by applicable law or agreed to in writing,
|
|
%% software distributed under the License is distributed on an
|
|
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
%% KIND, either express or implied. See the License for the
|
|
%% specific language governing permissions and limitations
|
|
%% under the License.
|
|
%%
|
|
%% -------------------------------------------------------------------
|
|
-module(machi_partition_simulator).
|
|
|
|
-behaviour(gen_server).
|
|
|
|
-ifdef(TEST).
|
|
|
|
-ifdef(EQC).
|
|
-include_lib("eqc/include/eqc.hrl").
|
|
-endif.
|
|
-ifdef(PULSE).
|
|
-compile({parse_transform, pulse_instrument}).
|
|
-endif.
|
|
|
|
-export([start_link/3, stop/0,
|
|
get/1, reset_thresholds/2, set_seed/1,
|
|
no_partitions/0, always_last_partitions/0, always_these_partitions/1]).
|
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
|
terminate/2, code_change/3]).
|
|
|
|
-export([islands2partitions/1,
|
|
partition2connection/2,
|
|
connection2partition/2,
|
|
partitions2num_islands/2,
|
|
partition_list_is_symmetric_p/2]).
|
|
|
|
-define(TAB, ?MODULE).
|
|
|
|
-record(state, {
|
|
seed,
|
|
old_partitions,
|
|
old_threshold,
|
|
no_partition_threshold,
|
|
method=oneway_partitions :: 'island' | 'oneway_partitions'
|
|
}).
|
|
|
|
start_link(Seed, OldThreshold, NoPartitionThreshold) ->
|
|
gen_server:start_link({local, ?MODULE}, ?MODULE,
|
|
{Seed, OldThreshold, NoPartitionThreshold}, []).
|
|
|
|
stop() ->
|
|
gen_server:call(?MODULE, {stop}, infinity).
|
|
|
|
get(Nodes) ->
|
|
gen_server:call(?MODULE, {get, Nodes}, infinity).
|
|
|
|
reset_thresholds(OldThreshold, NoPartitionThreshold) ->
|
|
gen_server:call(?MODULE, {reset_thresholds, OldThreshold, NoPartitionThreshold}, infinity).
|
|
|
|
set_seed(Seed) ->
|
|
gen_server:call(?MODULE, {set_seed, Seed}, infinity).
|
|
|
|
no_partitions() ->
|
|
reset_thresholds(-999, 999).
|
|
|
|
always_last_partitions() ->
|
|
reset_thresholds(999, 0).
|
|
|
|
always_these_partitions(Parts) ->
|
|
reset_thresholds(999, 0),
|
|
gen_server:call(?MODULE, {always_these_partitions, Parts}, infinity).
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
|
init({Seed, OldThreshold, NoPartitionThreshold}) ->
|
|
{ok, #state{seed=Seed,
|
|
old_partitions={[],[[]]},
|
|
old_threshold=OldThreshold,
|
|
no_partition_threshold=NoPartitionThreshold}}.
|
|
|
|
handle_call({get, Nodes}, _From, S) ->
|
|
{Seed2, Partitions} =
|
|
calc_network_partitions(S#state.method,
|
|
Nodes,
|
|
S#state.seed,
|
|
S#state.old_partitions,
|
|
S#state.old_threshold,
|
|
S#state.no_partition_threshold),
|
|
{reply, Partitions, S#state{seed=Seed2,
|
|
old_partitions=Partitions}};
|
|
handle_call({reset_thresholds, OldThreshold, NoPartitionThreshold}, _From, S) ->
|
|
{reply, ok, S#state{old_threshold=OldThreshold,
|
|
no_partition_threshold=NoPartitionThreshold}};
|
|
handle_call({set_seed, Seed}, _From, S) ->
|
|
{reply, ok, S#state{seed=Seed}};
|
|
handle_call({always_these_partitions, Parts}, _From, S) ->
|
|
{reply, ok, S#state{old_partitions={Parts,[na_reset_by_always]}}};
|
|
handle_call({stop}, _From, S) ->
|
|
{stop, normal, ok, S}.
|
|
|
|
handle_cast(_Cast, S) ->
|
|
{noreply, S}.
|
|
|
|
handle_info(_Info, S) ->
|
|
{noreply, S}.
|
|
|
|
terminate(_Reason, _S) ->
|
|
ok.
|
|
|
|
code_change(_OldVsn, S, _Extra) ->
|
|
{ok, S}.
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
|
calc_network_partitions(Method, Nodes, Seed1, OldPartition,
|
|
OldThreshold, NoPartitionThreshold) ->
|
|
{Cutoff2, Seed2} = random:uniform_s(100, Seed1),
|
|
if Cutoff2 < OldThreshold ->
|
|
{Seed2, OldPartition};
|
|
true ->
|
|
{Cutoff3, Seed3} = random:uniform_s(100, Seed1),
|
|
if Cutoff3 < NoPartitionThreshold ->
|
|
{Seed3, {[], [Nodes]}};
|
|
true ->
|
|
make_network_partition_locations(Method, Nodes, Seed3)
|
|
end
|
|
end.
|
|
|
|
make_network_partition_locations(island=_Method, Nodes, Seed1) ->
|
|
Num = length(Nodes),
|
|
{Seed2, WeightsNodes} = lists:foldl(
|
|
fun(Node, {Seeda, Acc}) ->
|
|
{Cutoff0, Seedb} =
|
|
random:uniform_s(100, Seeda),
|
|
Cutoff = erlang:max(
|
|
2, if Cutoff0 rem 4 == 0 ->
|
|
0;
|
|
true ->
|
|
Cutoff0
|
|
end),
|
|
{Seedb, [{Cutoff, Node}|Acc]}
|
|
end, {Seed1, []}, Nodes),
|
|
IslandSep = 100 div Num,
|
|
Islands = [
|
|
lists:sort([Nd || {Weight, Nd} <- WeightsNodes,
|
|
(Max - IslandSep) =< Weight, Weight < Max])
|
|
|| Max <- lists:seq(IslandSep + 1, 105, IslandSep)],
|
|
{Seed2, {lists:usort(islands2partitions(Islands)), lists:sort(Islands)}};
|
|
make_network_partition_locations(oneway_partitions=_Method, Nodes, Seed1) ->
|
|
Pairs = make_all_pairs(Nodes),
|
|
Num = length(Pairs),
|
|
{Seed2, Weights} = lists:foldl(
|
|
fun(_, {Seeda, Acc}) ->
|
|
{Cutoff, Seedb} = random:uniform_s(100, Seeda),
|
|
{Seedb, [Cutoff|Acc]}
|
|
end, {Seed1, []}, lists:seq(1, Num)),
|
|
{Cutoff3, Seed3} = random:uniform_s(100, Seed2),
|
|
{Seed3, {[X || {Weight, X} <- lists:zip(Weights, Pairs),
|
|
Weight < Cutoff3], [islands_not_supported]}}.
|
|
|
|
make_all_pairs(L) ->
|
|
lists:flatten(make_all_pairs2(lists:usort(L))).
|
|
|
|
make_all_pairs2([]) ->
|
|
[];
|
|
make_all_pairs2([_]) ->
|
|
[];
|
|
make_all_pairs2([H1|T]) ->
|
|
[[{H1, X}, {X, H1}] || X <- T] ++ make_all_pairs(T).
|
|
|
|
islands2partitions([]) ->
|
|
[];
|
|
islands2partitions([Island|Rest]) ->
|
|
[{X,Y} || X <- Island,
|
|
Y <- lists:append(Rest), X /= Y]
|
|
++
|
|
[{Y,X} || X <- Island,
|
|
Y <- lists:append(Rest), X /= Y]
|
|
++
|
|
islands2partitions(Rest).
|
|
|
|
partition2connection(Members0, Partition0) ->
|
|
p2c_invert(lists:usort(Members0), lists:usort(Partition0)).
|
|
|
|
connection2partition(Members0, Partition0) ->
|
|
p2c_invert(lists:usort(Members0), lists:usort(Partition0)).
|
|
|
|
p2c_invert(Members, Partition_list_Or_Connection_list) ->
|
|
All = [{X,Y} || X <- Members, Y <- Members, X /= Y],
|
|
All -- Partition_list_Or_Connection_list.
|
|
|
|
partitions2num_islands(Members0, Partition0) ->
|
|
%% Ignore duplicates in either arg, if any.
|
|
Members = lists:usort(Members0),
|
|
Partition = lists:usort(Partition0),
|
|
|
|
Connections = partition2connection(Members, Partition),
|
|
Cs = [lists:member({X,Y}, Connections)
|
|
orelse
|
|
lists:member({Y,X}, Connections) || X <- Members, Y <- Members,
|
|
X /= Y],
|
|
case lists:usort(Cs) of
|
|
[true] -> 1;
|
|
[false] -> many;
|
|
[false, true] -> many % TODO too lazy to finish
|
|
end.
|
|
|
|
partition_list_is_symmetric_p(Members0, Partition0) ->
|
|
%% %% Ignore duplicates in either arg, if any.
|
|
Members = lists:usort(Members0),
|
|
NumMembers = length(Members),
|
|
Partition = lists:usort(Partition0),
|
|
|
|
NewDict = lists:foldl(
|
|
fun({A,B}, Dict) ->
|
|
Key = if A > B -> {A,B};
|
|
true -> {B,A}
|
|
end,
|
|
orddict:update_counter(Key, 1, Dict)
|
|
end, orddict:new(), Partition),
|
|
AllOddP = orddict:fold(
|
|
fun(_Key, Count, true) when Count rem 2 == 0 ->
|
|
true;
|
|
(_, _, _) ->
|
|
false
|
|
end, true, NewDict),
|
|
if not AllOddP ->
|
|
false;
|
|
true ->
|
|
TwosCount = [Key || {Key, Count} <- orddict:to_list(NewDict),
|
|
Count == 2],
|
|
length(TwosCount) >= (NumMembers - 1)
|
|
end.
|
|
|
|
-endif. % TEST
|