diff --git a/apps/emqx_modules/src/emqx_telemetry.erl b/apps/emqx_modules/src/emqx_telemetry.erl index 02e3bc3f9..e9640cf60 100644 --- a/apps/emqx_modules/src/emqx_telemetry.erl +++ b/apps/emqx_modules/src/emqx_telemetry.erl @@ -93,6 +93,9 @@ -define(TELEMETRY_SHARD, emqx_telemetry_shard). +-define(NODE_UUID_FILENAME, "node.uuid"). +-define(CLUSTER_UUID_FILENAME, "cluster.uuid"). + %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- @@ -535,7 +538,11 @@ ensure_uuids() -> NodeUUID = case mnesia:wread({?TELEMETRY, node()}) of [] -> - NodeUUID0 = generate_uuid(), + NodeUUID0 = + case get_uuid_from_file(node) of + {ok, NUUID} -> NUUID; + undefined -> generate_uuid() + end, mnesia:write( ?TELEMETRY, #telemetry{ @@ -551,7 +558,11 @@ ensure_uuids() -> ClusterUUID = case mnesia:wread({?TELEMETRY, ?CLUSTER_UUID_KEY}) of [] -> - ClusterUUID0 = generate_uuid(), + ClusterUUID0 = + case get_uuid_from_file(cluster) of + {ok, CUUID} -> CUUID; + undefined -> generate_uuid() + end, mnesia:write( ?TELEMETRY, #telemetry{ @@ -566,8 +577,35 @@ ensure_uuids() -> end, {NodeUUID, ClusterUUID} end, - {atomic, UUIDs} = mria:transaction(?TELEMETRY_SHARD, Txn), - UUIDs. + {atomic, {NodeUUID, ClusterUUID}} = mria:transaction(?TELEMETRY_SHARD, Txn), + save_uuid_to_file(NodeUUID, node), + save_uuid_to_file(ClusterUUID, cluster), + {NodeUUID, ClusterUUID}. + +get_uuid_from_file(Type) -> + Path = uuid_file_path(Type), + case file:read_file(Path) of + {ok, + UUID = + <<_:8/binary, "-", _:4/binary, "-", _:4/binary, "-", _:4/binary, "-", _:12/binary>>} -> + {ok, UUID}; + _ -> + undefined + end. + +save_uuid_to_file(UUID, Type) when is_binary(UUID) -> + Path = uuid_file_path(Type), + ok = filelib:ensure_dir(Path), + ok = file:write_file(Path, UUID). + +uuid_file_path(Type) -> + DataDir = emqx:data_dir(), + Filename = + case Type of + node -> ?NODE_UUID_FILENAME; + cluster -> ?CLUSTER_UUID_FILENAME + end, + filename:join(DataDir, Filename). empty_state() -> #state{ diff --git a/apps/emqx_modules/test/emqx_telemetry_SUITE.erl b/apps/emqx_modules/test/emqx_telemetry_SUITE.erl index b90c0d6fc..ad195196e 100644 --- a/apps/emqx_modules/test/emqx_telemetry_SUITE.erl +++ b/apps/emqx_modules/test/emqx_telemetry_SUITE.erl @@ -151,6 +151,41 @@ init_per_testcase(t_cluster_uuid, Config) -> Node = start_slave(n1), ok = setup_slave(Node), [{n1, Node} | Config]; +init_per_testcase(t_uuid_restored_from_file, Config) -> + mock_httpc(), + NodeUUID = <<"AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE">>, + ClusterUUID = <<"FFFFFFFF-GGGG-HHHH-IIII-JJJJJJJJJJJJ">>, + DataDir = emqx:data_dir(), + NodeUUIDFile = filename:join(DataDir, "node.uuid"), + ClusterUUIDFile = filename:join(DataDir, "cluster.uuid"), + file:delete(NodeUUIDFile), + file:delete(ClusterUUIDFile), + ok = file:write_file(NodeUUIDFile, NodeUUID), + ok = file:write_file(ClusterUUIDFile, ClusterUUID), + + %% clear the UUIDs in the DB + {atomic, ok} = mria:clear_table(emqx_telemetry), + emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authn, emqx_authz, emqx_modules]), + emqx_common_test_helpers:start_apps( + [emqx_conf, emqx_authn, emqx_authz, emqx_modules], + fun set_special_configs/1 + ), + Node = start_slave(n1), + ok = setup_slave(Node), + [ + {n1, Node}, + {node_uuid, NodeUUID}, + {cluster_uuid, ClusterUUID} + | Config + ]; +init_per_testcase(t_uuid_saved_to_file, Config) -> + mock_httpc(), + DataDir = emqx:data_dir(), + NodeUUIDFile = filename:join(DataDir, "node.uuid"), + ClusterUUIDFile = filename:join(DataDir, "cluster.uuid"), + file:delete(NodeUUIDFile), + file:delete(ClusterUUIDFile), + Config; init_per_testcase(t_num_clients, Config) -> mock_httpc(), ok = snabbkaffe:start_trace(), @@ -213,6 +248,14 @@ end_per_testcase(t_num_clients, Config) -> meck:unload([httpc]), ok = snabbkaffe:stop(), Config; +end_per_testcase(t_uuid_restored_from_file, _Config) -> + DataDir = emqx:data_dir(), + NodeUUIDFile = filename:join(DataDir, "node.uuid"), + ClusterUUIDFile = filename:join(DataDir, "cluster.uuid"), + ok = file:delete(NodeUUIDFile), + ok = file:delete(ClusterUUIDFile), + meck:unload([httpc]), + ok; end_per_testcase(_Testcase, _Config) -> meck:unload([httpc]), ok. @@ -248,6 +291,48 @@ t_cluster_uuid(Config) -> ?assertNotEqual(NodeUUID0, NodeUUID1), ok. +%% should attempt read UUID from file in data dir to keep UUIDs +%% unique, in the event of a database purge. +t_uuid_restored_from_file(Config) -> + ExpectedNodeUUID = ?config(node_uuid, Config), + ExpectedClusterUUID = ?config(cluster_uuid, Config), + ?assertEqual( + {ok, ExpectedNodeUUID}, + emqx_telemetry:get_node_uuid() + ), + ?assertEqual( + {ok, ExpectedClusterUUID}, + emqx_telemetry:get_cluster_uuid() + ), + ok. + +t_uuid_saved_to_file(_Config) -> + DataDir = emqx:data_dir(), + NodeUUIDFile = filename:join(DataDir, "node.uuid"), + ClusterUUIDFile = filename:join(DataDir, "cluster.uuid"), + %% preconditions + ?assertEqual({error, enoent}, file:read_file(NodeUUIDFile)), + ?assertEqual({error, enoent}, file:read_file(ClusterUUIDFile)), + + %% clear the UUIDs in the DB + {atomic, ok} = mria:clear_table(emqx_telemetry), + emqx_common_test_helpers:stop_apps([emqx_conf, emqx_authn, emqx_authz, emqx_modules]), + emqx_common_test_helpers:start_apps( + [emqx_conf, emqx_authn, emqx_authz, emqx_modules], + fun set_special_configs/1 + ), + {ok, NodeUUID} = emqx_telemetry:get_node_uuid(), + {ok, ClusterUUID} = emqx_telemetry:get_cluster_uuid(), + ?assertEqual( + {ok, NodeUUID}, + file:read_file(NodeUUIDFile) + ), + ?assertEqual( + {ok, ClusterUUID}, + file:read_file(ClusterUUIDFile) + ), + ok. + t_official_version(_) -> true = emqx_telemetry:official_version("0.0.0"), true = emqx_telemetry:official_version("1.1.1"),