diff --git a/src/device/router_device_cache.erl b/src/device/router_device_cache.erl index 161364a59..8df160b60 100644 --- a/src/device/router_device_cache.erl +++ b/src/device/router_device_cache.erl @@ -10,7 +10,16 @@ -include("router_device.hrl"). -define(ETS, router_device_cache_ets). --define(DEVADDR_ETS, router_device_cache_devaddr_ets). +-define(DEVADDR_TABLES, router_device_cache_devaddr_tables). + +-define(ETS_OPTS, [ + public, + named_table, + set, + {write_concurrency, true}, + {read_concurrency, true}, + {keypos, #device_v7.id} +]). %% ------------------------------------------------------------------ %% API Exports @@ -30,77 +39,31 @@ -spec init() -> ok. init() -> - _ = ets:new(?ETS, [ - public, - named_table, - set, - {write_concurrency, true}, - {read_concurrency, true} - ]), - _ = ets:new(?DEVADDR_ETS, [ - public, - named_table, - bag, - {write_concurrency, true}, - {read_concurrency, true} - ]), + _ = ets:new(?ETS, ?ETS_OPTS), + ok = persistent_term:put(?DEVADDR_TABLES, []), ok = init_from_db(), ok. -spec get() -> [router_device:device()]. get() -> - [Device || {_ID, Device} <- ets:tab2list(?ETS)]. + ets:tab2list(?ETS). -spec get(binary()) -> {ok, router_device:device()} | {error, not_found}. get(DeviceID) -> case ets:lookup(?ETS, DeviceID) of [] -> {error, not_found}; - [{DeviceID, Device}] -> {ok, Device} + [Device] -> {ok, Device} end. -spec get_by_devaddr(binary()) -> [router_device:device()]. get_by_devaddr(DevAddr) -> - case ets:lookup(?DEVADDR_ETS, DevAddr) of - [] -> []; - List -> [Device || {_DevAddr, Device} <- List] - end. + ETSName = devaddr_to_ets(DevAddr), + ets:tab2list(ETSName). -spec save(router_device:device()) -> {ok, router_device:device()}. save(Device) -> - DeviceID = router_device:id(Device), - true = ets:insert(?ETS, {DeviceID, Device}), - _ = erlang:spawn(fun() -> - % MS = ets:fun2ms(fun({_, D}=O) when D#device_v7.id == DeviceID -> O end), - MS = [{{'_', '$1'}, [{'==', {element, 2, '$1'}, {const, DeviceID}}], ['$_']}], - CurrentDevaddrs = router_device:devaddrs(Device), - SelectResult = ets:select(?DEVADDR_ETS, MS), - %% We remove devaddrs that are not in use by the device and update existing ones - lists:foreach( - fun({DevAddr, _} = Obj) -> - true = ets:delete_object(?DEVADDR_ETS, Obj), - case lists:member(DevAddr, CurrentDevaddrs) of - true -> - true = ets:insert(?DEVADDR_ETS, {DevAddr, Device}); - false -> - noop - end - end, - SelectResult - ), - SelectDevaddrs = [DevAddr || {DevAddr, _} <- SelectResult], - %% We add devaddrs that are in use by the device and missing from ETS - lists:foreach( - fun(DevAddr) -> - case lists:member(DevAddr, SelectDevaddrs) of - true -> - noop; - false -> - true = ets:insert(?DEVADDR_ETS, {DevAddr, Device}) - end - end, - CurrentDevaddrs - ) - end), + true = ets:insert(?ETS, Device), + ok = insert_device_devaddr(Device), {ok, Device}. -spec delete(binary()) -> ok. @@ -120,7 +83,55 @@ size() -> init_from_db() -> {ok, DB, [_DefaultCF, DevicesCF]} = router_db:get(), Devices = router_device:get(DB, DevicesCF), - lists:foreach(fun(Device) -> ?MODULE:save(Device) end, Devices). + lists:foreach( + fun(Device) -> + true = ets:insert(?ETS, Device), + insert_device_devaddr(Device) + end, + Devices + ). + +-spec insert_device_devaddr(Device :: router_device:device()) -> ok. +insert_device_devaddr(Device) -> + CurrentDevaddrs = router_device:devaddrs(Device), + DeviceID = router_device:id(Device), + lists:foreach( + fun(Table) -> + case ets:whereis(Table) of + undefined -> + ok; + _TID -> + true = ets:delete(Table, DeviceID), + ok + end + end, + persistent_term:get(?DEVADDR_TABLES, []) + ), + lists:foreach( + fun(DevAddr) -> + ETSName = devaddr_to_ets(DevAddr), + case ets:whereis(ETSName) of + undefined -> + _ = ets:new(ETSName, ?ETS_OPTS), + ok = update_devaddr_tables(ETSName), + true = ets:insert(ETSName, Device), + ok; + _TID -> + true = ets:insert(ETSName, Device), + ok + end + end, + CurrentDevaddrs + ). + +-spec devaddr_to_ets(DevAddr :: binary()) -> atom(). +devaddr_to_ets(DevAddr) -> + erlang:binary_to_atom(binary:encode_hex(lorawan_utils:reverse(DevAddr))). + +-spec update_devaddr_tables(TableName :: atom()) -> ok. +update_devaddr_tables(TableName) -> + Tables = persistent_term:get(?DEVADDR_TABLES, []), + ok = persistent_term:put(?DEVADDR_TABLES, [TableName | Tables]). %% ------------------------------------------------------------------ %% EUNIT Tests @@ -129,19 +140,71 @@ init_from_db() -> -include_lib("eunit/include/eunit.hrl"). -init_from_db_test() -> - Dir = test_utils:tmp_dir("init_from_db_test"), +fulll_test() -> + Dir = test_utils:tmp_dir("router_device_cache_fulll_test"), {ok, Pid} = router_db:start_link([Dir]), - ok = init(), - ID = router_utils:uuid_v4(), - Device0 = router_device:new(ID), - DevAddr0 = <<"devaddr0">>, + {ok, DB, [_, CF]} = router_db:get(), + + DevAddr1 = <<255, 1, 0, 72>>, + Device1 = fake_device(DevAddr1), + Device1ID = router_device:id(Device1), + {ok, _} = router_device:save(DB, CF, Device1), + + ok = ?MODULE:init(), + + ?assert(undefined =/= ets:whereis(devaddr_to_ets(DevAddr1))), + ?assertEqual({ok, Device1}, router_device:get_by_id(DB, CF, Device1ID)), + ?assertEqual([Device1], ?MODULE:get_by_devaddr(DevAddr1)), + ?assertEqual([Device1], ?MODULE:get()), + ?assertEqual({ok, Device1}, ?MODULE:get(Device1ID)), + ?assertEqual(1, ?MODULE:size()), + + DevAddr2 = <<255, 2, 0, 72>>, + DevAddr3 = <<255, 3, 0, 72>>, + UpdatedDevice1 = router_device:update( + [ + {devaddrs, [DevAddr2, DevAddr3]} + ], + Device1 + ), + {ok, UpdatedDevice1} = ?MODULE:save(UpdatedDevice1), + + ?assert(undefined =/= ets:whereis(devaddr_to_ets(DevAddr2))), + ?assert(undefined =/= ets:whereis(devaddr_to_ets(DevAddr3))), + ?assertEqual([], ?MODULE:get_by_devaddr(DevAddr1)), + ?assertEqual([UpdatedDevice1], ?MODULE:get_by_devaddr(DevAddr2)), + ?assertEqual([UpdatedDevice1], ?MODULE:get_by_devaddr(DevAddr3)), + ?assertEqual([UpdatedDevice1], ?MODULE:get()), + ?assertEqual({ok, UpdatedDevice1}, ?MODULE:get(Device1ID)), + ?assertEqual(1, ?MODULE:size()), + ?assertEqual( + [devaddr_to_ets(DevAddr3), devaddr_to_ets(DevAddr2), devaddr_to_ets(DevAddr1)], + persistent_term:get(?DEVADDR_TABLES, []) + ), + + Device2 = fake_device(DevAddr2), + {ok, Device2} = ?MODULE:save(Device2), + + ?assertEqual([], ?MODULE:get_by_devaddr(DevAddr1)), + ?assertEqual([Device2, UpdatedDevice1], ?MODULE:get_by_devaddr(DevAddr2)), + ?assertEqual([UpdatedDevice1], ?MODULE:get_by_devaddr(DevAddr3)), + ?assertEqual([Device2, UpdatedDevice1], ?MODULE:get()), + ?assertEqual({ok, Device2}, ?MODULE:get(router_device:id(Device2))), + ?assertEqual(2, ?MODULE:size()), + + gen_server:stop(Pid), + ets:delete(?ETS), + lists:foreach(fun ets:delete/1, persistent_term:get(?DEVADDR_TABLES, [])), + ok. + +fake_device(DevAddr) -> + DeviceID = router_utils:uuid_v4(), Updates = [ - {name, <<"name">>}, + {name, DeviceID}, {app_eui, <<"app_eui">>}, {dev_eui, <<"dev_eui">>}, {keys, [{<<"nwk_s_key">>, <<"app_s_key">>}]}, - {devaddrs, [DevAddr0]}, + {devaddrs, [DevAddr]}, {dev_nonces, [<<"1">>]}, {fcnt, 1}, {fcntdown, 1}, @@ -155,44 +218,6 @@ init_from_db_test() -> {metadata, #{a => b}}, {is_active, false} ], - Device1 = router_device:update(Updates, Device0), - {ok, DB, [_, CF]} = router_db:get(), - ?assertEqual({ok, Device1}, router_device:save(DB, CF, Device1)), - ?assertEqual(ok, init_from_db()), - ?assertEqual({ok, Device1}, ?MODULE:get(ID)), - timer:sleep(10), - ?assertEqual([Device1], ?MODULE:get_by_devaddr(DevAddr0)), - - DevAddr1 = <<"devaddr1">>, - DevAddr2 = <<"devaddr2">>, - Device2 = router_device:devaddrs([DevAddr1, DevAddr2], Device1), - ?assertEqual({ok, Device2}, ?MODULE:save(Device2)), - timer:sleep(10), - ?assertEqual([], ?MODULE:get_by_devaddr(DevAddr0)), - ?assertEqual([Device2], ?MODULE:get_by_devaddr(DevAddr1)), - ?assertEqual([Device2], ?MODULE:get_by_devaddr(DevAddr2)), - - gen_server:stop(Pid), - ets:delete(?ETS), - ets:delete(?DEVADDR_ETS), - ok. - -get_save_delete_test() -> - Dir = test_utils:tmp_dir("get_save_delete_test"), - {ok, Pid} = router_db:start_link([Dir]), - ok = init(), - ID = router_utils:uuid_v4(), - Device = router_device:new(ID), - - ?assertEqual({ok, Device}, ?MODULE:save(Device)), - ?assertEqual({ok, Device}, ?MODULE:get(ID)), - ?assertEqual([Device], ?MODULE:get()), - ?assertEqual(ok, ?MODULE:delete(ID)), - ?assertEqual({error, not_found}, ?MODULE:get(ID)), - - gen_server:stop(Pid), - ets:delete(?ETS), - ets:delete(?DEVADDR_ETS), - ok. + router_device:update(Updates, router_device:new(DeviceID)). -endif. diff --git a/src/router_utils.erl b/src/router_utils.erl index 7dc6e9a38..fca5f2b84 100644 --- a/src/router_utils.erl +++ b/src/router_utils.erl @@ -908,15 +908,17 @@ enumerate_last_test_() -> trace_test() -> application:ensure_all_started(lager), application:set_env(lager, log_root, "log"), - ets:new(router_device_cache_ets, [public, named_table, set]), - ets:new(router_device_cache_devaddr_ets, [public, named_table, bag]), + + Dir = test_utils:tmp_dir("router_utils_trace_test"), + {ok, Pid} = router_db:start_link([Dir]), + ok = router_device_cache:init(), DeviceID = <<"12345678910">>, Device = router_device:update( [ {app_eui, <<"app_eui">>}, {dev_eui, <<"dev_eui">>}, - {devaddrs, [<<"devaddr">>]} + {devaddrs, [<<255, 7, 0, 72>>]} ], router_device:new(DeviceID) ), @@ -929,8 +931,9 @@ trace_test() -> ok = stop_trace(DeviceID), ?assert([] == get_device_traces(DeviceID)), + gen_server:stop(Pid), ets:delete(router_device_cache_ets), - ets:delete(router_device_cache_devaddr_ets), + lists:foreach(fun ets:delete/1, persistent_term:get(router_device_cache_devaddr_tables, [])), application:stop(lager), ok.