machi/src/machi_projection.erl

220 lines
8.3 KiB
Erlang
Raw Normal View History

%% -------------------------------------------------------------------
%%
%% 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.
%%
%% -------------------------------------------------------------------
%% @doc API for manipulating Machi projection data structures (i.e., records).
-module(machi_projection).
-include("machi_projection.hrl").
-export([
new/6, new/7, new/8,
update_checksum/1,
update_dbg2/2,
compare/2,
get_epoch_id/1,
make_summary/1,
make_members_dict/1,
make_epoch_id/1
]).
%% @doc Create a new projection record.
new(MyName, MemberDict, Down_list, UPI_list, Repairing_list, Ps) ->
new(0, MyName, MemberDict, Down_list, UPI_list, Repairing_list, Ps).
%% @doc Create a new projection record.
new(EpochNum, MyName, MemberDict, Down_list, UPI_list, Repairing_list, Dbg) ->
new(EpochNum, MyName, MemberDict, Down_list, UPI_list, Repairing_list,
Dbg, []).
%% @doc Create a new projection record.
%%
%% 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()'.
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),
is_list(MembersDict0), is_list(Down_list), is_list(UPI_list),
is_list(Repairing_list), is_list(Dbg), is_list(Dbg2) ->
MembersDict = make_members_dict(MembersDict0),
All_list = [Name || {Name, _P} <- MembersDict],
if length(All_list) =< ?MAX_CHAIN_LENGTH ->
ok;
true ->
exit(max_chain_length_error)
end,
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 = 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,
members_dict=MembersDict,
down=Down_list,
upi=UPI_list,
repairing=Repairing_list,
dbg=Dbg
},
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
},
update_dbg2(update_checksum(P), Dbg2).
%% @doc Update the checksum element of a projection record.
update_checksum(P) ->
%% Fields that we ignore when calculating checksum:
%% * epoch_csum
%% * dbg2: humming consensus participants may modify this at will without
%% voiding the identity of the projection as a whole.
%% * flap: In some cases in CP mode, coode upstream of C120 may have
%% updated the flapping information. That's OK enough: we aren't
%% going to violate chain replication safety rules (or
%% accidentally encourage someone else sometime later) by
%% replacing flapping information with our own local view at
%% this instant in time.
CSum = crypto:hash(sha,
term_to_binary(P#projection_v1{epoch_csum= <<>>,
flap=undefined,
dbg2=[]})),
P#projection_v1{epoch_csum=CSum}.
%% @doc Update the `dbg2' element of a projection record.
update_dbg2(P, Dbg2) when is_list(Dbg2) ->
P#projection_v1{dbg2=Dbg2}.
%% @doc Compare two projection records for equality (assuming that the
%% checksum element has been correctly calculated).
%%
%% 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?
-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.
%% @doc Return the epoch_id of the projection.
get_epoch_id(#projection_v1{epoch_number=Epoch, epoch_csum=CSum}) ->
{Epoch, CSum}.
%% @doc Create a proplist-style summary of a projection record.
make_summary(#projection_v1{epoch_number=EpochNum,
2015-08-27 11:27:33 +00:00
epoch_csum= <<_CSum4:4/binary, _/binary>>,
all_members=_All_list,
mode=CMode,
witnesses=Witness_list,
down=Down_list,
author_server=Author,
upi=UPI_list,
repairing=Repairing_list,
inner=Inner,
2015-07-15 02:25:06 +00:00
flap=Flap,
dbg=Dbg, dbg2=Dbg2}) ->
InnerInfo = if is_record(Inner, projection_v1) ->
[{inner, make_summary(Inner)}];
true ->
[]
end,
2015-08-27 11:27:33 +00:00
[{epoch,EpochNum}, %% {csum,CSum4},
{author,Author}, {mode,CMode},{witnesses, Witness_list},
{upi,UPI_list},{repair,Repairing_list},{down,Down_list}] ++
InnerInfo ++
2015-07-15 02:25:06 +00:00
[{flap, Flap}] ++
%% [{flap, lists:flatten(io_lib:format("~p", [Flap]))}] ++
[{d,Dbg}, {d2,Dbg2}].
%% @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) ->
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
true ->
orddict:from_list([{P#p_srvr.name, P} || P <- Ps]);
false ->
case lists:all(F_tup, Ps) of
true ->
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
end.
make_epoch_id(#projection_v1{epoch_number=Epoch, epoch_csum=CSum}) ->
{Epoch, CSum}.