WIP: chain manager simulation test

This commit is contained in:
Scott Lystig Fritchie 2014-10-26 16:42:04 +09:00
parent 410c8ff7ce
commit 057f958bb1
3 changed files with 190 additions and 24 deletions

View file

@ -22,12 +22,15 @@
-module(machi_chain_manager). -module(machi_chain_manager).
-export([]). -export([]).
-compile(export_all).
-ifdef(TEST). -ifdef(TEST).
-compile(export_all). -compile(export_all).
-ifdef(EQC). -ifdef(EQC).
-include_lib("eqc/include/eqc.hrl"). -include_lib("eqc/include/eqc.hrl").
-define(QC_OUT(P),
eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)).
-endif. -endif.
-ifdef(PULSE). -ifdef(PULSE).
-compile({parse_transform, pulse_instrument}). -compile({parse_transform, pulse_instrument}).
@ -49,15 +52,15 @@
creation_time :: timestamp(), creation_time :: timestamp(),
author_server :: m_server(), author_server :: m_server(),
all_members :: [m_server()], all_members :: [m_server()],
active_upi :: [m_server()], down :: [m_server()],
active_repairing:: [m_server()], upi :: [m_server()],
repairing :: [m_server()],
dbg :: list() %proplist() dbg :: list() %proplist()
}). }).
-record(state, { -record(state, {
name :: m_server(), name :: m_server(),
proj :: #projection{}, proj :: #projection{},
last_up :: list(m_server()),
%% %%
runenv :: list() %proplist() runenv :: list() %proplist()
}). }).
@ -67,15 +70,14 @@ make_initial_state(MyName, All_list, Seed) ->
{network_partitions, []}], {network_partitions, []}],
#state{name=MyName, #state{name=MyName,
proj=make_initial_projection(MyName, All_list, All_list, [], []), proj=make_initial_projection(MyName, All_list, All_list, [], []),
last_up=All_list,
runenv=RunEnv}. runenv=RunEnv}.
make_initial_projection(MyName, All_list, UPI_list, Repairing_list, Ps) -> make_initial_projection(MyName, All_list, UPI_list, Repairing_list, Ps) ->
make_projection(1, 0, <<>>, make_projection(1, 0, <<>>,
MyName, All_list, UPI_list, Repairing_list, Ps). MyName, All_list, [], UPI_list, Repairing_list, Ps).
make_projection(EpochNum, PrevEpochNum, PrevEpochCSum, make_projection(EpochNum, PrevEpochNum, PrevEpochCSum,
MyName, All_list, UPI_list, Repairing_list, Ps) -> MyName, All_list, Down_list, UPI_list, Repairing_list, Ps) ->
P = #projection{epoch_number=EpochNum, P = #projection{epoch_number=EpochNum,
epoch_csum= <<>>, epoch_csum= <<>>,
prev_epoch_num=PrevEpochNum, prev_epoch_num=PrevEpochNum,
@ -83,25 +85,57 @@ make_projection(EpochNum, PrevEpochNum, PrevEpochCSum,
creation_time=now(), creation_time=now(),
author_server=MyName, author_server=MyName,
all_members=All_list, all_members=All_list,
active_upi=UPI_list, down=Down_list,
active_repairing=Repairing_list, upi=UPI_list,
repairing=Repairing_list,
dbg=Ps}, dbg=Ps},
CSum = crypto:hash(sha, term_to_binary(P)), CSum = crypto:hash(sha, term_to_binary(P)),
P#projection{epoch_csum=CSum}. P#projection{epoch_csum=CSum}.
%% OldThreshold: Percent chance of using the old/previous network partition list
%% NoPartitionThreshold: If the network partition changes, what are the odds
%% that there are no partitions at all?
calc_projection(OldThreshold, NoPartitionThreshold, calc_projection(OldThreshold, NoPartitionThreshold,
#state{name=MyName, last_up=_LastUp, proj=LastProj, #state{name=MyName, proj=LastProj, runenv=RunEnv1} = S) ->
runenv=RunEnv1} = S) ->
#projection{epoch_number=OldEpochNum, #projection{epoch_number=OldEpochNum,
epoch_csum=OldEpochCsum, epoch_csum=OldEpochCsum,
all_members=All_list all_members=All_list,
upi=OldUPI_list,
repairing=OldRepairing_list
} = LastProj, } = LastProj,
LastUp = lists:usort(OldUPI_list ++ OldRepairing_list),
AllMembers = (S#state.proj)#projection.all_members, AllMembers = (S#state.proj)#projection.all_members,
{Up, RunEnv2} = calc_up_nodes(MyName, OldThreshold, NoPartitionThreshold, {Up, RunEnv2} = calc_up_nodes(MyName, OldThreshold, NoPartitionThreshold,
AllMembers, RunEnv1), AllMembers, RunEnv1),
NewUp = Up -- LastUp,
Down = AllMembers -- Up,
NewUPI_list = [X || X <- OldUPI_list, lists:member(X, Up)],
Repairing_list2 = [X || X <- OldRepairing_list, lists:member(X, Up)],
{NewUPI_list3, Repairing_list3, RunEnv3} =
case {NewUp, Repairing_list2} of
{[], []} ->
{NewUPI_list, [], RunEnv2};
{[], [H|T]} ->
{Prob, RunEnvX} = roll_dice(100, RunEnv2),
if Prob =< 50 ->
{NewUPI_list ++ [H], T, RunEnvX};
true ->
{NewUPI_list, OldRepairing_list, RunEnvX}
end;
{_, _} ->
{NewUPI_list, OldRepairing_list, RunEnv2}
end,
Repairing_list4 = case NewUp of
[] -> Repairing_list3;
NewUp -> Repairing_list3 ++ NewUp
end,
Repairing_list5 = Repairing_list4 -- Down,
P = make_projection(OldEpochNum + 1, OldEpochNum, OldEpochCsum, P = make_projection(OldEpochNum + 1, OldEpochNum, OldEpochCsum,
MyName, All_list, Up, [], [{fubar,true}]), MyName, All_list, Down, NewUPI_list3, Repairing_list5,
{P, S#state{runenv=RunEnv2}}. [goo]),
{P, S#state{runenv=RunEnv3}}.
calc_up_nodes(MyName, OldThreshold, NoPartitionThreshold, calc_up_nodes(MyName, OldThreshold, NoPartitionThreshold,
AllMembers, RunEnv1) -> AllMembers, RunEnv1) ->
@ -121,18 +155,17 @@ calc_up_nodes(MyName, OldThreshold, NoPartitionThreshold,
calc_network_partitions(Nodes, Seed1, OldPartition, calc_network_partitions(Nodes, Seed1, OldPartition,
OldThreshold, NoPartitionThreshold) -> OldThreshold, NoPartitionThreshold) ->
{F2, Seed2} = random:uniform_s(Seed1), {Cutoff2, Seed2} = random:uniform_s(100,Seed1),
Cutoff2 = trunc(F2 * 100),
if Cutoff2 < OldThreshold -> if Cutoff2 < OldThreshold ->
{Seed2, OldPartition};
true ->
{F3, Seed3} = random:uniform_s(Seed1), {F3, Seed3} = random:uniform_s(Seed1),
Cutoff3 = trunc(F3 * 100), Cutoff3 = trunc(F3 * 100),
if Cutoff3 < NoPartitionThreshold -> if Cutoff3 < NoPartitionThreshold ->
{Seed3, []}; {Seed3, []};
true -> true ->
make_network_partition_locations(Nodes, Seed3) make_network_partition_locations(Nodes, Seed3)
end; end
true ->
{Seed2, OldPartition}
end. end.
replace(PropList, Items) -> replace(PropList, Items) ->
@ -141,10 +174,19 @@ replace(PropList, Items) ->
end, PropList, Items). end, PropList, Items).
make_projection_summary(#projection{epoch_number=EpochNum, make_projection_summary(#projection{epoch_number=EpochNum,
all_members=All_list, all_members=_All_list,
active_upi=UPI_list, down=Down_list,
active_repairing=Repairing_list}) -> upi=UPI_list,
[{epoch,EpochNum},{all,All_list},{upi,UPI_list},{repair,Repairing_list}]. repairing=Repairing_list}) ->
[{epoch,EpochNum},
{upi,UPI_list},{repair,Repairing_list},{down,Down_list}].
%% [{epoch,EpochNum},{all,All_list},{down,Down_list},
%% {upi,UPI_list},{repair,Repairing_list}].
roll_dice(N, RunEnv) ->
Seed1 = proplists:get_value(seed, RunEnv),
{Val, Seed2} = random:uniform_s(N, Seed1),
{Val, replace(RunEnv, [{seed, Seed2}])}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@ -175,12 +217,112 @@ make_all_pairs2([H1|T]) ->
-ifdef(TEST). -ifdef(TEST).
smoke0_test() -> smoke0_test() ->
S0 = ?MODULE:make_initial_state(a, [a,b,c], {4,5,6}), S0 = ?MODULE:make_initial_state(a, [a,b,c,d], {4,5,6}),
lists:foldl(fun(_, S) -> lists:foldl(fun(_, S) ->
{P1, S1} = calc_projection(555, 0, S), {P1, S1} = calc_projection(20, 1, S),
io:format(user, "~p\n", [make_projection_summary(P1)]), io:format(user, "~p\n", [make_projection_summary(P1)]),
S1#state{proj=P1} S1#state{proj=P1}
end, S0, lists:seq(1,10)). end, S0, lists:seq(1,10)).
-ifdef(EQC).
gen_rand_seed() ->
noshrink({gen_num(), gen_num(), gen_num()}).
gen_num() ->
?LET(I, oneof([int(), largeint()]),
erlang:abs(I)).
calc_projection_prop() ->
?FORALL(
{Seed, OldThreshold, NoPartitionThreshold},
{gen_rand_seed(), choose(0, 101), choose(0,101)},
begin
Steps = 100,
S0 = ?MODULE:make_initial_state(a, [a,b,c,d], Seed),
F = fun(_, {S, Acc}) ->
{P1, S1} = calc_projection(OldThreshold,
NoPartitionThreshold, S),
%%io:format(user, "~p\n", [make_projection_summary(P1)]),
{S1#state{proj=P1}, [P1|Acc]}
end,
{_, Projs0} = lists:foldl(F, {S0, []}, lists:seq(1,Steps)),
Projs = lists:reverse(Projs0),
true = projection_transitions_are_sane(Projs)
end).
calc_projection_test_() ->
{timeout, 60,
fun() ->
true = eqc:quickcheck(eqc:numtests(500,
?QC_OUT(calc_projection_prop())))
end}.
projection_transitions_are_sane([]) ->
true;
projection_transitions_are_sane([_]) ->
true;
projection_transitions_are_sane([P1, P2|T]) ->
case projection_transition_is_sane(P1, P2) of
true ->
projection_transitions_are_sane([P2|T]);
Else ->
Else
end.
projection_transition_is_sane(
#projection{epoch_number=Epoch1,
epoch_csum=CSum1,
prev_epoch_num=PrevEpoch1,
prev_epoch_csum=PrevCSum1,
creation_time=CreationTime1,
author_server=AuthorServer1,
all_members=All_list1,
down=Down_list1,
upi=UPI_list1,
repairing=Repairing_list1,
dbg=Dbg1} = _P1,
#projection{epoch_number=Epoch2,
epoch_csum=CSum2,
prev_epoch_num=PrevEpoch2,
prev_epoch_csum=PrevCSum2,
creation_time=CreationTime2,
author_server=AuthorServer2,
all_members=All_list2,
down=Down_list2,
upi=UPI_list2,
repairing=Repairing_list2,
dbg=Dbg2} = _P2) ->
true = is_integer(Epoch1) andalso is_integer(Epoch2),
true = is_binary(CSum1) andalso is_binary(CSum2),
true = is_integer(PrevEpoch1) andalso is_integer(PrevEpoch2),
true = is_binary(PrevCSum1) andalso is_binary(PrevCSum2),
{_,_,_} = CreationTime1,
{_,_,_} = CreationTime2,
true = is_atom(AuthorServer1) andalso is_atom(AuthorServer2), % todo will probably change
true = is_list(All_list1) andalso is_list(All_list2),
true = is_list(Down_list1) andalso is_list(Down_list2),
true = is_list(UPI_list1) andalso is_list(UPI_list2),
true = is_list(Repairing_list1) andalso is_list(Repairing_list2),
true = is_list(Dbg1) andalso is_list(Dbg2),
true = Epoch2 > Epoch1,
true = PrevEpoch2 > PrevEpoch1,
All_list1 = All_list2,
true = lists:sort(Down_list2) == lists:usort(Down_list2),
[] = [X || X <- Down_list2, not lists:member(X, All_list2)],
true = lists:sort(UPI_list2) == lists:usort(UPI_list2),
[] = [X || X <- UPI_list2, not lists:member(X, All_list2)],
true = lists:sort(Repairing_list2) == lists:usort(Repairing_list2),
[] = [X || X <- Repairing_list2, not lists:member(X, All_list2)],
true = lists:sort(All_list2) == lists:sort(Down_list2 ++ UPI_list2 ++
Repairing_list2),
true.
-endif. % EQC
%% [begin %% [begin
%% %%io:format(user, "S0 ~p\n", [S0]), %% %%io:format(user, "S0 ~p\n", [S0]),
%% {P1, _S1} = calc_projection(555, 0, S0), %% {P1, _S1} = calc_projection(555, 0, S0),

View file

@ -28,6 +28,7 @@
-export([start_link/1, stop/1, -export([start_link/1, stop/1,
write/3, read/2, trim/2, write/3, read/2, trim/2,
proj_write/3, proj_read/2, proj_get_latest_num/1, proj_read_latest/1]). proj_write/3, proj_read/2, proj_get_latest_num/1, proj_read_latest/1]).
-export([set_fake_repairing_status/2, get_fake_repairing_status/1]).
-export([make_proj/1, make_proj/2]). -export([make_proj/1, make_proj/2]).
-ifdef(TEST). -ifdef(TEST).
@ -56,6 +57,7 @@
name :: list(), name :: list(),
wedged = false :: boolean(), wedged = false :: boolean(),
register = 'unwritten' :: register(), register = 'unwritten' :: register(),
fake_repairing = false :: boolean(),
proj_epoch :: non_neg_integer(), proj_epoch :: non_neg_integer(),
proj_store :: dict() proj_store :: dict()
}). }).
@ -87,6 +89,12 @@ proj_get_latest_num(Pid) ->
proj_read_latest(Pid) -> proj_read_latest(Pid) ->
g_call(Pid, {proj_read_latest}, ?LONG_TIME). g_call(Pid, {proj_read_latest}, ?LONG_TIME).
set_fake_repairing_status(Pid, Status) ->
gen_server:call(Pid, {set_fake_repairing_status, Status}, ?LONG_TIME).
get_fake_repairing_status(Pid) ->
gen_server:call(Pid, {get_fake_repairing_status}, ?LONG_TIME).
g_call(Pid, Arg, Timeout) -> g_call(Pid, Arg, Timeout) ->
LC1 = lclock_get(), LC1 = lclock_get(),
{Res, LC2} = gen_server:call(Pid, {Arg, LC1}, Timeout), {Res, LC2} = gen_server:call(Pid, {Arg, LC1}, Timeout),
@ -176,6 +184,10 @@ handle_call({{proj_read_latest}, LC1}, _From, S) ->
handle_call({stop, LC1}, _From, MLP) -> handle_call({stop, LC1}, _From, MLP) ->
LC2 = lclock_update(LC1), LC2 = lclock_update(LC1),
{stop, normal, {ok, LC2}, MLP}; {stop, normal, {ok, LC2}, MLP};
handle_call({set_fake_repairing_status, Status}, _From, S) ->
{reply, ok, S#state{fake_repairing=Status}};
handle_call({get_fake_repairing_status}, _From, S) ->
{reply, S#state.fake_repairing, S};
handle_call(_Request, _From, MLP) -> handle_call(_Request, _From, MLP) ->
Reply = whaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, Reply = whaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
{reply, Reply, MLP}. {reply, Reply, MLP}.

View file

@ -29,6 +29,18 @@
-endif. -endif.
-ifdef(TEST). -ifdef(TEST).
repair_status_test() ->
{ok, F} = machi_flu0:start_link("one"),
try
ok = machi_flu0:set_fake_repairing_status(F, true),
true = machi_flu0:get_fake_repairing_status(F),
ok = machi_flu0:set_fake_repairing_status(F, false),
false = machi_flu0:get_fake_repairing_status(F)
after
ok = machi_flu0:stop(F)
end.
-ifndef(PULSE). -ifndef(PULSE).
concuerror1_test() -> concuerror1_test() ->