Skip to content

Commit fe11c7b

Browse files
committed
fix: Improvements
- Remove unused functions, - make write throw on retry exhaust, - add docs to functions, - Improve syntax and tests
1 parent cf0c65f commit fe11c7b

File tree

1 file changed

+56
-49
lines changed

1 file changed

+56
-49
lines changed

src/hb_store_s3.erl

Lines changed: 56 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121

2222
-ifndef(ENABLE_S3).
2323
-export([start/1]).
24-
2524
start(_) -> error(s3_profile_not_enabled).
26-
2725
-else.
2826
-behaviour(hb_store).
2927
%% Store behavior callbacks
@@ -33,9 +31,6 @@ start(_) -> error(s3_profile_not_enabled).
3331
-export([path/2, add_path/3]).
3432
-export([default_test_opts/0, get_config/1]).
3533

36-
%% Helper functions
37-
-export([match/2]).
38-
3934
-include("include/hb.hrl").
4035
-include_lib("erlcloud/include/erlcloud_aws.hrl").
4136

@@ -200,15 +195,15 @@ get_config(Opts) ->
200195
end.
201196

202197
%% @doc Write a value to a key in S3.
203-
-spec write(opts(), key(), value()) -> ok.
198+
-spec write(opts(), key(), value()) -> ok | no_return.
204199
write(Opts, Key, Value) when is_list(Key) ->
205200
write(Opts, hb_store:join(Key), Value);
206201
write(Opts, Key, Value) when is_binary(Key) ->
207202
RetryAttempts = maps:get(<<"retry-attempts">>, Opts, ?DEFAULT_RETRIES),
208203
write(Opts, Key, Value, RetryAttempts).
209204
write(_Opts, Key, _Value, 0) ->
210205
?event(warning, {max_retries_reached_s3_write, {key, Key}}),
211-
ok;
206+
erlang:error({max_retries_reached_s3_write, {key, Key}});
212207
write(Opts, Key, Value, AttemptsRemaining) ->
213208
#{bucket := Bucket, config := Config} = get_config(Opts),
214209
BucketStr = hb_util:list(Bucket),
@@ -372,6 +367,7 @@ shard_key(Key) when is_binary(Key) andalso byte_size(Key) > ?SHARD_CUT ->
372367
_ ->
373368
shard_key_inner(Key)
374369
end;
370+
%% Data and computer are more specific prefixes, that will not be sharded
375371
shard_key(<<"data">>) -> <<"data">>;
376372
shard_key(Key)->
377373
ShardCut = integer_to_binary(?SHARD_CUT),
@@ -420,7 +416,6 @@ create_parent_groups(Opts, Current, [Next | Rest]) ->
420416
GroupKey = create_make_group_key(GroupPath),
421417
GroupPathValue = read_direct(Opts, GroupPath),
422418
GroupKeyValue = read_direct(Opts, GroupKey),
423-
424419
% Only create group if it doesn't already exist.
425420
case {GroupPathValue, GroupKeyValue} of
426421
{not_found, not_found} ->
@@ -649,7 +644,6 @@ resolve_path_accumulate(Opts, [Head|Tail], AccPath, Depth) ->
649644
% Replace the accumulated path with the link target and
650645
% continue with remaining segments
651646
NewPath = LinkSegments ++ Tail,
652-
653647
resolve_path_segments(Opts, NewPath, Depth + 1);
654648
false ->
655649
resolve_path_accumulate(Opts, Tail, [Head | AccPath], Depth)
@@ -696,6 +690,7 @@ reset(Opts) ->
696690
{error, reset_not_confirmed}
697691
end.
698692

