WIP: projection store: read, get latest epoch

This commit is contained in:
Scott Lystig Fritchie 2015-04-03 17:55:35 +09:00
parent acf54e3c21
commit 022b9c4d1f
4 changed files with 125 additions and 20 deletions

View file

@ -550,24 +550,26 @@ do_projection_command(Sock, LenHex, S) ->
{ok, ProjCmdBin} = gen_tcp:recv(Sock, Len), {ok, ProjCmdBin} = gen_tcp:recv(Sock, Len),
ok = inet:setopts(Sock, [{packet, line}]), ok = inet:setopts(Sock, [{packet, line}]),
ProjCmd = binary_to_term(ProjCmdBin), ProjCmd = binary_to_term(ProjCmdBin),
case handle_projection_command(ProjCmd, S) of put(hack, ProjCmd),
ok -> Res = handle_projection_command(ProjCmd, S),
ok = gen_tcp:send(Sock, <<"OK\n">>); ResBin = term_to_binary(Res),
{error, written} -> ResLenHex = machi_util:int_to_hexbin(byte_size(ResBin), 32),
ok = gen_tcp:send(Sock, <<"ERROR WRITTEN\n">>); ok = gen_tcp:send(Sock, [<<"OK ">>, ResLenHex, <<"\n">>, ResBin])
{error, not_written} ->
ok = gen_tcp:send(Sock, <<"ERROR NOT-WRITTEN\n">>);
Else ->
TODO = list_to_binary(io_lib:format("TODO-YOLO-~w", [Else])),
ok = gen_tcp:send(Sock, [<<"ERROR ">>, TODO, <<"\n">>])
end
catch catch
What:Why -> What:Why ->
io:format(user, "OOPS ~p\n", [get(hack)]),
io:format(user, "OOPS ~p ~p ~p\n", [What, Why, erlang:get_stacktrace()]),
WHA = list_to_binary(io_lib:format("TODO-YOLO.~w:~w-~w", WHA = list_to_binary(io_lib:format("TODO-YOLO.~w:~w-~w",
[What, Why, erlang:get_stacktrace()])), [What, Why, erlang:get_stacktrace()])),
_ = (catch gen_tcp:send(Sock, [<<"ERROR ">>, WHA, <<"\n">>])) _ = (catch gen_tcp:send(Sock, [<<"ERROR ">>, WHA, <<"\n">>]))
end. end.
handle_projection_command({get_latest_epoch, ProjType},
#state{proj_store=ProjStore}) ->
machi_projection_store:get_latest_epoch(ProjStore, ProjType);
handle_projection_command({read_projection, ProjType, Epoch},
#state{proj_store=ProjStore}) ->
machi_projection_store:read(ProjStore, ProjType, Epoch);
handle_projection_command({write_projection, ProjType, Proj}, handle_projection_command({write_projection, ProjType, Proj},
#state{proj_store=ProjStore}) -> #state{proj_store=ProjStore}) ->
machi_projection_store:write(ProjStore, ProjType, Proj); machi_projection_store:write(ProjStore, ProjType, Proj);

View file

@ -31,6 +31,8 @@
list_files/2, list_files/3, list_files/2, list_files/3,
%% Projection API %% Projection API
get_latest_epoch/2, get_latest_epoch/3,
read_projection/3, read_projection/4,
write_projection/3, write_projection/4, write_projection/3, write_projection/4,
%% Common API %% Common API
@ -146,6 +148,50 @@ list_files(Host, TcpPort, EpochID) when is_integer(TcpPort) ->
catch gen_tcp:close(Sock) catch gen_tcp:close(Sock)
end. end.
%% @doc Get the latest epoch number from the FLU's projection store.
-spec get_latest_epoch(port(), projection_type()) ->
{ok, -1|non_neg_integer()} | {error, term()}.
get_latest_epoch(Sock, ProjType)
when ProjType == 'public' orelse ProjType == 'private' ->
get_latest_epoch2(Sock, ProjType).
%% @doc Get the latest epoch number from the FLU's projection store.
-spec get_latest_epoch(inet_host(), inet_port(),
projection_type()) ->
{ok, -1|non_neg_integer()} | {error, term()}.
get_latest_epoch(Host, TcpPort, ProjType)
when ProjType == 'public' orelse ProjType == 'private' ->
Sock = machi_util:connect(Host, TcpPort),
try
get_latest_epoch2(Sock, ProjType)
after
catch gen_tcp:close(Sock)
end.
%% @doc Read a projection `Proj' of type `ProjType'.
-spec read_projection(port(), projection_type(), epoch_num()) ->
'ok' | {error, written} | {error, term()}.
read_projection(Sock, ProjType, Epoch)
when ProjType == 'public' orelse ProjType == 'private' ->
read_projection2(Sock, ProjType, Epoch).
%% @doc Read a projection `Proj' of type `ProjType'.
-spec read_projection(inet_host(), inet_port(),
projection_type(), epoch_num()) ->
'ok' | {error, written} | {error, term()}.
read_projection(Host, TcpPort, ProjType, Epoch)
when ProjType == 'public' orelse ProjType == 'private' ->
Sock = machi_util:connect(Host, TcpPort),
try
read_projection2(Sock, ProjType, Epoch)
after
catch gen_tcp:close(Sock)
end.
%% @doc Write a projection `Proj' of type `ProjType'. %% @doc Write a projection `Proj' of type `ProjType'.
-spec write_projection(port(), projection_type(), projection()) -> -spec write_projection(port(), projection_type(), projection()) ->
@ -480,6 +526,14 @@ trunc_hack2(Sock, EpochID, File) ->
{error, {badmatch, BadMatch}} {error, {badmatch, BadMatch}}
end. end.
get_latest_epoch2(Sock, ProjType) ->
ProjCmd = {get_latest_epoch, ProjType},
do_projection_common(Sock, ProjCmd).
read_projection2(Sock, ProjType, Epoch) ->
ProjCmd = {read_projection, ProjType, Epoch},
do_projection_common(Sock, ProjCmd).
write_projection2(Sock, ProjType, Proj) -> write_projection2(Sock, ProjType, Proj) ->
ProjCmd = {write_projection, ProjType, Proj}, ProjCmd = {write_projection, ProjType, Proj},
do_projection_common(Sock, ProjCmd). do_projection_common(Sock, ProjCmd).
@ -492,13 +546,15 @@ do_projection_common(Sock, ProjCmd) ->
LenHex = machi_util:int_to_hexbin(Len, 32), LenHex = machi_util:int_to_hexbin(Len, 32),
Cmd = [<<"PROJ ">>, LenHex, <<"\n">>], Cmd = [<<"PROJ ">>, LenHex, <<"\n">>],
ok = gen_tcp:send(Sock, [Cmd, ProjCmdBin]), ok = gen_tcp:send(Sock, [Cmd, ProjCmdBin]),
ok = inet:setopts(Sock, [{packet, line}]),
{ok, Line} = gen_tcp:recv(Sock, 0), {ok, Line} = gen_tcp:recv(Sock, 0),
PathLen = byte_size(Line) - 3 - 16 - 1 - 1,
case Line of case Line of
<<"OK\n">> -> <<"OK ", ResLenHex:8/binary, "\n">> ->
ok; ResLen = machi_util:hexstr_to_int(ResLenHex),
<<"ERROR WRITTEN\n">> -> ok = inet:setopts(Sock, [{packet, raw}]),
{error, written}; {ok, ResBin} = gen_tcp:recv(Sock, ResLen),
ok = inet:setopts(Sock, [{packet, line}]),
binary_to_term(ResBin);
Else -> Else ->
{error, Else} {error, Else}
end end

View file

@ -25,6 +25,8 @@
%% API %% API
-export([ -export([
start_link/3, start_link/3,
get_latest_epoch/2, get_latest_epoch/3,
read/3, read/4,
write/3, write/4 write/3, write/4
]). ]).
@ -37,14 +39,28 @@
private_dir = "" :: string(), private_dir = "" :: string(),
wedged = true :: boolean(), wedged = true :: boolean(),
wedge_notify_pid :: pid() | atom(), wedge_notify_pid :: pid() | atom(),
max_public_epoch = -1 :: non_neg_integer(), max_public_epoch = -1 :: -1 | non_neg_integer(),
max_private_epoch = -1 :: non_neg_integer() max_private_epoch = -1 :: -1 | non_neg_integer()
}). }).
start_link(RegName, DataDir, NotifyWedgeStateChanges) -> start_link(RegName, DataDir, NotifyWedgeStateChanges) ->
gen_server:start_link({local, RegName}, gen_server:start_link({local, RegName},
?MODULE, [DataDir, NotifyWedgeStateChanges], []). ?MODULE, [DataDir, NotifyWedgeStateChanges], []).
get_latest_epoch(PidSpec, ProjType) ->
get_latest_epoch(PidSpec, ProjType, infinity).
get_latest_epoch(PidSpec, ProjType, Timeout)
when ProjType == 'public' orelse ProjType == 'private' ->
g_call(PidSpec, {get_latest_epoch, ProjType}, Timeout).
read(PidSpec, ProjType, Epoch) ->
read(PidSpec, ProjType, Epoch, infinity).
read(PidSpec, ProjType, Epoch, Timeout)
when ProjType == 'public' orelse ProjType == 'private' ->
g_call(PidSpec, {read, ProjType, Epoch}, Timeout).
write(PidSpec, ProjType, Proj) -> write(PidSpec, ProjType, Proj) ->
write(PidSpec, ProjType, Proj, infinity). write(PidSpec, ProjType, Proj, infinity).
@ -79,6 +95,16 @@ g_call(PidSpec, Arg, Timeout) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_call({{get_latest_epoch, ProjType}, LC1}, _From, S) ->
LC2 = lclock_update(LC1),
Epoch = if ProjType == public -> S#state.max_public_epoch;
ProjType == private -> S#state.max_private_epoch
end,
{reply, {{ok, Epoch}, LC2}, S};
handle_call({{read, ProjType, Epoch}, LC1}, _From, S) ->
LC2 = lclock_update(LC1),
{Reply, NewS} = do_proj_read(ProjType, Epoch, S),
{reply, {Reply, LC2}, NewS};
handle_call({{write, ProjType, Proj}, LC1}, _From, S) -> handle_call({{write, ProjType, Proj}, LC1}, _From, S) ->
LC2 = lclock_update(LC1), LC2 = lclock_update(LC1),
{Reply, NewS} = do_proj_write(ProjType, Proj, S), {Reply, NewS} = do_proj_write(ProjType, Proj, S),
@ -101,6 +127,19 @@ code_change(_OldVsn, S, _Extra) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%
do_proj_read(ProjType, Epoch, S) ->
Dir = pick_path(ProjType, S),
Path = filename:join(Dir, epoch2name(Epoch)),
case file:read_file(Path) of
{ok, Bin} ->
%% TODO and if Bin is corrupt? (even if binary_to_term() succeeds)
{{ok, binary_to_term(Bin)}, S};
{error, enoent} ->
{{error, not_written}, S};
{error, Else} ->
{{error, Else}, S}
end.
do_proj_write(ProjType, #projection_v1{epoch_number=Epoch}=Proj, S) -> do_proj_write(ProjType, #projection_v1{epoch_number=Epoch}=Proj, S) ->
%% TODO: We probably ought to check the projection checksum for sanity, eh? %% TODO: We probably ought to check the projection checksum for sanity, eh?
Dir = pick_path(ProjType, S), Dir = pick_path(ProjType, S),

View file

@ -124,11 +124,19 @@ flu_projection_smoke_test() ->
FLU1 = setup_test_flu(projection_test_flu, TcpPort, DataDir), FLU1 = setup_test_flu(projection_test_flu, TcpPort, DataDir),
try try
{ok, -1} = ?FLU_C:get_latest_epoch(Host, TcpPort, public),
{ok, -1} = ?FLU_C:get_latest_epoch(Host, TcpPort, private),
P1 = machi_projection:new(1, a, [a], [], [a], [], []), P1 = machi_projection:new(1, a, [a], [], [a], [], []),
ok = ?FLU_C:write_projection(Host, TcpPort, public, P1), ok = ?FLU_C:write_projection(Host, TcpPort, public, P1),
{error, written} = ?FLU_C:write_projection(Host, TcpPort, public, P1), {error, written} = ?FLU_C:write_projection(Host, TcpPort, public, P1),
{ok, P1} = ?FLU_C:read_projection(Host, TcpPort, public, 1),
{error, not_written} = ?FLU_C:read_projection(Host, TcpPort, public, 2),
ok = ?FLU_C:write_projection(Host, TcpPort, private, P1), ok = ?FLU_C:write_projection(Host, TcpPort, private, P1),
{error, written} = ?FLU_C:write_projection(Host, TcpPort, private, P1), {error, written} = ?FLU_C:write_projection(Host, TcpPort, private, P1),
{ok, P1} = ?FLU_C:read_projection(Host, TcpPort, private, 1),
{error, not_written} = ?FLU_C:read_projection(Host, TcpPort, private, 2),
ok = ?FLU_C:quit(machi_util:connect(Host, TcpPort)) ok = ?FLU_C:quit(machi_util:connect(Host, TcpPort))
after after