MSPF-567: Generate ID's without ExternalID (#30)

* Handle generator service requests

* Upgrade bender_proto

* Add tests and test client

* Add new path to sample config

* Remove debug log & retry_strategy

* Remove irrelevant tests from groups, shorten test config

* Configure thrift services individually

* Refactor sequence generation test

* Upgrade genlib to a version with fixed possible race condition in pmap

* Add forgotten coma

* Pad mapping because I need a signed commit to be able to merge 😠
This commit is contained in:
Toporkov Igor 2020-08-12 19:30:59 +03:00 committed by GitHub
parent 90ca71b80a
commit b392d60018
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 318 additions and 13 deletions

View File

@ -63,7 +63,7 @@ init([]) ->
transport_opts => get_transport_opts(),
shutdown_timeout => get_shutdown_timeout(),
event_handler => EventHandlers,
handlers => [get_handler_spec()],
handlers => get_handler_spec(),
additional_routes => get_routes(EventHandlers)
}
),
@ -104,15 +104,24 @@ get_shutdown_timeout() ->
genlib_app:env(?MODULE, shutdown_timeout, 0).
-spec get_handler_spec() ->
woody:http_handler(woody:th_handler()).
[woody:http_handler(woody:th_handler())].
get_handler_spec() ->
Opts = genlib_app:env(?MODULE, service, #{}),
Path = maps:get(path, Opts, <<"/v1/bender">>),
{Path, {
{bender_thrift, 'Bender'},
bender_handler
}}.
Opts = genlib_app:env(?MODULE, services, #{}),
Bender = maps:get(bender, Opts, #{}),
Generator = maps:get(generator, Opts, #{}),
BenderPath = maps:get(path, Bender, <<"/v1/bender">>),
GeneratorPath = maps:get(generator_path, Generator, <<"/v1/generator">>),
[
{BenderPath, {
{bender_thrift, 'Bender'},
bender_handler
}},
{GeneratorPath, {
{bender_thrift, 'Generator'},
generator_handler
}}
].
-spec get_routes(woody:ev_handlers()) ->
[woody_server_thrift_http_handler:route(_)].

View File

@ -3,6 +3,7 @@
%% API
-export([bind/4]).
-export([generate/2]).
-export([get_internal_id/2]).
%% Machinery callbacks

View File

@ -0,0 +1,66 @@
-module(generator_handler).
%% Woody handler
-behaviour(woody_server_thrift_handler).
-export([handle_function/4]).
-include_lib("bender_proto/include/bender_thrift.hrl").
-include("bender_internal.hrl").
-type woody_context() :: woody_context:ctx().
-type schema() :: bender:schema().
-type generate_id_result() :: bender_thrift:'GeneratedID'().
-spec handle_function(woody:func(), woody:args(), woody_context(), woody:options()) ->
{ok, woody:result()}.
handle_function(Func, Args, WoodyCtx, Opts) ->
scoper:scope(bender,
fun() -> handle_function_(Func, Args, WoodyCtx, Opts) end
).
-spec handle_function_(woody:func(), woody:args(), woody_context(), woody:options()) ->
{ok, woody:result()}.
handle_function_('GenerateID', [Schema], WoodyCtx, _Opts) ->
generate_id(Schema, WoodyCtx).
-spec generate_id(bender_thrift:'GenerationSchema'(), woody_context()) ->
{ok, generate_id_result()} | no_return().
generate_id({constant, #bender_ConstantSchema{} = Schema}, WoodyCtx) ->
NewInternalID = Schema#bender_ConstantSchema.internal_id,
Constant = #constant{internal_id = NewInternalID},
generate(Constant, WoodyCtx);
generate_id({sequence, #bender_SequenceSchema{} = Schema}, WoodyCtx) ->
SequenceID = Schema#bender_SequenceSchema.sequence_id,
Minimum = Schema#bender_SequenceSchema.minimum,
Sequence = #sequence{id = SequenceID, minimum = Minimum},
generate(Sequence, WoodyCtx);
generate_id({snowflake, #bender_SnowflakeSchema{}}, WoodyCtx) ->
generate(snowflake, WoodyCtx);
generate_id(Schema, _WoodyCtx) ->
erlang:error({unknown_schema, Schema}).
-spec generate(schema(), woody_context()) ->
{ok, generate_id_result()} | no_return().
generate(Schema, WoodyCtx) ->
case bender_generator:generate(Schema, WoodyCtx) of
{ID, IntegerID} ->
{ok, #bender_GeneratedID{
id = ID,
integer_id = IntegerID
}};
ID ->
{ok, #bender_GeneratedID{
id = ID
}}
end.

View File

@ -0,0 +1,67 @@
-module(generator_client).
-export([new/0]).
-export([generate_id/2]).
-type client() :: woody_context:ctx().
-type schema() :: bender_thrift:'GenerationSchema'().
-define(retry_stategy, {linear, 5, 1000}).
%%% API
-spec new() ->
client().
new() ->
woody_context:new().
-spec generate_id(schema(), client()) ->
woody:result() | no_return().
generate_id(Schema, Client) ->
call('GenerateID', [Schema], Client).
%%% Internal functions
-spec call(atom(), list(), client()) ->
woody:result() | no_return().
call(Function, Args, Client) ->
Call = {{bender_thrift, 'Generator'}, Function, Args},
Opts = #{
url => <<"http://bender:8022/v1/generator">>,
event_handler => scoper_woody_event_handler,
transport_opts => #{
max_connections => 10000
}
},
call(Call, Opts, Client, ?retry_stategy).
call(Call, Opts, Client, Retry) ->
try
do_call(Call, Opts, Client)
catch
error:{woody_error, {_Source, Class, _Details}} = Error
when Class =:= resource_unavailable orelse Class =:= result_unknown ->
NextRetry = next_retry(Retry, Error),
call(Call, Opts, Client, NextRetry)
end.
do_call(Call, Opts, Client) ->
case woody_client:call(Call, Opts, Client) of
{ok, Response} ->
Response;
{exception, Exception} ->
throw(Exception)
end.
next_retry(Retry, Error) ->
retry_step(genlib_retry:next_step(Retry), Error).
retry_step(finish, Error) ->
erlang:error(Error);
retry_step({wait, Timeout, Retry}, _) ->
ok = timer:sleep(Timeout),
Retry.

View File

@ -0,0 +1,161 @@
-module(generator_tests_SUITE).
-include_lib("common_test/include/ct.hrl").
-export([all/0]).
-export([groups/0]).
-export([init_per_suite/1]).
-export([end_per_suite/1]).
-export([init_per_testcase/2]).
-export([end_per_testcase/2]).
-export([constant/1]).
-export([sequence/1]).
-export([sequence_minimum/1]).
-export([snowflake/1]).
-include_lib("bender_proto/include/bender_thrift.hrl").
-type config() :: [{atom(), term()}].
-type group_name() :: atom().
-type test_case_name() :: atom().
-define(config(Key, C), (element(2, lists:keyfind(Key, 1, C)))).
-spec all() ->
[atom()].
all() ->
[
{group, main}
].
-define(parallel_workers, 100).
-spec groups() -> [{group_name(), list(), [test_case_name()]}].
groups() ->
[
{main, [parallel], [
sequence,
sequence_minimum,
constant,
snowflake
]}
].
-spec init_per_suite(config()) ->
config().
init_per_suite(C) ->
Apps = genlib_app:start_application_with(scoper, [
{storage, scoper_storage_logger}
]) ++ genlib_app:start_application_with(bender, [
{sequence, #{
path => <<"/v1/stateproc/bender_sequence">>,
schema => machinery_mg_schema_generic,
url => <<"http://machinegun:8022/v1/automaton">>,
event_handler => scoper_woody_event_handler,
transport_opts => #{
max_connections => 1000
}
}},
{protocol_opts, #{
timeout => 60000
}},
{transport_opts, #{
max_connections => 10000,
num_acceptors => 100
}}
]),
[{suite_apps, Apps} | C].
-spec end_per_suite(config()) ->
ok.
end_per_suite(C) ->
genlib_app:stop_unload_applications(?config(suite_apps, C)).
-spec init_per_testcase(atom(), config()) ->
config().
init_per_testcase(_Name, C) ->
Client = generator_client:new(),
[{client, Client} | C].
-spec end_per_testcase(atom(), config()) ->
config().
end_per_testcase(_Name, _C) ->
ok.
-spec constant(config()) ->
ok.
constant(C) ->
Client = get_client(C),
InternalID = bender_utils:unique_id(),
Schema = {constant, #bender_ConstantSchema{internal_id = InternalID}},
InternalID = generate(Schema, Client),
ok.
-spec sequence(config()) ->
ok.
sequence(C) ->
Client = get_client(C),
SequenceID = bender_utils:unique_id(),
ExpectedIDs = lists:seq(1, ?parallel_workers),
GeneratedIDs = genlib_pmap:map(
fun(_) ->
Schema = {sequence, #bender_SequenceSchema{sequence_id = SequenceID}},
{_, IntegerID} = generate(Schema, Client),
IntegerID
end,
ExpectedIDs
),
ExpectedIDs = lists:sort(GeneratedIDs),
ok.
-spec sequence_minimum(config()) ->
ok.
sequence_minimum(C) ->
Client = get_client(C),
SequenceID = bender_utils:unique_id(),
Schema1 = {sequence, #bender_SequenceSchema{sequence_id = SequenceID}},
{<<"1">>, 1} = generate(Schema1, Client),
Schema2 = {sequence, #bender_SequenceSchema{sequence_id = SequenceID, minimum = 4}},
{<<"4">>, 4} = generate(Schema2, Client),
OtherSeqID = bender_utils:unique_id(),
Schema3 = {sequence, #bender_SequenceSchema{sequence_id = OtherSeqID, minimum = 8}},
{<<"8">>, 8} = generate(Schema3, Client),
ok.
-spec snowflake(config()) ->
ok.
snowflake(C) ->
Client = get_client(C),
Schema = {snowflake, #bender_SnowflakeSchema{}},
{_ID, _IntegerID} = generate(Schema, Client),
ok.
%%%
get_client(C) ->
?config(client, C).
generate(Schema, Client) ->
case generator_client:generate_id(Schema, Client) of
#bender_GeneratedID{
id = ID,
integer_id = undefined
} -> ID;
#bender_GeneratedID{
id = ID,
integer_id = IntegerID
} -> {ID, IntegerID}
end.

View File

@ -1,7 +1,8 @@
[
{bender, [
{service, #{
path => <<"/v1/bender">>
{services, #{
bender => #{path => <<"/v1/bender">>},
generator => #{path => <<"/v1/generator">>}
}},
{generator, #{

View File

@ -141,4 +141,4 @@
rebar3_run
]}.
%%{ct_readable, true}.
%%{ct_readable, true}.

View File

@ -2,7 +2,7 @@
[{<<"bear">>,{pkg,<<"bear">>,<<"0.8.7">>},2},
{<<"bender_proto">>,
{git,"git@github.com:rbkmoney/bender-proto.git",
{ref,"df5bedd950dd2492e1760eceeb9207645b9af822"}},
{ref,"0d5813b8a25c8d03e4e59e42aa5f4e9b785a3849"}},
0},
{<<"cache">>,{pkg,<<"cache">>,<<"2.2.0">>},1},
{<<"certifi">>,{pkg,<<"certifi">>,<<"2.5.2">>},2},
@ -22,7 +22,7 @@
1},
{<<"genlib">>,
{git,"https://github.com/rbkmoney/genlib.git",
{ref,"941b8c8d4dc544b740a2429ea8381c3873e5fe75"}},
{ref,"1ca08793ad8af0beb26eda8cd00687c69f7ef8b4"}},
0},
{<<"gproc">>,{pkg,<<"gproc">>,<<"0.8.0">>},1},
{<<"hackney">>,{pkg,<<"hackney">>,<<"1.16.0">>},1},