From 4afb4d9dd858043d53f7c90ac348e875f29bfaaf Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Mon, 23 May 2022 13:26:57 -0300 Subject: [PATCH] feat(telemetry): save uuids to files In order for the node and cluster UUIDs used by telemetry to survive database purges, we'll save the generated UUIDs to files in EMQX's data directory as well. --- apps/emqx_modules/src/emqx_telemetry.erl | 46 +++++++++- .../test/emqx_telemetry_SUITE.erl | 85 +++++++++++++++++++ 2 files changed, 127 insertions(+), 4 deletions(-) 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"),