693+
%% @doc Delete all objects from a bucket.
699694
delete_all_objects(Opts) ->
700695
#{bucket := Bucket, config := Config} = get_config(Opts),
701696
BucketStr = hb_util:list(Bucket),
@@ -730,12 +725,6 @@ scope() -> local.
730725
scope(#{ <<"scope">> := Scope }) -> Scope;
731726
scope(_Opts) -> scope().
732727

733-
%% @doc Match keys based on a template.
734-
%% Simple implementation - just returns not_found for now.
735-
-spec match(opts(), map()) -> {ok, [binary()]} | not_found.
736-
match(_Opts, _Template) ->
737-
not_found.
738-
739728
%% @doc Integration test suite demonstrating basic store operations.
740729
%%
741730
%% The following functions implement integration tests using EUnit to verify that
@@ -744,7 +733,6 @@ match(_Opts, _Template) ->
744733
%% resolution, and type detection.
745734
%%
746735
%% Be sure that minio io server is running before executing the integration tests.
747-
748736
default_test_opts() ->
749737
#{
750738
<<"store-module">> => ?MODULE,
@@ -764,32 +752,28 @@ default_test_opts() ->
764752
%% This test creates a temporary database, writes a key-value pair, reads it
765753
%% back to verify correctness, and cleans up by stopping the database. It
766754
%% serves as a sanity check that the basic storage mechanism is working.
767-
768755
init() ->
769756
%% Needed to use the gun http client used by HB.
770757
application:ensure_all_started(hb).
771758

772-
sharding_test() ->
759+
%% @doc Testing default operations for basic store functions.
760+
simple_test() ->
773761
init(),
774762
StoreOpts = default_test_opts(),
775763
start(StoreOpts),
776764
reset(StoreOpts),
777-
778765
Value = <<"value">>,
779766
Key = <<"TWaabU6nSWpn7zPaiWSd9gQkfNwa1t3onrDRNB-bFiI">>,
780767
%% Write
781768
ok = write(StoreOpts, Key, Value),
782-
783769
%% Confirm sharding
784-
785770
#{bucket := Bucket, config := Config} = get_config(StoreOpts),
786771
Result = erlcloud_s3:head_object(
787772
hb_util:list(Bucket),
788773
"TWaa/bU6nSWpn7zPaiWSd9gQkfNwa1t3onrDRNB-bFiI",
789774
Config
790775
),
791776
?assert(is_list(Result)),
792-
793777
%% Read
794778
{ok, Response} = read(StoreOpts, Key),
795779
?assertEqual(Value, Response),
@@ -801,12 +785,14 @@ sharding_test() ->
801785
% Group
802786
GroupKey = <<"UDgFxz7qUcB_TijjDfhUpXD3UGXpw8Xq6OrpoDiv3Y0">>,
803787
make_group(StoreOpts, GroupKey),
804-
write(StoreOpts, <<GroupKey/binary, "/", "content-type">>, <<"application/json">>),
788+
Key2 = add_path(#{}, GroupKey, <<"content-type">>),
789+
write(StoreOpts, Key2, <<"application/json">>),
805790
%% List
806791
R = list(StoreOpts, GroupKey),
807792
?assertEqual({ok,[<<"content-type">>]}, R),
808793
ok = stop(StoreOpts).
809794

795+
%% @doc Testing reading a value where key doesn't exists.
810796
not_found_test() ->
811797
init(),
812798
StoreOpts = default_test_opts(),
@@ -817,49 +803,60 @@ not_found_test() ->
817803
?assertEqual(not_found, Result),
818804
ok = stop(StoreOpts).
819805

806+
%% @doc Testing starting store with a bucket that doesn't exists.
820807
bucket_not_found_test() ->
821808
init(),
822809
StoreOpts = (default_test_opts())#{<<"bucket">> => <<"invalid_bucket">>},
823810
?assertError({bucket_access_failed, {aws_error, {http_error, 400, _, _}}}, start(StoreOpts)),
824811
ok = stop(StoreOpts).
825812

813+
%% @doc Test a failure in writing a key/value to the store. It should
814+
%% retry writing it.
826815
failed_write_test() ->
827816
init(),
828817
StoreOpts = (default_test_opts())#{<<"retry-attempts">> => 2},
829818
start(StoreOpts),
830819
reset(StoreOpts),
831820
Key = hb_message:id(#{}),
832821
Value = <<"value">>,
833-
822+
% Mock S3 dependency to return 400 error
834823
ok = meck:new(erlcloud_s3, [unstick, passthrough]),
835824
XMLBody = <<"">>,
836825
ok = meck:expect(erlcloud_s3, put_object, fun(_, _, _, _, _) ->
837826
error({aws_error,{http_error, 400, "Bad Request", XMLBody}}) end),
838-
839-
{Time, Result} = timer:tc(fun() -> write(StoreOpts, Key, Value) end, second),
827+
% Make sure it spends time retry write to the store
828+
{Time, Result} = timer:tc(fun() ->
829+
?assertError(
830+
{max_retries_reached_s3_write, {key, Key}},
831+
write(StoreOpts, Key, Value)
832+
)
833+
end,
834+
millisecond
835+
),
840836
?assertMatch(ok, Result),
841-
?assert(Time >= 3),
842-
837+
?assert(Time >= 3*?DEFAULT_RETRY_DELAY),
843838
?assert(meck:called(erlcloud_s3, put_object, ['_', '_', '_', '_', '_'])),
839+
% Unload and stop store
844840
ok = meck:unload(erlcloud_s3),
845841
ok = stop(StoreOpts).
846842

843+
%% @doc Test a failure in reading a key from S3 datasource.
847844
failed_read_test() ->
848845
init(),
849846
StoreOpts = default_test_opts(),
850847
start(StoreOpts),
851848
reset(StoreOpts),
852849
Key = hb_message:id(#{}),
853-
850+
% Mock S3 dependency to return 400 error
854851
ok = meck:new(erlcloud_s3, [passthrough]),
855852
XMLBody = <<"">>,
856853
ok = meck:expect(erlcloud_s3, get_object, fun(_, _, _, _) ->
857854
error({aws_error,{http_error, 400, "Bad Request", XMLBody}}) end),
858-
855+
% Read key and test result
859856
Result = read(StoreOpts, Key),
860857
?assertMatch(not_found, Result),
861-
862858
?assert(meck:called(erlcloud_s3, get_object, ['_', '_', '_', '_'])),
859+
% Unload and stop store
863860
ok = meck:unload(erlcloud_s3),
864861
ok = stop(StoreOpts).
865862

@@ -1058,7 +1055,7 @@ cache_style_test() ->
10581055
StoreOpts = default_test_opts(),
10591056
% Start the store
10601057
hb_store:start(StoreOpts),
1061-
reset(StoreOpts),
1058+
hb_store:reset(StoreOpts),
10621059
% Test writing through hb_store interface
10631060
ok = hb_store:write(StoreOpts, <<"test-key">>, <<"test-value">>),
10641061
% Test reading through hb_store interface
@@ -1275,17 +1272,18 @@ list_with_link_test() ->
12751272
?assertEqual(ExpectedChildren, lists:sort(LinkChildren)),
12761273
stop(StoreOpts).
12771274

1278-
resolve_bellow_max_test() ->
1275+
%% Test if resolves link bellow the maxmimum number of link redirection.
1276+
resolve_bellow_max_redirect_test() ->
12791277
init(),
12801278
StoreOpts = default_test_opts(),
1281-
12821279
start(StoreOpts),
12831280
reset(StoreOpts),
1284-
1281+
% Create a group with a children
12851282
GroupName = hb_message:id(#{}),
12861283
make_group(StoreOpts, GroupName),
12871284
Key1 = <<GroupName/binary, "/key">>,
12881285
ok = write(StoreOpts, Key1, <<"Value">>),
1286+
% Create a link chain (a link that refers the previous one)
12891287
LastLink = lists:foldl(
12901288
fun (ID, Link) ->
12911289
BinID = integer_to_binary(ID),
@@ -1294,24 +1292,27 @@ resolve_bellow_max_test() ->
12941292
NewLink
12951293
end,
12961294
GroupName,
1297-
lists:seq(1, 88)
1295+
lists:seq(1, ?MAX_REDIRECTS)
12981296
),
1297+
% Resolve link path
12991298
PathToResolve = <<LastLink/binary, "/key">>,
13001299
Result = resolve(StoreOpts, PathToResolve),
13011300
% Return the resolved link path
13021301
?assertEqual(Key1, Result).
13031302

1304-
resolve_above_max_test() ->
1303+
%% Test if returns the same path when number of redirection is above the
1304+
%% maximum defined.
1305+
resolve_above_max_redirect_test() ->
13051306
init(),
13061307
StoreOpts = default_test_opts(),
1307-
13081308
start(StoreOpts),
13091309
reset(StoreOpts),
1310-
1310+
% Create a group with a children
13111311
GroupName = hb_message:id(#{}),
13121312
make_group(StoreOpts, GroupName),
13131313
Key1 = <<GroupName/binary, "/key">>,
13141314
ok = write(StoreOpts, Key1, <<"Value">>),
1315+
% Create a link chain (a link that refers the previous one)
13151316
LastLink = lists:foldl(
13161317
fun (ID, Link) ->
13171318
BinID = integer_to_binary(ID),
@@ -1322,26 +1323,32 @@ resolve_above_max_test() ->
13221323
GroupName,
13231324
lists:seq(1, ?MAX_REDIRECTS + 1)
13241325
),
1326+
% Resolve link path
13251327
PathToResolve = <<LastLink/binary, "/key">>,
13261328
Result = resolve(StoreOpts, PathToResolve),
13271329
% Return the same path as given
13281330
?assertEqual(PathToResolve, Result).
13291331

1330-
shard_key_test() ->
1332+
%% @doc Test invalid keys, defined by keys with length less or equal
1333+
%% to ?SHARD_CUT
1334+
invalid_sharded_key_test() ->
13311335
InvalidKey1 = <<"data/xpto">>,
13321336
?assertError({invalid_key_for_sharding, <<"xpto">>}, shard_key(InvalidKey1)),
13331337
InvalidKey2 = <<"xpto">>,
1334-
?assertException(error, {invalid_key_for_sharding, InvalidKey2}, shard_key(InvalidKey2)),
1338+
?assertException(error, {invalid_key_for_sharding, InvalidKey2}, shard_key(InvalidKey2)).
1339+
1340+
%% @doc Shard valid keys.
1341+
shard_key_test() ->
13351342
Key1 = <<"data/xpto1">>,
13361343
?assertEqual(<<"data/xpto/1">>, shard_key(Key1)),
13371344
Key2 = <<"xpto1">>,
1338-
?assertEqual(<<"xpto/1">>, shard_key(Key2)),
1339-
%% Only first key is sharded.
1340-
Key3 = <<"xpto1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>,
1341-
?assertEqual(<<"xpto/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>, shard_key(Key3)),
1342-
Key4 = <<"data/xpto1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>,
1343-
?assertEqual(<<"data/xpto/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>, shard_key(Key4)),
1344-
ok.
1345-
1345+
?assertEqual(<<"xpto/1">>, shard_key(Key2)).
1346+
1347+
%% @doc Test that only shards the first Key (second when it starts with `data/`)
1348+
only_first_key_is_sharded_test() ->
1349+
Key1 = <<"xpto1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>,
1350+
?assertEqual(<<"xpto/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>, shard_key(Key1)),
1351+
Key2 = <<"data/xpto1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>,
1352+
?assertEqual(<<"data/xpto/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>, shard_key(Key2)).
13461353
-endif.
13471354
-endif.

0 commit comments

Comments
 (0)