2015-04-02 07:05:06 +00:00
|
|
|
%% -------------------------------------------------------------------
|
|
|
|
%%
|
|
|
|
%% Copyright (c) 2007-2015 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.
|
|
|
|
%%
|
|
|
|
%% -------------------------------------------------------------------
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc API for manipulating Machi projection data structures (i.e., records).
|
|
|
|
|
2015-04-02 07:05:06 +00:00
|
|
|
-module(machi_projection).
|
|
|
|
|
|
|
|
-include("machi_projection.hrl").
|
|
|
|
|
|
|
|
-export([
|
|
|
|
new/6, new/7, new/8,
|
2015-04-09 08:13:38 +00:00
|
|
|
update_checksum/1,
|
|
|
|
update_dbg2/2,
|
2015-04-02 07:05:06 +00:00
|
|
|
compare/2,
|
2015-04-09 08:13:38 +00:00
|
|
|
make_summary/1,
|
2015-04-09 03:16:58 +00:00
|
|
|
make_members_dict/1
|
2015-04-02 07:05:06 +00:00
|
|
|
]).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Create a new projection record.
|
|
|
|
|
2015-04-09 03:16:58 +00:00
|
|
|
new(MyName, MemberDict, UPI_list, Down_list, Repairing_list, Ps) ->
|
|
|
|
new(0, MyName, MemberDict, Down_list, UPI_list, Repairing_list, Ps).
|
2015-04-02 07:05:06 +00:00
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Create a new projection record.
|
|
|
|
|
2015-04-09 03:16:58 +00:00
|
|
|
new(EpochNum, MyName, MemberDict, Down_list, UPI_list, Repairing_list, Dbg) ->
|
|
|
|
new(EpochNum, MyName, MemberDict, Down_list, UPI_list, Repairing_list,
|
2015-04-02 07:05:06 +00:00
|
|
|
Dbg, []).
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Create a new projection record.
|
2015-04-09 03:16:58 +00:00
|
|
|
%%
|
|
|
|
%% The `MemberDict0' argument may be a true `p_srvr_dict()' (i.e, it
|
|
|
|
%% is a well-formed `orddict' with the correct 2-tuple key-value form)
|
|
|
|
%% or it may be simply `list(p_srvr())', in which case we'll convert it
|
|
|
|
%% to a `p_srvr_dict()'.
|
2015-04-08 05:24:07 +00:00
|
|
|
|
2015-04-30 08:28:43 +00:00
|
|
|
new(EpochNum, MyName, [_|_] = MembersDict0, Down_list, UPI_list, Repairing_list,
|
2015-04-02 07:05:06 +00:00
|
|
|
Dbg, Dbg2)
|
|
|
|
when is_integer(EpochNum), EpochNum >= 0,
|
|
|
|
is_atom(MyName) orelse is_binary(MyName),
|
2015-04-09 08:13:38 +00:00
|
|
|
is_list(MembersDict0), is_list(Down_list), is_list(UPI_list),
|
2015-04-02 07:05:06 +00:00
|
|
|
is_list(Repairing_list), is_list(Dbg), is_list(Dbg2) ->
|
2015-04-09 08:13:38 +00:00
|
|
|
MembersDict = make_members_dict(MembersDict0),
|
2015-04-09 03:16:58 +00:00
|
|
|
All_list = [Name || {Name, _P} <- MembersDict],
|
Chain manager bug fixes & enhancment (more...)
* Set max length of a chain at -define(MAX_CHAIN_LENGTH, 64).
* Perturb tick sleep time of each manager
* If a chain manager L has zero members in its chain, and then its local
public projection store (authored by some remote author R) has a projection
that contains L, then adopt R's projection and start humming consensus.
* Handle "cross-talk" across projection stores, when chain membership
is changed administratively, e.g. chain was [a,b,c] then changed to merely
[a], but that change only happens on a. Servers b & c continue to use
stale projections and scribble their projection suggestions to a, causing
it to flap.
What's really cool about the flapping handling is that it *works*. I
wasn't thinking about this scenario when designing the flapping logic, but
it's really nifty that this extra scenario causes a to flap and then a's
inner projection remains stable, yay!
* Add complaints when "cross-talk" is observed.
* Fix flapping sleep time throttle.
* Fix bug in the machi_projection_store.erl's bookkeeping of the
max epoch number when flapping.
2015-05-11 09:41:45 +00:00
|
|
|
if length(All_list) =< ?MAX_CHAIN_LENGTH ->
|
|
|
|
ok;
|
|
|
|
true ->
|
|
|
|
exit(max_chain_length_error)
|
|
|
|
end,
|
2015-04-02 07:05:06 +00:00
|
|
|
true = lists:all(fun(X) when is_atom(X) orelse is_binary(X) -> true;
|
|
|
|
(_) -> false
|
|
|
|
end, All_list),
|
|
|
|
[true = lists:sort(SomeList) == lists:usort(SomeList) ||
|
|
|
|
SomeList <- [All_list, Down_list, UPI_list, Repairing_list] ],
|
|
|
|
AllSet = ordsets:from_list(All_list),
|
|
|
|
DownSet = ordsets:from_list(Down_list),
|
|
|
|
UPISet = ordsets:from_list(UPI_list),
|
|
|
|
RepairingSet = ordsets:from_list(Repairing_list),
|
|
|
|
|
|
|
|
true = ordsets:is_element(MyName, AllSet),
|
|
|
|
true = (AllSet == ordsets:union([DownSet, UPISet, RepairingSet])),
|
|
|
|
true = ordsets:is_disjoint(DownSet, UPISet),
|
|
|
|
true = ordsets:is_disjoint(DownSet, RepairingSet),
|
|
|
|
true = ordsets:is_disjoint(UPISet, RepairingSet),
|
|
|
|
|
|
|
|
P = #projection_v1{epoch_number=EpochNum,
|
|
|
|
creation_time=now(),
|
|
|
|
author_server=MyName,
|
|
|
|
all_members=All_list,
|
2015-04-09 03:16:58 +00:00
|
|
|
members_dict=MembersDict,
|
2015-04-02 07:05:06 +00:00
|
|
|
down=Down_list,
|
|
|
|
upi=UPI_list,
|
|
|
|
repairing=Repairing_list,
|
|
|
|
dbg=Dbg
|
|
|
|
},
|
2015-04-30 08:28:43 +00:00
|
|
|
update_dbg2(update_checksum(P), Dbg2);
|
|
|
|
new(EpochNum, MyName, [] = _MembersDict0, _Down_list, _UPI_list,_Repairing_list,
|
|
|
|
Dbg, Dbg2)
|
|
|
|
when is_integer(EpochNum), EpochNum >= 0,
|
|
|
|
is_atom(MyName) orelse is_binary(MyName) ->
|
|
|
|
P = #projection_v1{epoch_number=EpochNum,
|
|
|
|
creation_time=now(),
|
|
|
|
author_server=MyName,
|
|
|
|
all_members=[],
|
|
|
|
members_dict=[],
|
|
|
|
down=[],
|
|
|
|
upi=[],
|
|
|
|
repairing=[],
|
|
|
|
dbg=Dbg
|
|
|
|
},
|
2015-04-09 08:13:38 +00:00
|
|
|
update_dbg2(update_checksum(P), Dbg2).
|
2015-04-02 07:05:06 +00:00
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Update the checksum element of a projection record.
|
|
|
|
|
2015-04-09 08:13:38 +00:00
|
|
|
update_checksum(P) ->
|
2015-04-02 07:05:06 +00:00
|
|
|
CSum = crypto:hash(sha,
|
|
|
|
term_to_binary(P#projection_v1{epoch_csum= <<>>,
|
|
|
|
dbg2=[]})),
|
|
|
|
P#projection_v1{epoch_csum=CSum}.
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Update the `dbg2' element of a projection record.
|
|
|
|
|
2015-04-09 08:13:38 +00:00
|
|
|
update_dbg2(P, Dbg2) when is_list(Dbg2) ->
|
2015-04-02 07:05:06 +00:00
|
|
|
P#projection_v1{dbg2=Dbg2}.
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Compare two projection records for equality (assuming that the
|
|
|
|
%% checksum element has been correctly calculated).
|
2015-04-09 03:21:15 +00:00
|
|
|
%%
|
|
|
|
%% The name "compare" is probably too close to "rank"? This
|
|
|
|
%% comparison has nothing to do with projection ranking.
|
|
|
|
%% TODO: change the name of this function?
|
2015-04-08 05:24:07 +00:00
|
|
|
|
2015-04-02 07:05:06 +00:00
|
|
|
-spec compare(#projection_v1{}, #projection_v1{}) ->
|
|
|
|
integer().
|
|
|
|
compare(#projection_v1{epoch_number=E1, epoch_csum=C1},
|
|
|
|
#projection_v1{epoch_number=E1, epoch_csum=C1}) ->
|
|
|
|
0;
|
|
|
|
compare(#projection_v1{epoch_number=E1},
|
|
|
|
#projection_v1{epoch_number=E2}) ->
|
|
|
|
if E1 =< E2 -> -1;
|
|
|
|
E1 > E2 -> 1
|
|
|
|
end.
|
|
|
|
|
2015-04-08 05:24:07 +00:00
|
|
|
%% @doc Create a proplist-style summary of a projection record.
|
|
|
|
|
2015-04-09 08:13:38 +00:00
|
|
|
make_summary(#projection_v1{epoch_number=EpochNum,
|
2015-07-04 05:57:38 +00:00
|
|
|
epoch_csum= <<_CSum4:4/binary, _/binary>>=_CSum,
|
2015-04-09 08:13:38 +00:00
|
|
|
all_members=_All_list,
|
|
|
|
down=Down_list,
|
|
|
|
author_server=Author,
|
|
|
|
upi=UPI_list,
|
|
|
|
repairing=Repairing_list,
|
|
|
|
dbg=Dbg, dbg2=Dbg2}) ->
|
2015-04-02 07:05:06 +00:00
|
|
|
[{epoch,EpochNum},{author,Author},
|
|
|
|
{upi,UPI_list},{repair,Repairing_list},{down,Down_list},
|
2015-07-04 05:57:38 +00:00
|
|
|
{d,Dbg}, {d2,Dbg2}, {csum4, _CSum4}].
|
2015-04-09 03:16:58 +00:00
|
|
|
|
|
|
|
%% @doc Make a `p_srvr_dict()' out of a list of `p_srvr()' or out of a
|
|
|
|
%% `p_srvr_dict()'.
|
|
|
|
%%
|
|
|
|
%% If `Ps' is a `p_srvr_dict()', then this function is usually a
|
|
|
|
%% no-op. However, if someone has tampered with the list and screwed
|
|
|
|
%% up its order, then we should fix it so `orddict' can work
|
|
|
|
%% correctly.
|
|
|
|
%%
|
|
|
|
%% If `Ps' is simply `list(p_srvr())', in which case we'll convert it
|
|
|
|
%% to a `p_srvr_dict()'.
|
|
|
|
|
|
|
|
-spec make_members_dict(list(p_srvr()) | p_srvr_dict()) ->
|
|
|
|
p_srvr_dict().
|
|
|
|
make_members_dict(Ps) ->
|
2015-04-09 08:13:38 +00:00
|
|
|
F_rec = fun(P) when is_record(P, p_srvr) -> true;
|
|
|
|
(_) -> false
|
|
|
|
end,
|
|
|
|
F_tup = fun({_K, P}) when is_record(P, p_srvr) -> true;
|
|
|
|
(_) -> false
|
|
|
|
end,
|
|
|
|
case lists:all(F_rec, Ps) of
|
2015-04-09 03:16:58 +00:00
|
|
|
true ->
|
|
|
|
orddict:from_list([{P#p_srvr.name, P} || P <- Ps]);
|
|
|
|
false ->
|
2015-04-09 08:13:38 +00:00
|
|
|
case lists:all(F_tup, Ps) of
|
2015-04-09 03:16:58 +00:00
|
|
|
true ->
|
2015-04-09 08:13:38 +00:00
|
|
|
orddict:from_list(Ps);
|
|
|
|
false ->
|
|
|
|
F_neither = fun(X) -> not (F_rec(X) or F_tup(X)) end,
|
|
|
|
exit({badarg, {make_members_dict, lists:filter(F_neither, Ps)}})
|
|
|
|
end
|
2015-04-09 03:16:58 +00:00
|
|
|
end.
|