~brightbox/riak/master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
%% -------------------------------------------------------------------
%%
%%
%% Copyright (c) 2013 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(riak_core_ring_eqc).

-ifdef(EQC).
-export([prop_future_index/0]).

-include_lib("eqc/include/eqc.hrl").
-include_lib("eunit/include/eunit.hrl").

-define(TEST_ITERATIONS, 10000).
-define(QC_OUT(P),
        eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)).


eqc_test_() ->
    {inparallel,
     [{spawn,
       [{setup,
         fun() -> ok end,
         fun(_) -> ok end,
         [
          %% Run the quickcheck tests
          {timeout, 60000, % timeout is in msec
           %% Indicate the number of test iterations for each property here
           ?_assertEqual(true,
                         quickcheck(numtests(?TEST_ITERATIONS,
                                             ?QC_OUT(prop_future_index()))))
          }]}]}]}.



prop_future_index() ->
    ?FORALL({CHashKey, OrigIdx, TargetIdx, N, Pos, Ring}=TransferItem, resize_item(),
            ?WHENFAIL(prop_future_index_failed(TransferItem),
                      collect(N =/= undefined andalso Pos >= N,
                              begin
                                  {Time, Val} = timer:tc(riak_core_ring, future_index,
                                                         [CHashKey, OrigIdx, N, Ring]),
                                  measure(future_index_usec, Time, TargetIdx =:= Val)
                              end))).

resize_item() ->
    %% RingSize - Starting Ring Size
    %% GrowthFactor - >1 expanding, <1 shrinking
    %% IndexStart - first partition in preflist for key
    %% Pos - position of source partition in preflist
    %% N - optional known N-value for the key
    ?LET({RingSize, GrowthF},
         {current_size(), growth_factor()},
         ?LET({IndexStart, Pos, N},
              {index_in_current_ring(RingSize),
               replica_position(RingSize),
               check_nval(RingSize, GrowthF)},
              begin
                  Ring0 = riak_core_ring:fresh(RingSize, node()),
                  Ring1 = riak_core_ring:resize(Ring0,trunc(GrowthF * RingSize)),
                  Ring = riak_core_ring:set_pending_resize(Ring1, Ring0),
                  CHashKey = <<(IndexStart-1):160/integer>>,
                  Preflist = riak_core_ring:preflist(CHashKey, Ring0),
                  FuturePreflist = riak_core_ring:preflist(CHashKey, Ring1),
                  {SourceIdx, _} = lists:nth(Pos+1, Preflist),

                  %% account for case where position is greater than
                  %% future ring size or possbly known N-value
                  %% (shrinking) we shouldn't have a target index in
                  %% that case since a transfer to it would be invalid
                  case Pos >= riak_core_ring:num_partitions(Ring1) orelse
                      (N =/= undefined andalso Pos >= N) of
                      true -> TargetIdx = undefined;
                      false -> {TargetIdx, _} = lists:nth(Pos+1, FuturePreflist)
                  end,
                  {CHashKey, SourceIdx, TargetIdx, N, Pos, Ring}
              end)).

index_in_current_ring(RingSize) ->
    elements([I || {I,_} <- riak_core_ring:all_owners(riak_core_ring:fresh(RingSize, node()))]).

current_size() ->
    elements([16, 32, 64, 128]).

growth_factor() ->
    oneof([0.25, 0.5, 2, 4]).


replica_position(RingSize) ->
    %% use a max position that could be invalid for shrinking (greater than future size)
    %% purposefully to generate negative cases
    Max = RingSize,
    choose(0, (Max - 1)).

check_nval(RingSize, GrowthF) ->
    case GrowthF > 1 of
        %% while expanding the n-value doesn't matter
        true -> undefined;
        %% while shrinking provide a "realistic" n-value of the key
        false -> choose(1, trunc((RingSize * GrowthF) / 2) - 1)
    end.


prop_future_index_failed({CHashKey, OrigIdx, TargetIdx, NValCheck, _, R}) ->
    <<CHashInt:160/integer>> = CHashKey,
    FoundTarget = riak_core_ring:future_index(CHashKey, OrigIdx, NValCheck, R),
    io:format("key: ~p~nsource: ~p~ncurrsize: ~p~nfuturesize: ~p~nexpected: ~p~nactual: ~p~n",
              [CHashInt, OrigIdx,
               riak_core_ring:num_partitions(R), riak_core_ring:future_num_partitions(R),
               TargetIdx, FoundTarget]).

-endif.