Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/sys.config
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
host => "localhost",
port => 8080
}},
{multi_buy_enabled, true},
{multi_buy_service, #{
transport => http,
host => "localhost",
Expand Down
1 change: 1 addition & 0 deletions config/sys.config.src
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
host => "${HPR_DOWNLINK_SERVICE_HOST}",
port => "${HPR_DOWNLINK_SERVICE_PORT}"
}},
{multi_buy_enabled, ${HPR_MULTI_BUY_ENABLED}},
{multi_buy_service, #{
transport => "${HPR_MULTI_BUY_SERVICE_TRANSPORT}",
host => "${HPR_MULTI_BUY_SERVICE_HOST}",
Expand Down
67 changes: 51 additions & 16 deletions src/hpr_multi_buy.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
init/0,
update_counter/2,
cleanup/1,
make_key/2
make_key/2,
enabled/0
]).

-define(ETS, hpr_multi_buy_ets).
Expand Down Expand Up @@ -41,22 +42,28 @@ update_counter(Key, Max) ->
LocalCounter when LocalCounter > Max ->
{error, ?MULTIBUY};
LocalCounter ->
case request(Key) of
{ok, ServiceCounter} when ServiceCounter > Max ->
ets:update_counter(
?ETS,
Key,
{2, Max - LocalCounter + 1},
{default, 0, erlang:system_time(millisecond)}
),
{error, ?MULTIBUY};
{ok, _ServiceCounter} ->
case enabled() of
false ->
%% ETS-only mode: no external service configured
{ok, false};
{error, Reason} ->
lager:error("failed to get a counter for ~s: ~p", [
hpr_utils:bin_to_hex_string(Key), Reason
]),
{ok, true}
true ->
case request(Key) of
{ok, ServiceCounter} when ServiceCounter > Max ->
ets:update_counter(
?ETS,
Key,
{2, Max - LocalCounter + 1},
{default, 0, erlang:system_time(millisecond)}
),
{error, ?MULTIBUY};
{ok, _ServiceCounter} ->
{ok, false};
{error, Reason} ->
lager:error("failed to get a counter for ~s: ~p", [
hpr_utils:bin_to_hex_string(Key), Reason
]),
{ok, true}
end
end
end.

Expand All @@ -82,6 +89,10 @@ make_key(PacketUp, Route) ->
%% Internal Function Definitions
%% ------------------------------------------------------------------

-spec enabled() -> boolean().
enabled() ->
application:get_env(hpr, multi_buy_enabled, true).

-spec scheduled_cleanup(Duration :: non_neg_integer()) -> ok.
scheduled_cleanup(Duration) ->
{ok, _} = timer:apply_interval(Duration, ?MODULE, cleanup, [Duration]),
Expand Down Expand Up @@ -119,6 +130,7 @@ all_test_() ->
?_test(test_max_too_low()),
?_test(test_update_counter()),
?_test(test_update_counter_with_service()),
?_test(test_update_counter_ets_only()),
?_test(test_cleanup()),
?_test(test_scheduled_cleanup())
]}.
Expand Down Expand Up @@ -173,6 +185,29 @@ test_update_counter_with_service() ->
?assertEqual({error, ?MULTIBUY}, ?MODULE:update_counter(Key, Max)),
ok.

test_update_counter_ets_only() ->
%% Disable external multi-buy service (ETS-only mode)
application:set_env(hpr, multi_buy_enabled, false),

Key = crypto:strong_rand_bytes(16),
Max = 3,

%% The external service should never be called in ETS-only mode
meck:expect(helium_multi_buy_multi_buy_client, inc, fun(_, _) ->
error(should_not_be_called)
end),

%% In ETS-only mode, successful updates return {ok, false} (not free)
?assertEqual({ok, false}, ?MODULE:update_counter(Key, Max)),
?assertEqual({ok, false}, ?MODULE:update_counter(Key, Max)),
?assertEqual({ok, false}, ?MODULE:update_counter(Key, Max)),
%% Once max is exceeded, still get multi_buy error from local ETS
?assertEqual({error, ?MULTIBUY}, ?MODULE:update_counter(Key, Max)),

%% Re-enable for other tests
application:set_env(hpr, multi_buy_enabled, true),
ok.

test_cleanup() ->
Key1 = crypto:strong_rand_bytes(16),
Key2 = crypto:strong_rand_bytes(16),
Expand Down
3 changes: 3 additions & 0 deletions src/hpr_routing.erl
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,9 @@ foreach_setup() ->
ok = hpr_multi_buy:init(),
ok = hpr_device_stats:init(),
ok = hpr_netid_stats:init(),
%% NOTE: These tests expect that routing is setup to hit multi-buy, but the
%% grpc service is not there.
application:set_env(hpr, multi_buy_enabled, true),
meck:new(hpr_gateway_location, [passthrough]),
meck:expect(hpr_gateway_location, get, fun(_) -> {error, not_implemented} end),
ok.
Expand Down
10 changes: 9 additions & 1 deletion src/hpr_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ init([]) ->
_ = maybe_start_channel(ConfigServiceConfig, ?IOT_CONFIG_CHANNEL),
_ = maybe_start_channel(LocationServiceConfig, ?LOCATION_CHANNEL),
_ = maybe_start_channel(DownlinkServiceConfig, ?DOWNLINK_CHANNEL),
_ = maybe_start_channel(MultiBuyServiceConfig, ?MULTI_BUY_CHANNEL),
_ = maybe_start_multi_buy_channel(MultiBuyServiceConfig),

ElliConfigMetrics = [
{callback, hpr_metrics_handler},
Expand Down Expand Up @@ -132,6 +132,14 @@ maybe_start_channel(Config, ChannelName) ->
lager:error("no host/port/transport to start ~s", [ChannelName])
end.

maybe_start_multi_buy_channel(Config) ->
case hpr_multi_buy:enabled() of
true ->
maybe_start_channel(Config, ?MULTI_BUY_CHANNEL);
false ->
lager:info("multi_buy disabled, not starting ~s", [?MULTI_BUY_CHANNEL])
end.

-spec timing(Label :: string(), Fn :: fun()) -> ok.
timing(Label, Fn) ->
Start = erlang:system_time(millisecond),
Expand Down
Loading