diff --git a/config/sys.config b/config/sys.config index 3f6eb44a..fe10c14c 100644 --- a/config/sys.config +++ b/config/sys.config @@ -24,6 +24,7 @@ host => "localhost", port => 8080 }}, + {multi_buy_enabled, true}, {multi_buy_service, #{ transport => http, host => "localhost", diff --git a/config/sys.config.src b/config/sys.config.src index e2c94e35..0e9fcc7c 100644 --- a/config/sys.config.src +++ b/config/sys.config.src @@ -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}", diff --git a/src/hpr_multi_buy.erl b/src/hpr_multi_buy.erl index 46b1c94b..0cae61b3 100644 --- a/src/hpr_multi_buy.erl +++ b/src/hpr_multi_buy.erl @@ -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). @@ -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. @@ -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]), @@ -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()) ]}. @@ -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), diff --git a/src/hpr_routing.erl b/src/hpr_routing.erl index 0301ab89..7be1d00f 100644 --- a/src/hpr_routing.erl +++ b/src/hpr_routing.erl @@ -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. diff --git a/src/hpr_sup.erl b/src/hpr_sup.erl index d0e61b5d..9daf1803 100644 --- a/src/hpr_sup.erl +++ b/src/hpr_sup.erl @@ -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}, @@ -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),