From 297b8b380dc9c1587fe070e2b57378b57c0a1bcb Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 24 May 2023 16:25:22 -0300 Subject: [PATCH 01/72] docs: improve changelog --- changes/ee/feat-10778.en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/ee/feat-10778.en.md b/changes/ee/feat-10778.en.md index 3084d2959..2f2131a8a 100644 --- a/changes/ee/feat-10778.en.md +++ b/changes/ee/feat-10778.en.md @@ -1 +1 @@ -Refactored Pulsar Producer bridge to avoid leaking resources during crashes. +Refactored Pulsar Producer bridge to avoid leaking resources during crashes at creation. From 34be8b3a00e1ba8ca4d5933c8ae33b4b7ada47ba Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Thu, 25 May 2023 15:05:18 -0300 Subject: [PATCH 02/72] ci: ensure `cluster.hocon` is removed before running tests --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 785458601..200fa7c71 100644 --- a/Makefile +++ b/Makefile @@ -104,7 +104,7 @@ APPS=$(shell $(SCRIPTS)/find-apps.sh) .PHONY: $(APPS:%=%-ct) define gen-app-ct-target -$1-ct: $(REBAR) merge-config +$1-ct: $(REBAR) merge-config clean-test-cluster-config $(eval SUITES := $(shell $(SCRIPTS)/find-suites.sh $1)) ifneq ($(SUITES),) @ENABLE_COVER_COMPILE=1 $(REBAR) ct -c -v \ @@ -127,7 +127,7 @@ endef $(foreach app,$(APPS),$(eval $(call gen-app-prop-target,$(app)))) .PHONY: ct-suite -ct-suite: $(REBAR) merge-config +ct-suite: $(REBAR) merge-config clean-test-cluster-config ifneq ($(TESTCASE),) ifneq ($(GROUP),) $(REBAR) ct -v --readable=$(CT_READABLE) --name $(CT_NODE_NAME) --suite $(SUITE) --case $(TESTCASE) --group $(GROUP) @@ -294,3 +294,7 @@ fmt: $(REBAR) @$(SCRIPTS)/erlfmt -w '{apps,lib-ee}/*/{src,include,test}/**/*.{erl,hrl,app.src}' @$(SCRIPTS)/erlfmt -w 'rebar.config.erl' @mix format + +.PHONY: clean-test-cluster-config +clean-test-cluster-config: + @rm apps/emqx_conf/data/configs/cluster.hocon || true From db60dcbada8329a431798ab6c03e009e2bb148c4 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Thu, 25 May 2023 16:11:37 -0300 Subject: [PATCH 03/72] test(buffer_worker): add assertion for inflight count after batch expiration Fixes https://emqx.atlassian.net/browse/EMQX-9829 --- apps/emqx_resource/src/emqx_resource_buffer_worker.erl | 7 +++++++ apps/emqx_resource/test/emqx_resource_SUITE.erl | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/apps/emqx_resource/src/emqx_resource_buffer_worker.erl b/apps/emqx_resource/src/emqx_resource_buffer_worker.erl index 35761822d..602551c33 100644 --- a/apps/emqx_resource/src/emqx_resource_buffer_worker.erl +++ b/apps/emqx_resource/src/emqx_resource_buffer_worker.erl @@ -1256,6 +1256,13 @@ handle_async_batch_reply2([Inflight], ReplyContext, Result, Now) -> %% some queries are not expired, put them back to the inflight batch %% so it can be either acked now or retried later ok = update_inflight_item(InflightTID, Ref, RealNotExpired, NumExpired), + ?tp_ignore_side_effects_in_prod( + handle_async_reply_partially_expired, + #{ + inflight_count => inflight_count(InflightTID), + num_inflight_messages => inflight_num_msgs(InflightTID) + } + ), do_handle_async_batch_reply(ReplyContext#{min_batch := RealNotExpired}, Result) end. diff --git a/apps/emqx_resource/test/emqx_resource_SUITE.erl b/apps/emqx_resource/test/emqx_resource_SUITE.erl index b960b0526..56d878859 100644 --- a/apps/emqx_resource/test/emqx_resource_SUITE.erl +++ b/apps/emqx_resource/test/emqx_resource_SUITE.erl @@ -2248,6 +2248,15 @@ do_t_expiration_async_after_reply(IsBatch) -> } ], ?of_kind(handle_async_reply_expired, Trace) + ), + ?assertMatch( + [ + #{ + inflight_count := 1, + num_inflight_messages := 1 + } + ], + ?of_kind(handle_async_reply_partially_expired, Trace) ); single -> ?assertMatch( From 42b37690c7666a3def04659b687aa5b6da23cffa Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 24 May 2023 16:27:31 -0300 Subject: [PATCH 04/72] refactor(pulsar): use macros for allocatable resources --- .../src/emqx_bridge_pulsar_impl_producer.erl | 12 ++++++++---- .../test/emqx_bridge_pulsar_impl_producer_SUITE.erl | 8 +++++++- apps/emqx_resource/src/emqx_resource.erl | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar_impl_producer.erl b/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar_impl_producer.erl index 5906cc57a..b8157d4fc 100644 --- a/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar_impl_producer.erl +++ b/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar_impl_producer.erl @@ -60,6 +60,10 @@ sync_timeout := emqx_schema:duration_ms() }. +%% Allocatable resources +-define(pulsar_client_id, pulsar_client_id). +-define(pulsar_producers, pulsar_producers). + %%------------------------------------------------------------------------------------- %% `emqx_resource' API %%------------------------------------------------------------------------------------- @@ -81,7 +85,7 @@ on_start(InstanceId, Config) -> } = Config, Servers = format_servers(Servers0), ClientId = make_client_id(InstanceId, BridgeName), - ok = emqx_resource:allocate_resource(InstanceId, pulsar_client_id, ClientId), + ok = emqx_resource:allocate_resource(InstanceId, ?pulsar_client_id, ClientId), SSLOpts = emqx_tls_lib:to_client_opts(SSL), ConnectTimeout = maps:get(connect_timeout, Config, timer:seconds(5)), ClientOpts = #{ @@ -119,7 +123,7 @@ on_start(InstanceId, Config) -> -spec on_stop(resource_id(), state()) -> ok. on_stop(InstanceId, _State) -> case emqx_resource:get_allocated_resources(InstanceId) of - #{pulsar_client_id := ClientId, pulsar_producers := Producers} -> + #{?pulsar_client_id := ClientId, ?pulsar_producers := Producers} -> stop_producers(ClientId, Producers), stop_client(ClientId), ?tp(pulsar_bridge_stopped, #{ @@ -128,7 +132,7 @@ on_stop(InstanceId, _State) -> pulsar_producers => Producers }), ok; - #{pulsar_client_id := ClientId} -> + #{?pulsar_client_id := ClientId} -> stop_client(ClientId), ?tp(pulsar_bridge_stopped, #{ instance_id => InstanceId, @@ -340,7 +344,7 @@ start_producer(Config, InstanceId, ClientId, ClientOpts) -> ?tp(pulsar_producer_about_to_start_producers, #{producer_name => ProducerName}), try pulsar:ensure_supervised_producers(ClientId, PulsarTopic, ProducerOpts) of {ok, Producers} -> - ok = emqx_resource:allocate_resource(InstanceId, pulsar_producers, Producers), + ok = emqx_resource:allocate_resource(InstanceId, ?pulsar_producers, Producers), ?tp(pulsar_producer_producers_allocated, #{}), State = #{ pulsar_client_id => ClientId, diff --git a/apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_impl_producer_SUITE.erl b/apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_impl_producer_SUITE.erl index 3605baaab..4dc318205 100644 --- a/apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_impl_producer_SUITE.erl +++ b/apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_impl_producer_SUITE.erl @@ -541,8 +541,14 @@ kill_resource_managers() -> lists:foreach( fun({_, Pid, _, _}) -> ct:pal("terminating resource manager ~p", [Pid]), - %% sys:terminate(Pid, stop), + Ref = monitor(process, Pid), exit(Pid, kill), + receive + {'DOWN', Ref, process, Pid, killed} -> + ok + after 500 -> + ct:fail("pid ~p didn't die!", [Pid]) + end, ok end, supervisor:which_children(emqx_resource_manager_sup) diff --git a/apps/emqx_resource/src/emqx_resource.erl b/apps/emqx_resource/src/emqx_resource.erl index 10f1de6c4..840c6cfec 100644 --- a/apps/emqx_resource/src/emqx_resource.erl +++ b/apps/emqx_resource/src/emqx_resource.erl @@ -533,7 +533,7 @@ clean_allocated_resources(ResourceId, ResourceMod) -> true -> %% The resource entries in the ETS table are erased inside %% `call_stop' if the call is successful. - ok = emqx_resource:call_stop(ResourceId, ResourceMod, _ResourceState = undefined), + ok = call_stop(ResourceId, ResourceMod, _ResourceState = undefined), ok; false -> ok From 5df7314255d93ef497803783b36200e7da638ac2 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 24 May 2023 16:28:08 -0300 Subject: [PATCH 05/72] feat(kafka): ensure allocated resources are removed on failures --- .../src/emqx_bridge_kafka.app.src | 2 +- .../src/emqx_bridge_kafka_impl_consumer.erl | 24 +++++- .../src/emqx_bridge_kafka_impl_producer.erl | 85 ++++++++++++++----- .../emqx_bridge_kafka_impl_consumer_SUITE.erl | 84 +++++++++++++++++- .../emqx_bridge_kafka_impl_producer_SUITE.erl | 2 + changes/ee/feat-10813.en.md | 1 + 6 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 changes/ee/feat-10813.en.md diff --git a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.app.src b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.app.src index 6c103f73b..64811c91c 100644 --- a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.app.src +++ b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.app.src @@ -1,6 +1,6 @@ {application, emqx_bridge_kafka, [ {description, "EMQX Enterprise Kafka Bridge"}, - {vsn, "0.1.2"}, + {vsn, "0.1.3"}, {registered, [emqx_bridge_kafka_consumer_sup]}, {applications, [ kernel, diff --git a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_consumer.erl b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_consumer.erl index f7958af81..225f90c18 100644 --- a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_consumer.erl +++ b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_consumer.erl @@ -101,6 +101,10 @@ " the connection parameters." ). +%% Allocatable resources +-define(kafka_client_id, kafka_client_id). +-define(kafka_subscriber_id, kafka_subscriber_id). + %%------------------------------------------------------------------------------------- %% `emqx_resource' API %%------------------------------------------------------------------------------------- @@ -140,6 +144,7 @@ on_start(ResourceId, Config) -> Auth -> [{sasl, emqx_bridge_kafka_impl:sasl(Auth)}] end, ClientOpts = add_ssl_opts(ClientOpts0, SSL), + ok = emqx_resource:allocate_resource(ResourceId, ?kafka_client_id, ClientID), case brod:start_client(BootstrapHosts, ClientID, ClientOpts) of ok -> ?tp( @@ -163,7 +168,21 @@ on_start(ResourceId, Config) -> start_consumer(Config, ResourceId, ClientID). -spec on_stop(resource_id(), state()) -> ok. -on_stop(_ResourceID, State) -> +on_stop(ResourceId, _State = undefined) -> + case emqx_resource:get_allocated_resources(ResourceId) of + #{?kafka_client_id := ClientID, ?kafka_subscriber_id := SubscriberId} -> + stop_subscriber(SubscriberId), + stop_client(ClientID), + ?tp(kafka_consumer_subcriber_and_client_stopped, #{}), + ok; + #{?kafka_client_id := ClientID} -> + stop_client(ClientID), + ?tp(kafka_consumer_just_client_stopped, #{}), + ok; + _ -> + ok + end; +on_stop(_ResourceId, State) -> #{ subscriber_id := SubscriberId, kafka_client_id := ClientID @@ -333,6 +352,9 @@ start_consumer(Config, ResourceId, ClientID) -> %% spawns one worker for each assigned topic-partition %% automatically, so we should not spawn duplicate workers. SubscriberId = make_subscriber_id(BridgeName), + ?tp(kafka_consumer_about_to_start_subscriber, #{}), + ok = emqx_resource:allocate_resource(ResourceId, ?kafka_subscriber_id, SubscriberId), + ?tp(kafka_consumer_subscriber_allocated, #{}), case emqx_bridge_kafka_consumer_sup:start_child(SubscriberId, GroupSubscriberConfig) of {ok, _ConsumerPid} -> ?tp( diff --git a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_producer.erl b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_producer.erl index 7bee2c70d..8b8337b09 100644 --- a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_producer.erl +++ b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_producer.erl @@ -23,6 +23,11 @@ -include_lib("emqx/include/logger.hrl"). +%% Allocatable resources +-define(kafka_resource_id, kafka_resource_id). +-define(kafka_client_id, kafka_client_id). +-define(kafka_producers, kafka_producers). + %% TODO: rename this to `kafka_producer' after alias support is added %% to hocon; keeping this as just `kafka' for backwards compatibility. -define(BRIDGE_TYPE, kafka). @@ -46,9 +51,11 @@ on_start(InstId, Config) -> } = Config, BridgeType = ?BRIDGE_TYPE, ResourceId = emqx_bridge_resource:resource_id(BridgeType, BridgeName), + ok = emqx_resource:allocate_resource(InstId, ?kafka_resource_id, ResourceId), _ = maybe_install_wolff_telemetry_handlers(ResourceId), Hosts = emqx_bridge_kafka_impl:hosts(Hosts0), ClientId = emqx_bridge_kafka_impl:make_client_id(BridgeType, BridgeName), + ok = emqx_resource:allocate_resource(InstId, ?kafka_client_id, ClientId), ClientConfig = #{ min_metadata_refresh_interval => MinMetaRefreshInterval, connect_timeout => ConnTimeout, @@ -86,6 +93,7 @@ on_start(InstId, Config) -> WolffProducerConfig = producers_config(BridgeName, ClientId, KafkaConfig, IsDryRun), case wolff:ensure_supervised_producers(ClientId, KafkaTopic, WolffProducerConfig) of {ok, Producers} -> + ok = emqx_resource:allocate_resource(InstId, ?kafka_producers, Producers), {ok, #{ message_template => compile_message_template(MessageTemplate), client_id => ClientId, @@ -120,28 +128,63 @@ on_start(InstId, Config) -> ) end. -on_stop(_InstanceID, #{client_id := ClientID, producers := Producers, resource_id := ResourceID}) -> - _ = with_log_at_error( - fun() -> wolff:stop_and_delete_supervised_producers(Producers) end, +on_stop(InstanceId, _State) -> + case emqx_resource:get_allocated_resources(InstanceId) of #{ - msg => "failed_to_delete_kafka_producer", - client_id => ClientID - } - ), - _ = with_log_at_error( - fun() -> wolff:stop_and_delete_supervised_client(ClientID) end, - #{ - msg => "failed_to_delete_kafka_client", - client_id => ClientID - } - ), - with_log_at_error( - fun() -> uninstall_telemetry_handlers(ResourceID) end, - #{ - msg => "failed_to_uninstall_telemetry_handlers", - client_id => ClientID - } - ). + ?kafka_client_id := ClientId, + ?kafka_producers := Producers, + ?kafka_resource_id := ResourceId + } -> + _ = with_log_at_error( + fun() -> wolff:stop_and_delete_supervised_producers(Producers) end, + #{ + msg => "failed_to_delete_kafka_producer", + client_id => ClientId + } + ), + _ = with_log_at_error( + fun() -> wolff:stop_and_delete_supervised_client(ClientId) end, + #{ + msg => "failed_to_delete_kafka_client", + client_id => ClientId + } + ), + _ = with_log_at_error( + fun() -> uninstall_telemetry_handlers(ResourceId) end, + #{ + msg => "failed_to_uninstall_telemetry_handlers", + resource_id => ResourceId + } + ), + ok; + #{?kafka_client_id := ClientId, ?kafka_resource_id := ResourceId} -> + _ = with_log_at_error( + fun() -> wolff:stop_and_delete_supervised_client(ClientId) end, + #{ + msg => "failed_to_delete_kafka_client", + client_id => ClientId + } + ), + _ = with_log_at_error( + fun() -> uninstall_telemetry_handlers(ResourceId) end, + #{ + msg => "failed_to_uninstall_telemetry_handlers", + resource_id => ResourceId + } + ), + ok; + #{?kafka_resource_id := ResourceId} -> + _ = with_log_at_error( + fun() -> uninstall_telemetry_handlers(ResourceId) end, + #{ + msg => "failed_to_uninstall_telemetry_handlers", + resource_id => ResourceId + } + ), + ok; + _ -> + ok + end. on_query( _InstId, diff --git a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl index c17d21635..bffd4caa4 100644 --- a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl +++ b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl @@ -59,7 +59,9 @@ only_once_tests() -> t_cluster_group, t_node_joins_existing_cluster, t_cluster_node_down, - t_multiple_topic_mappings + t_multiple_topic_mappings, + t_resource_manager_crash_after_subscriber_started, + t_resource_manager_crash_before_subscriber_started ]. init_per_suite(Config) -> @@ -1118,6 +1120,24 @@ stop_async_publisher(Pid) -> end, ok. +kill_resource_managers() -> + ct:pal("gonna kill resource managers"), + lists:foreach( + fun({_, Pid, _, _}) -> + ct:pal("terminating resource manager ~p", [Pid]), + Ref = monitor(process, Pid), + exit(Pid, kill), + receive + {'DOWN', Ref, process, Pid, killed} -> + ok + after 500 -> + ct:fail("pid ~p didn't die!", [Pid]) + end, + ok + end, + supervisor:which_children(emqx_resource_manager_sup) + ). + %%------------------------------------------------------------------------------ %% Testcases %%------------------------------------------------------------------------------ @@ -2024,3 +2044,65 @@ t_begin_offset_earliest(Config) -> end ), ok. + +t_resource_manager_crash_after_subscriber_started(Config) -> + ?check_trace( + begin + ?force_ordering( + #{?snk_kind := kafka_consumer_subscriber_allocated}, + #{?snk_kind := will_kill_resource_manager} + ), + ?force_ordering( + #{?snk_kind := resource_manager_killed}, + #{?snk_kind := kafka_consumer_subscriber_started} + ), + spawn_link(fun() -> + ?tp(will_kill_resource_manager, #{}), + kill_resource_managers(), + ?tp(resource_manager_killed, #{}), + ok + end), + %% even if the resource manager is dead, we can still + %% clear the allocated resources. + {{error, {config_update_crashed, {killed, _}}}, {ok, _}} = + ?wait_async_action( + create_bridge(Config), + #{?snk_kind := kafka_consumer_subcriber_and_client_stopped}, + 10_000 + ), + ok + end, + [] + ), + ok. + +t_resource_manager_crash_before_subscriber_started(Config) -> + ?check_trace( + begin + ?force_ordering( + #{?snk_kind := kafka_consumer_client_started}, + #{?snk_kind := will_kill_resource_manager} + ), + ?force_ordering( + #{?snk_kind := resource_manager_killed}, + #{?snk_kind := kafka_consumer_about_to_start_subscriber} + ), + spawn_link(fun() -> + ?tp(will_kill_resource_manager, #{}), + kill_resource_managers(), + ?tp(resource_manager_killed, #{}), + ok + end), + %% even if the resource manager is dead, we can still + %% clear the allocated resources. + {{error, {config_update_crashed, {killed, _}}}, {ok, _}} = + ?wait_async_action( + create_bridge(Config), + #{?snk_kind := kafka_consumer_just_client_stopped}, + 10_000 + ), + ok + end, + [] + ), + ok. diff --git a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_producer_SUITE.erl b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_producer_SUITE.erl index d1a29fffe..ad41c9904 100644 --- a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_producer_SUITE.erl +++ b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_producer_SUITE.erl @@ -446,6 +446,8 @@ t_failed_creation_then_fix(Config) -> ?assertMatch(#kafka_message{key = BinTime}, KafkaMsg), %% TODO: refactor those into init/end per testcase ok = ?PRODUCER:on_stop(ResourceId, State), + ?assertEqual([], supervisor:which_children(wolff_client_sup)), + ?assertEqual([], supervisor:which_children(wolff_producers_sup)), ok = emqx_bridge_resource:remove(BridgeId), delete_all_bridges(), ok. diff --git a/changes/ee/feat-10813.en.md b/changes/ee/feat-10813.en.md new file mode 100644 index 000000000..b36039f4c --- /dev/null +++ b/changes/ee/feat-10813.en.md @@ -0,0 +1 @@ +Refactored Kafka Producer and Consumer bridges to avoid leaking resources during crashes at creation. From 0ca3f51503f6381dcaac69442d9a2e67c5e2a4ca Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 24 May 2023 17:47:26 -0300 Subject: [PATCH 06/72] fix(kafka): improve shutdown and health check logs during shutdown --- .../src/emqx_bridge_kafka_impl_consumer.erl | 34 +++++++++++++------ .../emqx_bridge_kafka_impl_consumer_SUITE.erl | 1 + 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_consumer.erl b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_consumer.erl index 225f90c18..c0de23d94 100644 --- a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_consumer.erl +++ b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka_impl_consumer.erl @@ -381,7 +381,13 @@ start_consumer(Config, ResourceId, ClientID) -> stop_subscriber(SubscriberId) -> _ = log_when_error( fun() -> - emqx_bridge_kafka_consumer_sup:ensure_child_deleted(SubscriberId) + try + emqx_bridge_kafka_consumer_sup:ensure_child_deleted(SubscriberId) + catch + exit:{noproc, _} -> + %% may happen when node is shutting down + ok + end end, #{ msg => "failed_to_delete_kafka_subscriber", @@ -465,16 +471,22 @@ do_get_topic_status(ClientID, KafkaTopic, SubscriberId, NPartitions) -> end. are_subscriber_workers_alive(SubscriberId) -> - Children = supervisor:which_children(emqx_bridge_kafka_consumer_sup), - case lists:keyfind(SubscriberId, 1, Children) of - false -> - false; - {_, Pid, _, _} -> - Workers = brod_group_subscriber_v2:get_workers(Pid), - %% we can't enforce the number of partitions on a single - %% node, as the group might be spread across an emqx - %% cluster. - lists:all(fun is_process_alive/1, maps:values(Workers)) + try + Children = supervisor:which_children(emqx_bridge_kafka_consumer_sup), + case lists:keyfind(SubscriberId, 1, Children) of + false -> + false; + {_, Pid, _, _} -> + Workers = brod_group_subscriber_v2:get_workers(Pid), + %% we can't enforce the number of partitions on a single + %% node, as the group might be spread across an emqx + %% cluster. + lists:all(fun is_process_alive/1, maps:values(Workers)) + end + catch + exit:{shutdown, _} -> + %% may happen if node is shutting down + false end. log_when_error(Fun, Log) -> diff --git a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl index bffd4caa4..194ca95d6 100644 --- a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl +++ b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl @@ -335,6 +335,7 @@ init_per_testcase(TestCase, Config) -> common_init_per_testcase(TestCase, Config0) -> ct:timetrap(timer:seconds(60)), delete_all_bridges(), + emqx_config:delete_override_conf_files(), KafkaTopic = << (atom_to_binary(TestCase))/binary, From cb34bc5c46ddf5a369a77ecbf50089494eb1ea35 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 24 May 2023 13:13:54 -0300 Subject: [PATCH 07/72] test(kafka_consumer): attempt to stabilize cluster tests Example failure: https://github.com/emqx/emqx/actions/runs/5070096710/jobs/9105822319#step:7:515 The attempt here is to setup the spy as early as possible, before the bridge starts, so we avoid missing rebalancing events. --- .../emqx_bridge_kafka_impl_consumer_SUITE.erl | 61 +++++++++---------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl index 194ca95d6..8b61b2ee1 100644 --- a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl +++ b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl @@ -997,36 +997,33 @@ reconstruct_assignments_from_events(KafkaTopic, Events0, Acc0) -> Assignments ). -setup_group_subscriber_spy(Node) -> +setup_group_subscriber_spy_fn() -> TestPid = self(), - ok = erpc:call( - Node, - fun() -> - ok = meck:new(brod_group_subscriber_v2, [ - passthrough, no_link, no_history, non_strict - ]), - ok = meck:expect( - brod_group_subscriber_v2, - assignments_received, - fun(Pid, MemberId, GenerationId, TopicAssignments) -> - ?tp( - kafka_assignment, - #{ - node => node(), - pid => Pid, - member_id => MemberId, - generation_id => GenerationId, - topic_assignments => TopicAssignments - } - ), - TestPid ! - {kafka_assignment, node(), {Pid, MemberId, GenerationId, TopicAssignments}}, - meck:passthrough([Pid, MemberId, GenerationId, TopicAssignments]) - end - ), - ok - end - ). + fun() -> + ok = meck:new(brod_group_subscriber_v2, [ + passthrough, no_link, no_history, non_strict + ]), + ok = meck:expect( + brod_group_subscriber_v2, + assignments_received, + fun(Pid, MemberId, GenerationId, TopicAssignments) -> + ?tp( + kafka_assignment, + #{ + node => node(), + pid => Pid, + member_id => MemberId, + generation_id => GenerationId, + topic_assignments => TopicAssignments + } + ), + TestPid ! + {kafka_assignment, node(), {Pid, MemberId, GenerationId, TopicAssignments}}, + meck:passthrough([Pid, MemberId, GenerationId, TopicAssignments]) + end + ), + ok + end. wait_for_cluster_rpc(Node) -> %% need to wait until the config handler is ready after @@ -1070,6 +1067,7 @@ cluster(Config) -> _ -> ct_slave end, + ExtraEnvHandlerHook = setup_group_subscriber_spy_fn(), Cluster = emqx_common_test_helpers:emqx_cluster( [core, core], [ @@ -1083,6 +1081,7 @@ cluster(Config) -> {env_handler, fun (emqx) -> application:set_env(emqx, boot_modules, [broker, router]), + ExtraEnvHandlerHook(), ok; (emqx_conf) -> ok; @@ -1701,7 +1700,6 @@ t_cluster_group(Config) -> Nodes ) end), - lists:foreach(fun setup_group_subscriber_spy/1, Nodes), {ok, SRef0} = snabbkaffe:subscribe( ?match_event(#{?snk_kind := kafka_consumer_subscriber_started}), length(Nodes), @@ -1778,7 +1776,6 @@ t_node_joins_existing_cluster(Config) -> ct:pal("stopping ~p", [N1]), ok = emqx_common_test_helpers:stop_slave(N1) end), - setup_group_subscriber_spy(N1), {{ok, _}, {ok, _}} = ?wait_async_action( erpc:call(N1, fun() -> @@ -1822,7 +1819,6 @@ t_node_joins_existing_cluster(Config) -> ct:pal("stopping ~p", [N2]), ok = emqx_common_test_helpers:stop_slave(N2) end), - setup_group_subscriber_spy(N2), Nodes = [N1, N2], wait_for_cluster_rpc(N2), @@ -1923,7 +1919,6 @@ t_cluster_node_down(Config) -> Nodes ) end), - lists:foreach(fun setup_group_subscriber_spy/1, Nodes), {ok, SRef0} = snabbkaffe:subscribe( ?match_event(#{?snk_kind := kafka_consumer_subscriber_started}), length(Nodes), From e43517188fbd61857a3d92e5a32009dcc58e6571 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 24 May 2023 17:47:57 -0300 Subject: [PATCH 08/72] fix(bridge): remove bridge if exception happens during creation --- apps/emqx_bridge/src/emqx_bridge.erl | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/emqx_bridge/src/emqx_bridge.erl b/apps/emqx_bridge/src/emqx_bridge.erl index 1fbd6902e..cbbe937aa 100644 --- a/apps/emqx_bridge/src/emqx_bridge.erl +++ b/apps/emqx_bridge/src/emqx_bridge.erl @@ -227,9 +227,13 @@ post_config_update(_, _Req, NewConf, OldConf, _AppEnv) -> diff_confs(NewConf, OldConf), %% The config update will be failed if any task in `perform_bridge_changes` failed. Result = perform_bridge_changes([ - {fun emqx_bridge_resource:remove/4, Removed}, - {fun emqx_bridge_resource:create/4, Added}, - {fun emqx_bridge_resource:update/4, Updated} + #{action => fun emqx_bridge_resource:remove/4, data => Removed}, + #{ + action => fun emqx_bridge_resource:create/4, + data => Added, + on_exception_fn => fun emqx_bridge_resource:remove/4 + }, + #{action => fun emqx_bridge_resource:update/4, data => Updated} ]), ok = unload_hook(), ok = load_hook(NewConf), @@ -345,7 +349,8 @@ perform_bridge_changes(Tasks) -> perform_bridge_changes([], Result) -> Result; -perform_bridge_changes([{Action, MapConfs} | Tasks], Result0) -> +perform_bridge_changes([#{action := Action, data := MapConfs} = Task | Tasks], Result0) -> + OnException = maps:get(on_exception_fn, Task, fun(_Type, _Name, _Conf, _Opts) -> ok end), Result = maps:fold( fun ({_Type, _Name}, _Conf, {error, Reason}) -> @@ -359,9 +364,21 @@ perform_bridge_changes([{Action, MapConfs} | Tasks], Result0) -> end; ({Type, Name}, Conf, _) -> ResOpts = emqx_resource:fetch_creation_opts(Conf), - case Action(Type, Name, Conf, ResOpts) of + try Action(Type, Name, Conf, ResOpts) of {error, Reason} -> {error, Reason}; Return -> Return + catch + Kind:Error:Stacktrace -> + ?SLOG(error, #{ + msg => "bridge_config_update_exception", + kind => Kind, + error => Error, + type => Type, + name => Name, + stacktrace => Stacktrace + }), + OnException(Type, Name, Conf, ResOpts), + erlang:raise(Kind, Error, Stacktrace) end end, Result0, From 32e6213ce3e01cca814015766887de890b4bfb3d Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Thu, 25 May 2023 15:01:39 -0300 Subject: [PATCH 09/72] fix(resource_manager_sup): use `one_for_one` instead of `simple_one_for_one` Using `simple_one_for_one` has a potential race condition issue where we read the PID of the resource manager before trying to remove a resource, and then that PID changes because it was either dead at first, or it crashed and changed, and later we use this stale PID to try to remove it from the supervisor. Under such circumstances, the restarting child might linger in the supervisor, leaking resources. By using the resource ID itself as a child ID (and using `one_for_one` restart strategy), we ensure the child is truly removed. --- .../emqx_bridge_kafka_impl_consumer_SUITE.erl | 57 ++++++++++++++++++- ...emqx_bridge_pulsar_impl_producer_SUITE.erl | 9 +++ .../src/emqx_resource_manager.erl | 3 +- .../src/emqx_resource_manager_sup.erl | 37 ++++++------ 4 files changed, 84 insertions(+), 22 deletions(-) diff --git a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl index 8b61b2ee1..7e7acbcd5 100644 --- a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl +++ b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_consumer_SUITE.erl @@ -2058,14 +2058,41 @@ t_resource_manager_crash_after_subscriber_started(Config) -> ?tp(resource_manager_killed, #{}), ok end), + %% even if the resource manager is dead, we can still %% clear the allocated resources. - {{error, {config_update_crashed, {killed, _}}}, {ok, _}} = + + %% We avoid asserting only the `config_update_crashed' + %% error here because there's a race condition (just a + %% problem for the test assertion below) in which the + %% `emqx_resource_manager:create/5' call returns a failure + %% (not checked) and then `lookup' in that module is + %% delayed enough so that the manager supervisor has time + %% to restart the manager process and for the latter to + %% startup successfully. Occurs frequently in CI... + + {Res, {ok, _}} = ?wait_async_action( create_bridge(Config), #{?snk_kind := kafka_consumer_subcriber_and_client_stopped}, 10_000 ), + case Res of + {error, {config_update_crashed, {killed, _}}} -> + ok; + {ok, _} -> + %% the new manager may have had time to startup + %% before the resource status cache is read... + ok; + _ -> + ct:fail("unexpected result: ~p", [Res]) + end, + ?assertMatch({ok, _}, delete_bridge(Config)), + ?retry( + _Sleep = 50, + _Attempts = 50, + ?assertEqual([], supervisor:which_children(emqx_bridge_kafka_consumer_sup)) + ), ok end, [] @@ -2089,14 +2116,40 @@ t_resource_manager_crash_before_subscriber_started(Config) -> ?tp(resource_manager_killed, #{}), ok end), + %% even if the resource manager is dead, we can still %% clear the allocated resources. - {{error, {config_update_crashed, {killed, _}}}, {ok, _}} = + + %% We avoid asserting only the `config_update_crashed' + %% error here because there's a race condition (just a + %% problem for the test assertion below) in which the + %% `emqx_resource_manager:create/5' call returns a failure + %% (not checked) and then `lookup' in that module is + %% delayed enough so that the manager supervisor has time + %% to restart the manager process and for the latter to + %% startup successfully. Occurs frequently in CI... + {Res, {ok, _}} = ?wait_async_action( create_bridge(Config), #{?snk_kind := kafka_consumer_just_client_stopped}, 10_000 ), + case Res of + {error, {config_update_crashed, {killed, _}}} -> + ok; + {ok, _} -> + %% the new manager may have had time to startup + %% before the resource status cache is read... + ok; + _ -> + ct:fail("unexpected result: ~p", [Res]) + end, + ?assertMatch({ok, _}, delete_bridge(Config)), + ?retry( + _Sleep = 50, + _Attempts = 50, + ?assertEqual([], supervisor:which_children(emqx_bridge_kafka_consumer_sup)) + ), ok end, [] diff --git a/apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_impl_producer_SUITE.erl b/apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_impl_producer_SUITE.erl index 4dc318205..a5c04160c 100644 --- a/apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_impl_producer_SUITE.erl +++ b/apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_impl_producer_SUITE.erl @@ -285,6 +285,11 @@ create_bridge(Config, Overrides) -> PulsarConfig = emqx_utils_maps:deep_merge(PulsarConfig0, Overrides), emqx_bridge:create(Type, Name, PulsarConfig). +delete_bridge(Config) -> + Type = ?BRIDGE_TYPE_BIN, + Name = ?config(pulsar_name, Config), + emqx_bridge:remove(Type, Name). + create_bridge_api(Config) -> create_bridge_api(Config, _Overrides = #{}). @@ -1008,6 +1013,8 @@ t_resource_manager_crash_after_producers_started(Config) -> Producers =/= undefined, 10_000 ), + ?assertMatch({ok, _}, delete_bridge(Config)), + ?assertEqual([], get_pulsar_producers()), ok end, [] @@ -1039,6 +1046,8 @@ t_resource_manager_crash_before_producers_started(Config) -> #{?snk_kind := pulsar_bridge_stopped, pulsar_producers := undefined}, 10_000 ), + ?assertMatch({ok, _}, delete_bridge(Config)), + ?assertEqual([], get_pulsar_producers()), ok end, [] diff --git a/apps/emqx_resource/src/emqx_resource_manager.erl b/apps/emqx_resource/src/emqx_resource_manager.erl index 7a54bfa97..388251c0b 100644 --- a/apps/emqx_resource/src/emqx_resource_manager.erl +++ b/apps/emqx_resource/src/emqx_resource_manager.erl @@ -192,14 +192,13 @@ remove(ResId) when is_binary(ResId) -> %% @doc Stops a running resource_manager and optionally clears the metrics for the resource -spec remove(resource_id(), boolean()) -> ok | {error, Reason :: term()}. remove(ResId, ClearMetrics) when is_binary(ResId) -> - ResourceManagerPid = gproc:whereis_name(?NAME(ResId)), try safe_call(ResId, {remove, ClearMetrics}, ?T_OPERATION) after %% Ensure the supervisor has it removed, otherwise the immediate re-add will see a stale process %% If the 'remove' call babove had succeeded, this is mostly a no-op but still needed to avoid race condition. %% Otherwise this is a 'infinity' shutdown, so it may take arbitrary long. - emqx_resource_manager_sup:delete_child(ResourceManagerPid) + emqx_resource_manager_sup:delete_child(ResId) end. %% @doc Stops and then starts an instance that was already running diff --git a/apps/emqx_resource/src/emqx_resource_manager_sup.erl b/apps/emqx_resource/src/emqx_resource_manager_sup.erl index 9e86e6363..732d5e513 100644 --- a/apps/emqx_resource/src/emqx_resource_manager_sup.erl +++ b/apps/emqx_resource/src/emqx_resource_manager_sup.erl @@ -26,12 +26,12 @@ -export([init/1]). ensure_child(ResId, Group, ResourceType, Config, Opts) -> - _ = supervisor:start_child(?MODULE, [ResId, Group, ResourceType, Config, Opts]), + _ = supervisor:start_child(?MODULE, child_spec(ResId, Group, ResourceType, Config, Opts)), ok. -delete_child(Pid) -> - _ = supervisor:terminate_child(?MODULE, Pid), - _ = supervisor:delete_child(?MODULE, Pid), +delete_child(ResId) -> + _ = supervisor:terminate_child(?MODULE, ResId), + _ = supervisor:delete_child(?MODULE, ResId), ok. start_link() -> @@ -44,18 +44,19 @@ init([]) -> public, {read_concurrency, true} ]), - ChildSpecs = [ - #{ - id => emqx_resource_manager, - start => {emqx_resource_manager, start_link, []}, - restart => transient, - %% never force kill a resource manager. - %% becasue otherwise it may lead to release leak, - %% resource_manager's terminate callback calls resource on_stop - shutdown => infinity, - type => worker, - modules => [emqx_resource_manager] - } - ], - SupFlags = #{strategy => simple_one_for_one, intensity => 10, period => 10}, + ChildSpecs = [], + SupFlags = #{strategy => one_for_one, intensity => 10, period => 10}, {ok, {SupFlags, ChildSpecs}}. + +child_spec(ResId, Group, ResourceType, Config, Opts) -> + #{ + id => ResId, + start => {emqx_resource_manager, start_link, [ResId, Group, ResourceType, Config, Opts]}, + restart => transient, + %% never force kill a resource manager. + %% becasue otherwise it may lead to release leak, + %% resource_manager's terminate callback calls resource on_stop + shutdown => infinity, + type => worker, + modules => [emqx_resource_manager] + }. From 7188346d6c3b683e20bf991f5f68c4be55066b73 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Fri, 26 May 2023 11:29:55 -0300 Subject: [PATCH 10/72] ci: improve script Co-authored-by: Zaiming (Stone) Shi --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 200fa7c71..d220c8a50 100644 --- a/Makefile +++ b/Makefile @@ -297,4 +297,4 @@ fmt: $(REBAR) .PHONY: clean-test-cluster-config clean-test-cluster-config: - @rm apps/emqx_conf/data/configs/cluster.hocon || true + @rm -f apps/emqx_conf/data/configs/cluster.hocon || true From 9b7e473cf65e4bbff3a2af7880d6c97572a95a5a Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Fri, 26 May 2023 11:58:14 -0300 Subject: [PATCH 11/72] feat(kafka_producer): add validation for empty message key when strategy = key_dispatch Fixes https://emqx.atlassian.net/browse/EMQX-9979 --- .../src/emqx_bridge_kafka.app.src | 2 +- .../src/emqx_bridge_kafka.erl | 11 ++++++- .../test/emqx_bridge_kafka_tests.erl | 30 +++++++++++++++++++ changes/ee/feat-10841.en.md | 1 + 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 changes/ee/feat-10841.en.md diff --git a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.app.src b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.app.src index 6c103f73b..64811c91c 100644 --- a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.app.src +++ b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.app.src @@ -1,6 +1,6 @@ {application, emqx_bridge_kafka, [ {description, "EMQX Enterprise Kafka Bridge"}, - {vsn, "0.1.2"}, + {vsn, "0.1.3"}, {registered, [emqx_bridge_kafka_consumer_sup]}, {applications, [ kernel, diff --git a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.erl b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.erl index 30f6cd60d..73a71787e 100644 --- a/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.erl +++ b/apps/emqx_bridge_kafka/src/emqx_bridge_kafka.erl @@ -247,7 +247,8 @@ fields(producer_opts) -> {kafka, mk(ref(producer_kafka_opts), #{ required => true, - desc => ?DESC(producer_kafka_opts) + desc => ?DESC(producer_kafka_opts), + validator => fun producer_strategy_key_validator/1 })} ]; fields(producer_kafka_opts) -> @@ -459,3 +460,11 @@ consumer_topic_mapping_validator(TopicMapping = [_ | _]) -> false -> {error, "Kafka topics must not be repeated in a bridge"} end. + +producer_strategy_key_validator(#{ + <<"partition_strategy">> := key_dispatch, + <<"message">> := #{<<"key">> := ""} +}) -> + {error, "Message key cannot be empty when `key_dispatch` strategy is used"}; +producer_strategy_key_validator(_) -> + ok. diff --git a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_tests.erl b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_tests.erl index b16df854f..23bcf8e9d 100644 --- a/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_tests.erl +++ b/apps/emqx_bridge_kafka/test/emqx_bridge_kafka_tests.erl @@ -138,6 +138,36 @@ kafka_consumer_test() -> ok. +message_key_dispatch_validations_test() -> + Conf0 = kafka_producer_new_hocon(), + Conf1 = + Conf0 ++ + "\n" + "bridges.kafka.myproducer.kafka.message.key = \"\"" + "\n" + "bridges.kafka.myproducer.kafka.partition_strategy = \"key_dispatch\"", + Conf = parse(Conf1), + ?assertMatch( + #{ + <<"kafka">> := + #{ + <<"partition_strategy">> := <<"key_dispatch">>, + <<"message">> := #{<<"key">> := <<>>} + } + }, + emqx_utils_maps:deep_get([<<"bridges">>, <<"kafka">>, <<"myproducer">>], Conf) + ), + ?assertThrow( + {_, [ + #{ + path := "bridges.kafka.myproducer.kafka", + reason := "Message key cannot be empty when `key_dispatch` strategy is used" + } + ]}, + check(Conf) + ), + ok. + %%=========================================================================== %% Helper functions %%=========================================================================== diff --git a/changes/ee/feat-10841.en.md b/changes/ee/feat-10841.en.md new file mode 100644 index 000000000..3bf8daa24 --- /dev/null +++ b/changes/ee/feat-10841.en.md @@ -0,0 +1 @@ +Added a schema validation to ensure message key is not empty when dispatching by key in Kafka Producer bridge. From d27f593309e928dad2fb361af018d2032d952314 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Fri, 26 May 2023 16:40:18 -0300 Subject: [PATCH 12/72] test: fix flaky authn test --- apps/emqx/test/emqx_authentication_SUITE.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/emqx/test/emqx_authentication_SUITE.erl b/apps/emqx/test/emqx_authentication_SUITE.erl index 0190ab936..652b634ca 100644 --- a/apps/emqx/test/emqx_authentication_SUITE.erl +++ b/apps/emqx/test/emqx_authentication_SUITE.erl @@ -98,6 +98,8 @@ init_per_suite(Config) -> LogLevel = emqx_logger:get_primary_log_level(), ok = emqx_logger:set_log_level(debug), application:set_env(ekka, strict_mode, true), + emqx_config:erase_all(), + emqx_common_test_helpers:stop_apps([]), emqx_common_test_helpers:boot_modules(all), emqx_common_test_helpers:start_apps([]), [{log_level, LogLevel} | Config]. From 8e6dc29637fb389a8862ef06fa3465fe9b857562 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Mon, 29 May 2023 08:57:28 +0200 Subject: [PATCH 13/72] chore: bump release version to v5.0.26-alpha.1 --- apps/emqx/include/emqx_release.hrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqx/include/emqx_release.hrl b/apps/emqx/include/emqx_release.hrl index ec78091b7..f9154f987 100644 --- a/apps/emqx/include/emqx_release.hrl +++ b/apps/emqx/include/emqx_release.hrl @@ -32,7 +32,7 @@ %% `apps/emqx/src/bpapi/README.md' %% Community edition --define(EMQX_RELEASE_CE, "5.0.25"). +-define(EMQX_RELEASE_CE, "5.0.26-alpha.1"). %% Enterprise edition -define(EMQX_RELEASE_EE, "5.0.4"). From e6869a290473f87d95fb07b7241f5ba46e086786 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Mon, 29 May 2023 09:04:17 +0200 Subject: [PATCH 14/72] chore: bump app vsn for emqx_machine --- apps/emqx_machine/src/emqx_machine.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqx_machine/src/emqx_machine.app.src b/apps/emqx_machine/src/emqx_machine.app.src index 7cf0e4b53..5cfa80369 100644 --- a/apps/emqx_machine/src/emqx_machine.app.src +++ b/apps/emqx_machine/src/emqx_machine.app.src @@ -3,7 +3,7 @@ {id, "emqx_machine"}, {description, "The EMQX Machine"}, % strict semver, bump manually! - {vsn, "0.2.4"}, + {vsn, "0.2.5"}, {modules, []}, {registered, []}, {applications, [kernel, stdlib, emqx_ctl]}, From 87ebbc2cf05d869327faa4ffce8bbf2064d5c3fe Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Mon, 29 May 2023 09:06:53 +0200 Subject: [PATCH 15/72] docs: format en changelog for v5.0.26 --- changes/v5.0.26.en.md | 107 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 changes/v5.0.26.en.md diff --git a/changes/v5.0.26.en.md b/changes/v5.0.26.en.md new file mode 100644 index 000000000..c79131bec --- /dev/null +++ b/changes/v5.0.26.en.md @@ -0,0 +1,107 @@ +# + +## Enhancements + +- [#10584](https://github.com/emqx/emqx/pull/10584) Add log level configuration to SSL communication + +- [#10702](https://github.com/emqx/emqx/pull/10702) Introduce a more straightforward configuration option `keepalive_multiplier` and + deprecate the old `keepalive_backoff` configuration. + After this enhancement, EMQX checks the client's keepalive timeout status + period by multiplying the "Client Requested Keepalive Interval" with `keepalive_multiplier`. + +- [#10713](https://github.com/emqx/emqx/pull/10713) We hide the request_timeout in resource_option of the webhook to keep it consistent with the http request_timeout of the webhook. + From now on, when configuring a webhook through API or configuration files, + it is no longer necessary to configure the request_timeout of the resource. Only configuring the http request_timeout is sufficient, and the request_timeout in the resource will automatically be consistent with the http request_timeout. + +- [#10511](https://github.com/emqx/emqx/pull/10511) Improve the security and privacy of some resource logs by masking sensitive information in the data. + +- [#10678](https://github.com/emqx/emqx/pull/10678) Optimized counter increment calls to avoid work if increment is zero. + +- [#10690](https://github.com/emqx/emqx/pull/10690) Added a retry mechanism to webhook bridge that attempts to improve throughput. + + This optimization retries request failures without blocking the buffering layer, which can improve throughput in situations of high messaging rate. + +- [#10698](https://github.com/emqx/emqx/pull/10698) Optimize memory usage when accessing the configuration during runtime. + +## Bug Fixes + +- [#10340](https://github.com/emqx/emqx/pull/10340) Fixed the issue that could lead to crash logs being printed when stopping EMQX via systemd. + ``` + 2023-03-29T16:43:25.915761+08:00 [error] Generic server memsup terminating. Reason: {port_died,normal}. Last message: {'EXIT',<0.2117.0>,{port_died,normal}}. State: [{data,[{"Timeout",60000}]},{items,{"Memory Usage",[{"Allocated",929959936},{"Total",3832242176}]}},{items,{"Worst Memory User",[{"Pid",<0.2031.0>},{"Memory",4720472}]}}]. + 2023-03-29T16:43:25.924764+08:00 [error] crasher: initial call: memsup:init/1, pid: <0.2116.0>, registered_name: memsup, exit: {{port_died,normal},[{gen_server,handle_common_reply,8,[{file,"gen_server.erl"},{line,811}]},{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,226}]}]}, ancestors: [os_mon_sup,<0.2114.0>], message_queue_len: 0, messages: [], links: [<0.2115.0>], dictionary: [], trap_exit: true, status: running, heap_size: 4185, stack_size: 29, reductions: 187637; neighbours: + 2023-03-29T16:43:25.924979+08:00 [error] Supervisor: {local,os_mon_sup}. Context: child_terminated. Reason: {port_died,normal}. Offender: id=memsup,pid=<0.2116.0>. + ``` + +- [#10563](https://github.com/emqx/emqx/pull/10563) Corrected an issue where the no_local flag was not functioning correctly. + + +- [#10600](https://github.com/emqx/emqx/pull/10600) Deleted emqx_statsd application. + + +- [#10653](https://github.com/emqx/emqx/pull/10653) Store gateway authentication TLS certificates and keys in the data directory. + +- [#10677](https://github.com/emqx/emqx/pull/10677) In Rule API, reapond with 404 HTTP error code when trying to delete a rule that does not exist. + +- [#10682](https://github.com/emqx/emqx/pull/10682) Fix the timestamp for the will message is incorrectly assigned at the session creation time, now this timestamp is the disconnected time of the session. + +- [#10701](https://github.com/emqx/emqx/pull/10701) RPM package for Amazon Linux 2 did not support TLS v1.3 as it was assembled with Erlang/OTP built with openssl 1.0. + +- [#10715](https://github.com/emqx/emqx/pull/10715) Postpone trimming the connection information structure until after `client.connected` hooks have been executed. These hooks once again have access to the client's peer certificate. + +- [#10717](https://github.com/emqx/emqx/pull/10717) Fixed an issue where the buffering layer processes could use a lot of CPU when inflight window is full. + +- [#10724](https://github.com/emqx/emqx/pull/10724) A summary has been added for all endpoints in the HTTP API documentation (accessible at "http://emqx_host_name:18083/api-docs"). + +- [#10726](https://github.com/emqx/emqx/pull/10726) Validate Health Check Interval and Auto Restart Interval against the range from 1ms to 1 hour. + +- [#10728](https://github.com/emqx/emqx/pull/10728) Fixed an issue where the rule engine was unable to access variables exported by `FOREACH` in the `DO` clause. + + Given a payload: `{"date": "2023-05-06", "array": ["a"]}`, as well as the following SQL statement: + ``` + FOREACH payload.date as date, payload.array as elem + DO date, elem + FROM "t/#" + ``` + Prior to the fix, the `date` variable exported by `FOREACH` could not be accessed in the `DO` clause of the above SQL, resulting in the following output for the SQL statement: + `[{"elem": "a","date": "undefined"}]`. + After the fix, the output of the SQL statement is: `[{"elem": "a","date": "2023-05-06"}]` + +- [#10737](https://github.com/emqx/emqx/pull/10737) Fix the issue where the HTTP API interface of Gateway cannot handle ClientIDs with + special characters, such as: `!@#$%^&*()_+{}:"<>?/`. + +- [#10742](https://github.com/emqx/emqx/pull/10742) Check the correctness of the rules before saving the authorization file source. + Previously, Saving wrong rules could lead to restart failure. + +- [#10743](https://github.com/emqx/emqx/pull/10743) Fixes an issue where trying to get a bridge info or metrics could result in a crash when a node is joining a cluster. + +- [#10746](https://github.com/emqx/emqx/pull/10746) Add missing support of the event `$events/delivery_dropped` into the rule engine test API `rule_test`. + +- [#10747](https://github.com/emqx/emqx/pull/10747) Refactor date and time functions, `format_date` and `date_to_unix_ts`, in the rule engine to fix the implementation problem. + +- [#10755](https://github.com/emqx/emqx/pull/10755) Fixed data bridge resource update race condition. + + In the 'delete + create' process for EMQX resource updates, + long bridge creation times could cause dashboard request timeouts. + If a bridge resource update was initiated before completion of its creation, + it led to an erroneous deletion from the runtime, despite being present in the config file. + + This fix addresses the race condition in bridge resource updates, + ensuring the accurate identification and addition of new resources, + maintaining consistency between runtime and configuration file statuses. + +- [#10760](https://github.com/emqx/emqx/pull/10760) Fix Internal Error 500 that occurred sometimes when bridge statistics page was updated while a node was (re)joining the cluster. + +- [#10761](https://github.com/emqx/emqx/pull/10761) Fixing the issue where the default value of SSL certificate for Dashboard Listener was not correctly interpolated, which caused HTTPS to be inaccessible when verify_peer and cacertfile were using the default configuration. + +- [#10785](https://github.com/emqx/emqx/pull/10785) Ensure `EMQX_LOG_DIR` is set by Windows boot script. + + The environment variable `EMQX_LOG_DIR` was missing in v5.0.25, caused EMQX Windows package fail to boot unless set by sysadmin. + +- [#10801](https://github.com/emqx/emqx/pull/10801) Avoid duplicated percent decode the topic name in API `/topics/{topic}` and `/topics`. + +- [#10809](https://github.com/emqx/emqx/pull/10809) Address `** ERROR ** Mnesia post_commit hook failed: error:badarg` error messages happening during node shutdown or restart. + Mria pull request: https://github.com/emqx/mria/pull/142 + +- [#10817](https://github.com/emqx/emqx/pull/10817) Fix the error of not being able to configure `auto_restart_interval` as infinity + +- [#10818](https://github.com/emqx/emqx/pull/10818) Fixing `emqx_ctl traces` command. From a4e0fccbf42421e1a9f280bf3fc8c63549208d38 Mon Sep 17 00:00:00 2001 From: Kinplemelon Date: Mon, 29 May 2023 18:06:06 +0800 Subject: [PATCH 16/72] chore: upgrade dashboard to v1.2.5-1 for ce --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b579a078d..add7e4291 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ endif # Dashbord version # from https://github.com/emqx/emqx-dashboard5 -export EMQX_DASHBOARD_VERSION ?= v1.2.5 +export EMQX_DASHBOARD_VERSION ?= v1.2.5-1 export EMQX_EE_DASHBOARD_VERSION ?= e1.0.7 # `:=` should be used here, otherwise the `$(shell ...)` will be executed every time when the variable is used From adae1aec9620f563c16544464285b4b074668adf Mon Sep 17 00:00:00 2001 From: JimMoen Date: Mon, 29 May 2023 19:08:15 +0800 Subject: [PATCH 17/72] chore: fix typo for enterprise readme --- apps/emqx_enterprise/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/emqx_enterprise/README.md b/apps/emqx_enterprise/README.md index 06f249581..91a93a520 100644 --- a/apps/emqx_enterprise/README.md +++ b/apps/emqx_enterprise/README.md @@ -1,6 +1,5 @@ # EMQX Enterprise Application -This application so fart only holds EMQX config schema for enterprise edition. +This application only holds EMQX config schema for enterprise edition so far. In the future this application will collect more responsibilities in managing enterprise edition specific features. - From 9526d080affa41ac38483b36b9b19e52e6074d26 Mon Sep 17 00:00:00 2001 From: JimMoen Date: Mon, 29 May 2023 19:10:31 +0800 Subject: [PATCH 18/72] fix: override `emqx_license` is unnecessary * continue #10820 --- apps/emqx_dashboard/src/emqx_dashboard.app.src | 2 +- apps/emqx_enterprise/src/emqx_enterprise_schema.erl | 2 +- apps/emqx_machine/src/emqx_machine.app.src | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/emqx_dashboard/src/emqx_dashboard.app.src b/apps/emqx_dashboard/src/emqx_dashboard.app.src index 02fbdfb74..04658ab3c 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard.app.src +++ b/apps/emqx_dashboard/src/emqx_dashboard.app.src @@ -2,7 +2,7 @@ {application, emqx_dashboard, [ {description, "EMQX Web Dashboard"}, % strict semver, bump manually! - {vsn, "5.0.21"}, + {vsn, "5.0.22"}, {modules, []}, {registered, [emqx_dashboard_sup]}, {applications, [kernel, stdlib, mnesia, minirest, emqx, emqx_ctl]}, diff --git a/apps/emqx_enterprise/src/emqx_enterprise_schema.erl b/apps/emqx_enterprise/src/emqx_enterprise_schema.erl index d128aa844..67e8a9c9a 100644 --- a/apps/emqx_enterprise/src/emqx_enterprise_schema.erl +++ b/apps/emqx_enterprise/src/emqx_enterprise_schema.erl @@ -64,7 +64,7 @@ redefine_roots(Roots) -> override(Roots, Overrides). redefine_node(Fields) -> - Overrides = [{"applications", #{default => <<"emqx_license">>}}], + Overrides = [], override(Fields, Overrides). override(Fields, []) -> diff --git a/apps/emqx_machine/src/emqx_machine.app.src b/apps/emqx_machine/src/emqx_machine.app.src index 7cf0e4b53..5cfa80369 100644 --- a/apps/emqx_machine/src/emqx_machine.app.src +++ b/apps/emqx_machine/src/emqx_machine.app.src @@ -3,7 +3,7 @@ {id, "emqx_machine"}, {description, "The EMQX Machine"}, % strict semver, bump manually! - {vsn, "0.2.4"}, + {vsn, "0.2.5"}, {modules, []}, {registered, []}, {applications, [kernel, stdlib, emqx_ctl]}, From 79f686149970d706e182ef0f674fe80b2a28e867 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Mon, 29 May 2023 09:17:59 -0300 Subject: [PATCH 19/72] chore: prepare to tag v5.0.26 --- apps/emqx/include/emqx_release.hrl | 2 +- apps/emqx_dashboard/src/emqx_dashboard.app.src | 2 +- deploy/charts/emqx/Chart.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/emqx/include/emqx_release.hrl b/apps/emqx/include/emqx_release.hrl index f9154f987..e13dd0e52 100644 --- a/apps/emqx/include/emqx_release.hrl +++ b/apps/emqx/include/emqx_release.hrl @@ -32,7 +32,7 @@ %% `apps/emqx/src/bpapi/README.md' %% Community edition --define(EMQX_RELEASE_CE, "5.0.26-alpha.1"). +-define(EMQX_RELEASE_CE, "5.0.26"). %% Enterprise edition -define(EMQX_RELEASE_EE, "5.0.4"). diff --git a/apps/emqx_dashboard/src/emqx_dashboard.app.src b/apps/emqx_dashboard/src/emqx_dashboard.app.src index 02fbdfb74..04658ab3c 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard.app.src +++ b/apps/emqx_dashboard/src/emqx_dashboard.app.src @@ -2,7 +2,7 @@ {application, emqx_dashboard, [ {description, "EMQX Web Dashboard"}, % strict semver, bump manually! - {vsn, "5.0.21"}, + {vsn, "5.0.22"}, {modules, []}, {registered, [emqx_dashboard_sup]}, {applications, [kernel, stdlib, mnesia, minirest, emqx, emqx_ctl]}, diff --git a/deploy/charts/emqx/Chart.yaml b/deploy/charts/emqx/Chart.yaml index ee2ae4be2..11bc95951 100644 --- a/deploy/charts/emqx/Chart.yaml +++ b/deploy/charts/emqx/Chart.yaml @@ -14,8 +14,8 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 5.0.25 +version: 5.0.26 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. -appVersion: 5.0.25 +appVersion: 5.0.26 From 7032881790650b771fc08ac31830b45ee7b651d2 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Mon, 29 May 2023 11:06:34 +0200 Subject: [PATCH 20/72] docs: delete i18n/zh from this repo --- rel/i18n/zh/emqx_authn_api.hocon | 96 -- rel/i18n/zh/emqx_authn_http.hocon | 45 - rel/i18n/zh/emqx_authn_jwt.hocon | 118 -- rel/i18n/zh/emqx_authn_mnesia.hocon | 12 - rel/i18n/zh/emqx_authn_mongodb.hocon | 45 - rel/i18n/zh/emqx_authn_mysql.hocon | 18 - rel/i18n/zh/emqx_authn_pgsql.hocon | 12 - rel/i18n/zh/emqx_authn_redis.hocon | 18 - rel/i18n/zh/emqx_authn_schema.hocon | 135 -- rel/i18n/zh/emqx_authn_user_import_api.hocon | 9 - rel/i18n/zh/emqx_authz_api_cache.hocon | 6 - rel/i18n/zh/emqx_authz_api_mnesia.hocon | 87 - rel/i18n/zh/emqx_authz_api_schema.hocon | 90 - rel/i18n/zh/emqx_authz_api_settings.hocon | 9 - rel/i18n/zh/emqx_authz_api_sources.hocon | 48 - rel/i18n/zh/emqx_authz_schema.hocon | 285 ---- rel/i18n/zh/emqx_auto_subscribe_api.hocon | 12 - rel/i18n/zh/emqx_auto_subscribe_schema.hocon | 48 - rel/i18n/zh/emqx_bridge_api.hocon | 100 -- rel/i18n/zh/emqx_bridge_clickhouse.hocon | 46 - .../zh/emqx_bridge_clickhouse_connector.hocon | 15 - rel/i18n/zh/emqx_bridge_dynamo.hocon | 40 - .../zh/emqx_bridge_dynamo_connector.hocon | 27 - rel/i18n/zh/emqx_bridge_gcp_pubsub.hocon | 77 - rel/i18n/zh/emqx_bridge_influxdb.hocon | 47 - .../zh/emqx_bridge_influxdb_connector.hocon | 71 - rel/i18n/zh/emqx_bridge_kafka.hocon | 354 ---- rel/i18n/zh/emqx_bridge_mqtt_schema.hocon | 21 - rel/i18n/zh/emqx_bridge_opents.hocon | 26 - .../zh/emqx_bridge_opents_connector.hocon | 20 - rel/i18n/zh/emqx_bridge_oracle.hocon | 56 - rel/i18n/zh/emqx_bridge_pgsql.hocon | 40 - rel/i18n/zh/emqx_bridge_pulsar.hocon | 178 -- rel/i18n/zh/emqx_bridge_rocketmq.hocon | 43 - .../zh/emqx_bridge_rocketmq_connector.hocon | 53 - rel/i18n/zh/emqx_bridge_schema.hocon | 158 -- rel/i18n/zh/emqx_bridge_sqlserver.hocon | 46 - .../zh/emqx_bridge_sqlserver_connector.hocon | 11 - rel/i18n/zh/emqx_bridge_tdengine.hocon | 40 - .../zh/emqx_bridge_tdengine_connector.hocon | 11 - rel/i18n/zh/emqx_bridge_webhook_schema.hocon | 87 - rel/i18n/zh/emqx_coap_api.hocon | 27 - rel/i18n/zh/emqx_coap_schema.hocon | 37 - rel/i18n/zh/emqx_conf_schema.hocon | 775 --------- rel/i18n/zh/emqx_connector_api.hocon | 46 - rel/i18n/zh/emqx_connector_http.hocon | 69 - rel/i18n/zh/emqx_connector_ldap.hocon | 21 - rel/i18n/zh/emqx_connector_mongo.hocon | 152 -- rel/i18n/zh/emqx_connector_mqtt.hocon | 21 - rel/i18n/zh/emqx_connector_mqtt_schema.hocon | 170 -- rel/i18n/zh/emqx_connector_mysql.hocon | 11 - rel/i18n/zh/emqx_connector_pgsql.hocon | 11 - rel/i18n/zh/emqx_connector_redis.hocon | 50 - rel/i18n/zh/emqx_connector_schema_lib.hocon | 45 - rel/i18n/zh/emqx_dashboard_api.hocon | 66 - rel/i18n/zh/emqx_dashboard_schema.hocon | 130 -- rel/i18n/zh/emqx_delayed_api.hocon | 72 - rel/i18n/zh/emqx_ee_bridge_cassa.hocon | 40 - rel/i18n/zh/emqx_ee_bridge_hstreamdb.hocon | 52 - rel/i18n/zh/emqx_ee_bridge_mongodb.hocon | 57 - rel/i18n/zh/emqx_ee_bridge_mysql.hocon | 40 - rel/i18n/zh/emqx_ee_bridge_redis.hocon | 41 - rel/i18n/zh/emqx_ee_connector_cassa.hocon | 17 - rel/i18n/zh/emqx_ee_connector_hstreamdb.hocon | 45 - .../zh/emqx_ee_schema_registry_http_api.hocon | 39 - .../zh/emqx_ee_schema_registry_schema.hocon | 51 - rel/i18n/zh/emqx_eviction_agent_api.hocon | 9 - rel/i18n/zh/emqx_exhook_api.hocon | 81 - rel/i18n/zh/emqx_exhook_schema.hocon | 43 - rel/i18n/zh/emqx_exproto_schema.hocon | 26 - rel/i18n/zh/emqx_gateway_api.hocon | 73 - rel/i18n/zh/emqx_gateway_api_authn.hocon | 45 - rel/i18n/zh/emqx_gateway_api_clients.hocon | 207 --- rel/i18n/zh/emqx_gateway_api_listeners.hocon | 65 - rel/i18n/zh/emqx_gateway_schema.hocon | 112 -- rel/i18n/zh/emqx_license_http_api.hocon | 15 - rel/i18n/zh/emqx_license_schema.hocon | 29 - rel/i18n/zh/emqx_limiter_schema.hocon | 89 - rel/i18n/zh/emqx_lwm2m_api.hocon | 27 - rel/i18n/zh/emqx_lwm2m_schema.hocon | 56 - rel/i18n/zh/emqx_mgmt_api_alarms.hocon | 37 - rel/i18n/zh/emqx_mgmt_api_banned.hocon | 54 - rel/i18n/zh/emqx_mgmt_api_key_schema.hocon | 19 - rel/i18n/zh/emqx_mgmt_api_publish.hocon | 81 - rel/i18n/zh/emqx_mgmt_api_status.hocon | 34 - rel/i18n/zh/emqx_modules_schema.hocon | 45 - rel/i18n/zh/emqx_mqttsn_schema.hocon | 30 - rel/i18n/zh/emqx_node_rebalance_api.hocon | 266 --- rel/i18n/zh/emqx_oracle.hocon | 20 - rel/i18n/zh/emqx_plugins_schema.hocon | 46 - rel/i18n/zh/emqx_prometheus_schema.hocon | 47 - rel/i18n/zh/emqx_psk_schema.hocon | 28 - rel/i18n/zh/emqx_resource_schema.hocon | 112 -- rel/i18n/zh/emqx_retainer_api.hocon | 63 - rel/i18n/zh/emqx_retainer_schema.hocon | 46 - rel/i18n/zh/emqx_rewrite_api.hocon | 12 - rel/i18n/zh/emqx_rule_api_schema.hocon | 381 ----- rel/i18n/zh/emqx_rule_engine_api.hocon | 93 -- rel/i18n/zh/emqx_rule_engine_schema.hocon | 184 --- rel/i18n/zh/emqx_schema.hocon | 1471 ----------------- rel/i18n/zh/emqx_slow_subs_api.hocon | 30 - rel/i18n/zh/emqx_slow_subs_schema.hocon | 18 - rel/i18n/zh/emqx_stomp_schema.hocon | 15 - rel/i18n/zh/emqx_telemetry_api.hocon | 54 - rel/i18n/zh/emqx_telemetry_schema.hocon | 8 - rel/i18n/zh/emqx_topic_metrics_api.hocon | 105 -- 106 files changed, 8921 deletions(-) delete mode 100644 rel/i18n/zh/emqx_authn_api.hocon delete mode 100644 rel/i18n/zh/emqx_authn_http.hocon delete mode 100644 rel/i18n/zh/emqx_authn_jwt.hocon delete mode 100644 rel/i18n/zh/emqx_authn_mnesia.hocon delete mode 100644 rel/i18n/zh/emqx_authn_mongodb.hocon delete mode 100644 rel/i18n/zh/emqx_authn_mysql.hocon delete mode 100644 rel/i18n/zh/emqx_authn_pgsql.hocon delete mode 100644 rel/i18n/zh/emqx_authn_redis.hocon delete mode 100644 rel/i18n/zh/emqx_authn_schema.hocon delete mode 100644 rel/i18n/zh/emqx_authn_user_import_api.hocon delete mode 100644 rel/i18n/zh/emqx_authz_api_cache.hocon delete mode 100644 rel/i18n/zh/emqx_authz_api_mnesia.hocon delete mode 100644 rel/i18n/zh/emqx_authz_api_schema.hocon delete mode 100644 rel/i18n/zh/emqx_authz_api_settings.hocon delete mode 100644 rel/i18n/zh/emqx_authz_api_sources.hocon delete mode 100644 rel/i18n/zh/emqx_authz_schema.hocon delete mode 100644 rel/i18n/zh/emqx_auto_subscribe_api.hocon delete mode 100644 rel/i18n/zh/emqx_auto_subscribe_schema.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_api.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_clickhouse.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_clickhouse_connector.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_dynamo.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_dynamo_connector.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_gcp_pubsub.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_influxdb.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_influxdb_connector.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_kafka.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_mqtt_schema.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_opents.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_opents_connector.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_oracle.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_pgsql.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_pulsar.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_rocketmq.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_rocketmq_connector.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_schema.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_sqlserver.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_sqlserver_connector.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_tdengine.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_tdengine_connector.hocon delete mode 100644 rel/i18n/zh/emqx_bridge_webhook_schema.hocon delete mode 100644 rel/i18n/zh/emqx_coap_api.hocon delete mode 100644 rel/i18n/zh/emqx_coap_schema.hocon delete mode 100644 rel/i18n/zh/emqx_conf_schema.hocon delete mode 100644 rel/i18n/zh/emqx_connector_api.hocon delete mode 100644 rel/i18n/zh/emqx_connector_http.hocon delete mode 100644 rel/i18n/zh/emqx_connector_ldap.hocon delete mode 100644 rel/i18n/zh/emqx_connector_mongo.hocon delete mode 100644 rel/i18n/zh/emqx_connector_mqtt.hocon delete mode 100644 rel/i18n/zh/emqx_connector_mqtt_schema.hocon delete mode 100644 rel/i18n/zh/emqx_connector_mysql.hocon delete mode 100644 rel/i18n/zh/emqx_connector_pgsql.hocon delete mode 100644 rel/i18n/zh/emqx_connector_redis.hocon delete mode 100644 rel/i18n/zh/emqx_connector_schema_lib.hocon delete mode 100644 rel/i18n/zh/emqx_dashboard_api.hocon delete mode 100644 rel/i18n/zh/emqx_dashboard_schema.hocon delete mode 100644 rel/i18n/zh/emqx_delayed_api.hocon delete mode 100644 rel/i18n/zh/emqx_ee_bridge_cassa.hocon delete mode 100644 rel/i18n/zh/emqx_ee_bridge_hstreamdb.hocon delete mode 100644 rel/i18n/zh/emqx_ee_bridge_mongodb.hocon delete mode 100644 rel/i18n/zh/emqx_ee_bridge_mysql.hocon delete mode 100644 rel/i18n/zh/emqx_ee_bridge_redis.hocon delete mode 100644 rel/i18n/zh/emqx_ee_connector_cassa.hocon delete mode 100644 rel/i18n/zh/emqx_ee_connector_hstreamdb.hocon delete mode 100644 rel/i18n/zh/emqx_ee_schema_registry_http_api.hocon delete mode 100644 rel/i18n/zh/emqx_ee_schema_registry_schema.hocon delete mode 100644 rel/i18n/zh/emqx_eviction_agent_api.hocon delete mode 100644 rel/i18n/zh/emqx_exhook_api.hocon delete mode 100644 rel/i18n/zh/emqx_exhook_schema.hocon delete mode 100644 rel/i18n/zh/emqx_exproto_schema.hocon delete mode 100644 rel/i18n/zh/emqx_gateway_api.hocon delete mode 100644 rel/i18n/zh/emqx_gateway_api_authn.hocon delete mode 100644 rel/i18n/zh/emqx_gateway_api_clients.hocon delete mode 100644 rel/i18n/zh/emqx_gateway_api_listeners.hocon delete mode 100644 rel/i18n/zh/emqx_gateway_schema.hocon delete mode 100644 rel/i18n/zh/emqx_license_http_api.hocon delete mode 100644 rel/i18n/zh/emqx_license_schema.hocon delete mode 100644 rel/i18n/zh/emqx_limiter_schema.hocon delete mode 100644 rel/i18n/zh/emqx_lwm2m_api.hocon delete mode 100644 rel/i18n/zh/emqx_lwm2m_schema.hocon delete mode 100644 rel/i18n/zh/emqx_mgmt_api_alarms.hocon delete mode 100644 rel/i18n/zh/emqx_mgmt_api_banned.hocon delete mode 100644 rel/i18n/zh/emqx_mgmt_api_key_schema.hocon delete mode 100644 rel/i18n/zh/emqx_mgmt_api_publish.hocon delete mode 100644 rel/i18n/zh/emqx_mgmt_api_status.hocon delete mode 100644 rel/i18n/zh/emqx_modules_schema.hocon delete mode 100644 rel/i18n/zh/emqx_mqttsn_schema.hocon delete mode 100644 rel/i18n/zh/emqx_node_rebalance_api.hocon delete mode 100644 rel/i18n/zh/emqx_oracle.hocon delete mode 100644 rel/i18n/zh/emqx_plugins_schema.hocon delete mode 100644 rel/i18n/zh/emqx_prometheus_schema.hocon delete mode 100644 rel/i18n/zh/emqx_psk_schema.hocon delete mode 100644 rel/i18n/zh/emqx_resource_schema.hocon delete mode 100644 rel/i18n/zh/emqx_retainer_api.hocon delete mode 100644 rel/i18n/zh/emqx_retainer_schema.hocon delete mode 100644 rel/i18n/zh/emqx_rewrite_api.hocon delete mode 100644 rel/i18n/zh/emqx_rule_api_schema.hocon delete mode 100644 rel/i18n/zh/emqx_rule_engine_api.hocon delete mode 100644 rel/i18n/zh/emqx_rule_engine_schema.hocon delete mode 100644 rel/i18n/zh/emqx_schema.hocon delete mode 100644 rel/i18n/zh/emqx_slow_subs_api.hocon delete mode 100644 rel/i18n/zh/emqx_slow_subs_schema.hocon delete mode 100644 rel/i18n/zh/emqx_stomp_schema.hocon delete mode 100644 rel/i18n/zh/emqx_telemetry_api.hocon delete mode 100644 rel/i18n/zh/emqx_telemetry_schema.hocon delete mode 100644 rel/i18n/zh/emqx_topic_metrics_api.hocon diff --git a/rel/i18n/zh/emqx_authn_api.hocon b/rel/i18n/zh/emqx_authn_api.hocon deleted file mode 100644 index bd8332ac7..000000000 --- a/rel/i18n/zh/emqx_authn_api.hocon +++ /dev/null @@ -1,96 +0,0 @@ -emqx_authn_api { - -authentication_get.desc: -"""列出全局认证链上的认证器。""" - -authentication_id_delete.desc: -"""删除全局认证链上的指定认证器。""" - -authentication_id_get.desc: -"""获取全局认证链上的指定认证器。""" - -authentication_id_position_put.desc: -"""更改全局认证链上指定认证器的顺序。""" - -authentication_id_put.desc: -"""更新全局认证链上的指定认证器。""" - -authentication_id_status_get.desc: -"""获取全局认证链上指定认证器的状态。""" - -authentication_id_users_get.desc: -"""获取全局认证链上指定认证器中的用户数据。""" - -authentication_id_users_post.desc: -"""为全局认证链上的指定认证器创建用户数据。""" - -authentication_id_users_user_id_delete.desc: -"""删除全局认证链上指定认证器中的指定用户数据。""" - -authentication_id_users_user_id_get.desc: -"""获取全局认证链上指定认证器中的指定用户数据。""" - -authentication_id_users_user_id_put.desc: -"""更新全局认证链上指定认证器中的指定用户数据。""" - -authentication_post.desc: -"""为全局认证链创建认证器。""" - -is_superuser.desc: -"""是否是超级用户""" - -like_user_id.desc: -"""使用用户 ID (username 或 clientid)模糊查询。""" - -like_user_id.label: -"""like_user_id""" - -listeners_listener_id_authentication_get.desc: -"""列出监听器认证链上的认证器。""" - -listeners_listener_id_authentication_id_delete.desc: -"""删除监听器认证链上的指定认证器。""" - -listeners_listener_id_authentication_id_get.desc: -"""获取监听器认证链上的指定认证器。""" - -listeners_listener_id_authentication_id_position_put.desc: -"""更改监听器认证链上指定认证器的顺序。""" - -listeners_listener_id_authentication_id_put.desc: -"""更新监听器认证链上的指定认证器。""" - -listeners_listener_id_authentication_id_status_get.desc: -"""获取监听器认证链上指定认证器的状态。""" - -listeners_listener_id_authentication_id_users_get.desc: -"""列出监听器认证链上指定认证器中的用户数据。""" - -listeners_listener_id_authentication_id_users_post.desc: -"""为监听器认证链上的指定认证器创建用户数据。""" - -listeners_listener_id_authentication_id_users_user_id_delete.desc: -"""删除监听器认证链上指定认证器中的指定用户数据。""" - -listeners_listener_id_authentication_id_users_user_id_get.desc: -"""获取监听器认证链上指定认证器中的指定用户数据。""" - -listeners_listener_id_authentication_id_users_user_id_put.desc: -"""更新监听器认证链上指定认证器中的指定用户数据。""" - -listeners_listener_id_authentication_post.desc: -"""在监听器认证链上创建认证器。""" - -param_auth_id.desc: -"""认证器 ID。""" - -param_listener_id.desc: -"""监听器 ID。""" - -param_position.desc: -"""认证者在链中的位置。可能的值是 'front', 'rear', 'before:{other_authenticator}', 'after:{other_authenticator}'""" - -param_user_id.desc: -"""用户 ID。""" - -} diff --git a/rel/i18n/zh/emqx_authn_http.hocon b/rel/i18n/zh/emqx_authn_http.hocon deleted file mode 100644 index 17c922b33..000000000 --- a/rel/i18n/zh/emqx_authn_http.hocon +++ /dev/null @@ -1,45 +0,0 @@ -emqx_authn_http { - -body.desc: -"""HTTP request body。""" - -body.label: -"""Request Body""" - -get.desc: -"""使用 HTTP Server 作为认证服务的认证器的配置项 (使用 GET 请求)。""" - -headers.desc: -"""HTTP Headers 列表""" - -headers.label: -"""请求头""" - -headers_no_content_type.desc: -"""HTTP Headers 列表 (无 content-type) 。""" - -headers_no_content_type.label: -"""请求头(无 content-type)""" - -method.desc: -"""HTTP 请求方法。""" - -method.label: -"""请求方法""" - -post.desc: -"""使用 HTTP Server 作为认证服务的认证器的配置项 (使用 POST 请求)。""" - -request_timeout.desc: -"""HTTP 请求超时时长。""" - -request_timeout.label: -"""请求超时时间""" - -url.desc: -"""认证 HTTP 服务器地址。""" - -url.label: -"""URL""" - -} diff --git a/rel/i18n/zh/emqx_authn_jwt.hocon b/rel/i18n/zh/emqx_authn_jwt.hocon deleted file mode 100644 index 76b999101..000000000 --- a/rel/i18n/zh/emqx_authn_jwt.hocon +++ /dev/null @@ -1,118 +0,0 @@ -emqx_authn_jwt { - -acl_claim_name.desc: -"""JWT claim name to use for getting ACL rules.""" - -acl_claim_name.label: -"""ACL claim name""" - -algorithm.desc: -"""JWT 签名算法,支持 HMAC (配置为 hmac-based)和 RSA、ECDSA (配置为 public-key)。""" - -algorithm.label: -"""JWT 签名算法""" - -cacertfile.desc: -"""包含 PEM 编码的 CA 证书的文件的路径。""" - -cacertfile.label: -"""CA 证书文件""" - -certfile.desc: -"""包含用户证书的文件的路径。""" - -certfile.label: -"""证书文件""" - -enable.desc: -"""启用/禁用 SSL。""" - -enable.label: -"""启用/禁用 SSL""" - -endpoint.desc: -"""JWKS 端点, 它是一个以 JWKS 格式返回服务端的公钥集的只读端点。""" - -endpoint.label: -"""JWKS Endpoint""" - -from.desc: -"""要从中获取 JWT 的字段。""" - -from.label: -"""源字段""" - -jwt_hmac.desc: -"""用于认证的 JWT 使用 HMAC 算法签发时的配置。""" - -jwt_jwks.desc: -"""用于认证的 JWTs 需要从 JWKS 端点获取时的配置。""" - -keyfile.desc: -"""包含 PEM 编码的用户私钥的文件的路径。""" - -keyfile.label: -"""私钥文件""" - -jwt_public_key.desc: -"""用于认证的 JWT 使用 RSA 或 ECDSA 算法签发时的配置。""" - -public_key.desc: -"""用于验证 JWT 的公钥。""" - -public_key.label: -"""公钥""" - -refresh_interval.desc: -"""JWKS 刷新间隔。""" - -refresh_interval.label: -"""JWKS 刷新间隔""" - -secret.desc: -"""使用 HMAC 算法时用于验证 JWT 的密钥""" - -secret.label: -"""Secret""" - -secret_base64_encoded.desc: -"""密钥是否为 Base64 编码。""" - -secret_base64_encoded.label: -"""密钥是否为 Base64 编码""" - -server_name_indication.desc: -"""服务器名称指示(SNI)。""" - -server_name_indication.label: -"""服务器名称指示""" - -ssl.desc: -"""SSL 选项。""" - -ssl.label: -"""SSL 选项""" - -use_jwks.desc: -"""是否使用 JWKS。""" - -use_jwks.label: -"""是否使用 JWKS""" - -verify.desc: -"""指定握手过程中是否校验对端证书。""" - -verify.label: -"""Verify""" - -verify_claims.desc: -"""需要验证的自定义声明列表,它是一个名称/值对列表。 -值可以使用以下占位符: -- ${username}: 将在运行时被替换为客户端连接时使用的用户名 -- ${clientid}: 将在运行时被替换为客户端连接时使用的客户端标识符 -认证时将验证 JWT(取自 Password 字段)中 claims 的值是否与 verify_claims 中要求的相匹配。""" - -verify_claims.label: -"""Verify Claims""" - -} diff --git a/rel/i18n/zh/emqx_authn_mnesia.hocon b/rel/i18n/zh/emqx_authn_mnesia.hocon deleted file mode 100644 index 78fc96c75..000000000 --- a/rel/i18n/zh/emqx_authn_mnesia.hocon +++ /dev/null @@ -1,12 +0,0 @@ -emqx_authn_mnesia { - -builtin_db.desc: -"""使用内置数据库作为认证数据源的认证器的配置项。""" - -user_id_type.desc: -"""指定使用客户端ID `clientid` 还是用户名 `username` 进行认证。""" - -user_id_type.label: -"""认证 ID 类型""" - -} diff --git a/rel/i18n/zh/emqx_authn_mongodb.hocon b/rel/i18n/zh/emqx_authn_mongodb.hocon deleted file mode 100644 index 949322a76..000000000 --- a/rel/i18n/zh/emqx_authn_mongodb.hocon +++ /dev/null @@ -1,45 +0,0 @@ -emqx_authn_mongodb { - -collection.desc: -"""存储认证数据的集合。""" - -collection.label: -"""集合""" - -filter.desc: -"""在查询中定义过滤条件的条件表达式。 -过滤器支持如下占位符: -- ${username}: 将在运行时被替换为客户端连接时使用的用户名 -- ${clientid}: 将在运行时被替换为客户端连接时使用的客户端标识符""" - -filter.label: -"""过滤器""" - -is_superuser_field.desc: -"""文档中用于定义用户是否具有超级用户权限的字段。""" - -is_superuser_field.label: -"""超级用户字段""" - -password_hash_field.desc: -"""文档中用于存放密码散列的字段。""" - -password_hash_field.label: -"""密码散列字段""" - -replica-set.desc: -"""使用 MongoDB (Replica Set) 作为认证数据源的认证器的配置项。""" - -salt_field.desc: -"""文档中用于存放盐值的字段。""" - -salt_field.label: -"""盐值字段""" - -sharded-cluster.desc: -"""使用 MongoDB (Sharded Cluster) 作为认证数据源的认证器的配置项。""" - -single.desc: -"""使用 MongoDB (Standalone) 作为认证数据源的认证器的配置项。""" - -} diff --git a/rel/i18n/zh/emqx_authn_mysql.hocon b/rel/i18n/zh/emqx_authn_mysql.hocon deleted file mode 100644 index c8b72d720..000000000 --- a/rel/i18n/zh/emqx_authn_mysql.hocon +++ /dev/null @@ -1,18 +0,0 @@ -emqx_authn_mysql { - -mysql.desc: -"""使用 MySQL 作为认证数据源的认证器的配置项。""" - -query.desc: -"""用于查询密码散列等用于认证的数据的 SQL 语句。""" - -query.label: -"""查询语句""" - -query_timeout.desc: -"""SQL 查询的超时时间。""" - -query_timeout.label: -"""查询超时""" - -} diff --git a/rel/i18n/zh/emqx_authn_pgsql.hocon b/rel/i18n/zh/emqx_authn_pgsql.hocon deleted file mode 100644 index b9d47828b..000000000 --- a/rel/i18n/zh/emqx_authn_pgsql.hocon +++ /dev/null @@ -1,12 +0,0 @@ -emqx_authn_pgsql { - -postgresql.desc: -"""使用 PostgreSQL 作为认证数据源的认证器的配置项。""" - -query.desc: -"""用于查询密码散列等用于认证的数据的 SQL 语句。""" - -query.label: -"""查询语句""" - -} diff --git a/rel/i18n/zh/emqx_authn_redis.hocon b/rel/i18n/zh/emqx_authn_redis.hocon deleted file mode 100644 index 58137f195..000000000 --- a/rel/i18n/zh/emqx_authn_redis.hocon +++ /dev/null @@ -1,18 +0,0 @@ -emqx_authn_redis { - -cluster.desc: -"""使用 Redis (Cluster) 作为认证数据源的认证器的配置项。""" - -cmd.desc: -"""用于查询密码散列等用于认证的数据的 Redis Command,目前仅支持 HGETHMGET。""" - -cmd.label: -"""Command""" - -sentinel.desc: -"""使用 Redis (Sentinel) 作为认证数据源的认证器的配置项。""" - -single.desc: -"""使用 Redis (Standalone) 作为认证数据源的认证器的配置项。""" - -} diff --git a/rel/i18n/zh/emqx_authn_schema.hocon b/rel/i18n/zh/emqx_authn_schema.hocon deleted file mode 100644 index e6e76e8ab..000000000 --- a/rel/i18n/zh/emqx_authn_schema.hocon +++ /dev/null @@ -1,135 +0,0 @@ -emqx_authn_schema { - -backend.desc: -"""后端类型。""" - -backend.label: -"""后端类型""" - -enable.desc: -"""设为 truefalse 以启用或禁用此认证数据源。""" - -enable.label: -"""启用""" - -failed.desc: -"""请求失败次数。""" - -failed.label: -"""失败""" - -matched.desc: -"""请求命中次数。""" - -matched.label: -"""已命中""" - -mechanism.desc: -"""认证机制。""" - -mechanism.label: -"""认证机制""" - -metrics.desc: -"""资源统计指标。""" - -metrics.label: -"""指标""" - -metrics_failed.desc: -"""在当前实例中找到需要的认证信息,并且实例返回认证失败的次数。""" - -metrics_failed.label: -"""实例认证失败的次数""" - -metrics_nomatch.desc: -"""在当前实例中没有找到需要的认证信息,实例被忽略的次数。""" - -metrics_nomatch.label: -"""实例被忽略的次数""" - -metrics_rate.desc: -"""实例被触发的速率。触发速率等于匹配速率 + 忽略速率,单位:次/秒。""" - -metrics_rate.label: -"""实例被触发的速率""" - -metrics_rate_last5m.desc: -"""实例5分钟内平均触发速率,单位:次/秒。""" - -metrics_rate_last5m.label: -"""实例5分钟内平均触发速率""" - -metrics_rate_max.desc: -"""实例曾经达到的最高触发速率,单位:次/秒。""" - -metrics_rate_max.label: -"""实例曾经达到的最高触发速率""" - -metrics_success.desc: -"""在当前实例中找到需要的认证信息,并且实例返回认证成功的次数。""" - -metrics_success.label: -"""实例认证成功的次数""" - -metrics_total.desc: -"""当前实例被触发的总次数。""" - -metrics_total.label: -"""当前实例被触发的总次数""" - -node.desc: -"""节点名称。""" - -node.label: -"""节点名称。""" - -node_error.desc: -"""节点上产生的错误。""" - -node_error.label: -"""节点产生的错误""" - -node_metrics.desc: -"""每个节点上资源的统计指标。""" - -node_metrics.label: -"""节点资源指标""" - -node_status.desc: -"""每个节点上资源的状态。""" - -node_status.label: -"""节点资源状态""" - -rate.desc: -"""命中速率,单位:次/秒。""" - -rate.label: -"""速率""" - -rate_last5m.desc: -"""5分钟内平均命中速率,单位:次/秒。""" - -rate_last5m.label: -"""5分钟内速率""" - -rate_max.desc: -"""最大命中速率,单位:次/秒。""" - -rate_max.label: -"""最大速率""" - -status.desc: -"""资源状态。""" - -status.label: -"""状态""" - -success.desc: -"""请求成功次数。""" - -success.label: -"""成功""" - -} diff --git a/rel/i18n/zh/emqx_authn_user_import_api.hocon b/rel/i18n/zh/emqx_authn_user_import_api.hocon deleted file mode 100644 index a546066bb..000000000 --- a/rel/i18n/zh/emqx_authn_user_import_api.hocon +++ /dev/null @@ -1,9 +0,0 @@ -emqx_authn_user_import_api { - -authentication_id_import_users_post.desc: -"""为全局认证链上的指定认证器导入用户数据。""" - -listeners_listener_id_authentication_id_import_users_post.desc: -"""为监听器认证链上的指定认证器导入用户数据。""" - -} diff --git a/rel/i18n/zh/emqx_authz_api_cache.hocon b/rel/i18n/zh/emqx_authz_api_cache.hocon deleted file mode 100644 index 94486e3e0..000000000 --- a/rel/i18n/zh/emqx_authz_api_cache.hocon +++ /dev/null @@ -1,6 +0,0 @@ -emqx_authz_api_cache { - -authorization_cache_delete.desc: -"""清除集群中所有授权数据缓存。""" - -} diff --git a/rel/i18n/zh/emqx_authz_api_mnesia.hocon b/rel/i18n/zh/emqx_authz_api_mnesia.hocon deleted file mode 100644 index bc5121cc3..000000000 --- a/rel/i18n/zh/emqx_authz_api_mnesia.hocon +++ /dev/null @@ -1,87 +0,0 @@ -emqx_authz_api_mnesia { - -action.desc: -"""被授权的行为 (发布/订阅/所有)""" - -action.label: -"""行为""" - -clientid.desc: -"""客户端标识符""" - -clientid.label: -"""客户端标识符""" - -fuzzy_clientid.desc: -"""使用字串匹配模糊搜索客户端标识符""" - -fuzzy_clientid.label: -"""客户端标识符子串""" - -fuzzy_username.desc: -"""使用字串匹配模糊搜索用户名""" - -fuzzy_username.label: -"""用户名子串""" - -permission.desc: -"""权限""" - -permission.label: -"""权限""" - -rules_all_delete.desc: -"""删除 `all` 规则""" - -rules_all_get.desc: -"""列出为所有客户端启用的规则列表""" - -rules_all_post.desc: -"""创建/更新 为所有客户端启用的规则列表。""" - -rules_delete.desc: -"""清除内置数据库中的所有类型('users' 、'clients' 、'all')的所有规则""" - -topic.desc: -"""在指定主题上的规则""" - -topic.label: -"""主题""" - -user_clientid_delete.desc: -"""删除内置数据库中指定客户端标识符类型的规则记录""" - -user_clientid_get.desc: -"""获取内置数据库中指定客户端标识符类型的规则记录""" - -user_clientid_put.desc: -"""更新内置数据库中指定客户端标识符类型的规则记录""" - -user_username_delete.desc: -"""删除内置数据库中指定用户名类型的规则记录""" - -user_username_get.desc: -"""获取内置数据库中指定用户名类型的规则记录""" - -user_username_put.desc: -"""更新内置数据库中指定用户名类型的规则记录""" - -username.desc: -"""用户名""" - -username.label: -"""用户名""" - -users_clientid_get.desc: -"""获取内置数据库中所有客户端标识符类型的规则记录""" - -users_clientid_post.desc: -"""添加内置数据库中客户端标识符类型的规则记录""" - -users_username_get.desc: -"""获取内置数据库中所有用户名类型的规则记录""" - -users_username_post.desc: -"""添加内置数据库中用户名类型的规则记录""" - -} diff --git a/rel/i18n/zh/emqx_authz_api_schema.hocon b/rel/i18n/zh/emqx_authz_api_schema.hocon deleted file mode 100644 index 41e3819cd..000000000 --- a/rel/i18n/zh/emqx_authz_api_schema.hocon +++ /dev/null @@ -1,90 +0,0 @@ -emqx_authz_api_schema { - -body.desc: -"""HTTP 请求体。""" - -body.label: -"""请求体""" - -cmd.desc: -"""访问控制数据查询命令。""" - -cmd.label: -"""查询命令""" - -collection.desc: -"""`MongoDB` 授权数据集。""" - -collection.label: -"""数据集""" - -enable.desc: -"""设为 truefalse 以启用或禁用此访问控制数据源。""" - -enable.label: -"""enable""" - -filter.desc: -"""在查询中定义过滤条件的条件表达式。 -过滤器支持如下占位符: -- ${username}: 将在运行时被替换为客户端连接时使用的用户名 -- ${clientid}: 将在运行时被替换为客户端连接时使用的客户端标识符""" - -filter.label: -"""过滤器""" - -headers.desc: -"""HTTP Headers 列表""" - -headers.label: -"""请求头""" - -headers_no_content_type.desc: -"""HTTP Headers 列表(无 content-type)。""" - -headers_no_content_type.label: -"""请求头(无 content-type)""" - -method.desc: -"""HTTP 请求方法。""" - -method.label: -"""method""" - -position.desc: -"""认证数据源位置。""" - -position.label: -"""位置""" - -query.desc: -"""访问控制数据查询语句。""" - -query.label: -"""查询语句""" - -request_timeout.desc: -"""请求超时时间。""" - -request_timeout.label: -"""请求超时""" - -rules.desc: -"""静态授权文件规则。""" - -rules.label: -"""规则""" - -type.desc: -"""数据后端类型。""" - -type.label: -"""type""" - -url.desc: -"""认证服务器 URL。""" - -url.label: -"""url""" - -} diff --git a/rel/i18n/zh/emqx_authz_api_settings.hocon b/rel/i18n/zh/emqx_authz_api_settings.hocon deleted file mode 100644 index c78fb7a0b..000000000 --- a/rel/i18n/zh/emqx_authz_api_settings.hocon +++ /dev/null @@ -1,9 +0,0 @@ -emqx_authz_api_settings { - -authorization_settings_get.desc: -"""获取授权配置""" - -authorization_settings_put.desc: -"""更新授权配置""" - -} diff --git a/rel/i18n/zh/emqx_authz_api_sources.hocon b/rel/i18n/zh/emqx_authz_api_sources.hocon deleted file mode 100644 index 8e9bfef9c..000000000 --- a/rel/i18n/zh/emqx_authz_api_sources.hocon +++ /dev/null @@ -1,48 +0,0 @@ -emqx_authz_api_sources { - -authorization_sources_get.desc: -"""列出所有授权数据源""" - -authorization_sources_post.desc: -"""添加授权数据源""" - -authorization_sources_type_delete.desc: -"""删除指定类型的授权数据源""" - -authorization_sources_type_get.desc: -"""获取指定类型的授权数据源""" - -authorization_sources_type_move_post.desc: -"""更新授权数据源的优先执行顺序""" - -authorization_sources_type_put.desc: -"""更新指定类型的授权数据源""" - -authorization_sources_type_status_get.desc: -"""获取指定授权数据源的状态""" - -source.desc: -"""授权数据源""" - -source.label: -"""数据源""" - -source_config.desc: -"""数据源配置""" - -source_config.label: -"""数据源配置""" - -source_type.desc: -"""数据源类型""" - -source_type.label: -"""数据源类型""" - -sources.desc: -"""授权数据源列表""" - -sources.label: -"""数据源列表""" - -} diff --git a/rel/i18n/zh/emqx_authz_schema.hocon b/rel/i18n/zh/emqx_authz_schema.hocon deleted file mode 100644 index f0f973a55..000000000 --- a/rel/i18n/zh/emqx_authz_schema.hocon +++ /dev/null @@ -1,285 +0,0 @@ -emqx_authz_schema { - -deny.desc: -"""授权失败的次数。""" - -deny.label: -"""授权失败次数""" - -redis_sentinel.desc: -"""使用 Redis 授权(哨兵模式)。""" - -redis_sentinel.label: -"""redis_sentinel""" - -rate.desc: -"""命中速率,单位:次/秒。""" - -rate.label: -"""速率""" - -status.desc: -"""资源状态。""" - -status.label: -"""状态""" - -method.desc: -"""HTTP 请求方法""" - -method.label: -"""method""" - -query.desc: -"""访问控制数据查询语句/查询命令。""" - -query.label: -"""查询语句""" - -metrics_total.desc: -"""授权实例被触发的总次数。""" - -metrics_total.label: -"""授权实例被触发的总次数""" - -redis_cluster.desc: -"""使用 Redis 授权(集群模式)。""" - -redis_cluster.label: -"""redis_cluster""" - -mysql.desc: -"""使用 MySOL 数据库授权""" - -mysql.label: -"""mysql""" - -postgresql.desc: -"""使用 PostgreSQL 数据库授权""" - -postgresql.label: -"""postgresql""" - -mongo_rs.desc: -"""使用 MongoDB 授权(副本集模式)""" - -mongo_rs.label: -"""mongo_rs""" - -type.desc: -"""数据后端类型""" - -type.label: -"""type""" - -mongo_sharded.desc: -"""使用 MongoDB 授权(分片集群模式)。""" - -mongo_sharded.label: -"""mongo_sharded""" - -body.desc: -"""HTTP request body。""" - -body.label: -"""Request Body""" - -url.desc: -"""授权 HTTP 服务器地址。""" - -url.label: -"""URL""" - -node.desc: -"""节点名称。""" - -node.label: -"""节点名称。""" - -headers.desc: -"""HTTP Headers 列表""" - -headers.label: -"""请求头""" - -rate_last5m.desc: -"""5分钟内平均命中速率,单位:次/秒。""" - -rate_last5m.label: -"""5分钟内速率""" - -headers_no_content_type.desc: -"""HTTP Headers 列表 (无 content-type) 。""" - -headers_no_content_type.label: -"""请求头(无 content-type)""" - -node_error.desc: -"""节点上产生的错误。""" - -node_error.label: -"""节点产生的错误""" - -builtin_db.desc: -"""使用内部数据库授权(mnesia)。""" - -builtin_db.label: -"""Buitin Database""" - -enable.desc: -"""设为 truefalse 以启用或禁用此访问控制数据源""" - -enable.label: -"""enable""" - -matched.desc: -"""请求命中次数。""" - -matched.label: -"""已命中""" - -node_status.desc: -"""每个节点上资源的状态。""" - -node_status.label: -"""节点资源状态""" - -rate_max.desc: -"""最大命中速率,单位:次/秒。""" - -rate_max.label: -"""最大速率""" - -filter.desc: -"""在查询中定义过滤条件的条件表达式。 -过滤器支持如下占位符:
-- ${username}:将在运行时被替换为客户端连接时使用的用户名
-- ${clientid}:将在运行时被替换为客户端连接时使用的客户端标识符""" - -filter.label: -"""过滤器""" - -path.desc: -"""包含 ACL 规则的文件路径。 -如果在启动 EMQX 节点前预先配置该路径, -那么可以将该文件置于任何 EMQX 可以访问到的位置。 - -如果从 EMQX Dashboard 或 HTTP API 创建或修改了规则集, -那么EMQX将会生成一个新的文件并将它存放在 `data_dir` 下的 `authz` 子目录中, -并从此弃用旧的文件。""" - -path.label: -"""path""" - -redis_single.desc: -"""使用 Redis 授权(单实例)。""" - -redis_single.label: -"""redis_single""" - -failed.desc: -"""请求失败次数。""" - -failed.label: -"""失败""" - -metrics.desc: -"""资源统计指标。""" - -metrics.label: -"""指标""" - -authorization.desc: -"""客户端授权相关配置""" - -authorization.label: -"""授权""" - -collection.desc: -"""`MongoDB` 授权数据集。""" - -collection.label: -"""数据集""" - -mongo_single.desc: -"""使用 MongoDB 授权(单实例)。""" - -mongo_single.label: -"""mongo_single""" - -file.desc: -"""使用静态文件授权""" - -file.label: -"""文件""" - -http_post.desc: -"""使用外部 HTTP 服务器授权(POST 请求)。""" - -http_post.label: -"""http_post""" - -request_timeout.desc: -"""HTTP 请求超时时长。""" - -request_timeout.label: -"""请求超时时间""" - -allow.desc: -"""授权成功的次数。""" - -allow.label: -"""授权成功次数""" - -cmd.desc: -"""访问控制数据查查询命令""" - -cmd.label: -"""查询命令""" - -nomatch.desc: -"""没有匹配到任何授权规则的次数。""" - -nomatch.label: -"""没有匹配到任何授权规则的次数""" - -sources.desc: -"""授权数据源。
-授权(ACL)数据源的列表。 -它被设计为一个数组,而不是一个散列映射, -所以可以作为链式访问控制。
- -当授权一个 'publish' 或 'subscribe' 行为时, -该配置列表中的所有数据源将按顺序进行检查。 -如果在某个客户端未找到时(使用 ClientID 或 Username)。 -将会移动到下一个数据源。直至得到 'allow' 或 'deny' 的结果。
- -如果在任何数据源中都未找到对应的客户端信息。 -配置的默认行为 ('authorization.no_match') 将生效。
- -注意: -数据源使用 'type' 进行标识。 -使用同一类型的数据源多于一次不被允许。""" - -sources.label: -"""数据源""" - -node_metrics.desc: -"""每个节点上资源的统计指标。""" - -node_metrics.label: -"""节点资源指标""" - -success.desc: -"""请求成功次数。""" - -success.label: -"""成功""" - -http_get.desc: -"""使用外部 HTTP 服务器授权(GET 请求)。""" - -http_get.label: -"""http_get""" - -} diff --git a/rel/i18n/zh/emqx_auto_subscribe_api.hocon b/rel/i18n/zh/emqx_auto_subscribe_api.hocon deleted file mode 100644 index 7a42ece2c..000000000 --- a/rel/i18n/zh/emqx_auto_subscribe_api.hocon +++ /dev/null @@ -1,12 +0,0 @@ -emqx_auto_subscribe_api { - -list_auto_subscribe_api.desc: -"""获取自动订阅主题列表""" - -update_auto_subscribe_api.desc: -"""更新自动订阅主题列表""" - -update_auto_subscribe_api_response409.desc: -"""超出自定订阅主题列表长度限制""" - -} diff --git a/rel/i18n/zh/emqx_auto_subscribe_schema.hocon b/rel/i18n/zh/emqx_auto_subscribe_schema.hocon deleted file mode 100644 index f4156fe34..000000000 --- a/rel/i18n/zh/emqx_auto_subscribe_schema.hocon +++ /dev/null @@ -1,48 +0,0 @@ -emqx_auto_subscribe_schema { - -auto_subscribe.desc: -"""设备登录成功之后,通过预设的订阅表示符,为设备自动完成订阅。支持使用占位符。""" - -auto_subscribe.label: -"""自动订阅""" - -nl.desc: -"""缺省值为0, -MQTT v3.1.1:如果设备订阅了自己发布消息的主题,那么将收到自己发布的所有消息。 -MQTT v5:如果设备在订阅时将此选项设置为 1,那么服务端将不会向设备转发自己发布的消息""" - -nl.label: -"""No Local""" - -qos.desc: -"""缺省值为 0,服务质量, -QoS 0:消息最多传递一次,如果当时客户端不可用,则会丢失该消息。 -QoS 1:消息传递至少 1 次。 -QoS 2:消息仅传送一次。""" - -qos.label: -"""服务质量""" - -rap.desc: -"""缺省值为 0,这一选项用来指定服务端向客户端转发消息时是否要保留其中的 RETAIN 标识,注意这一选项不会影响保留消息中的 RETAIN 标识。因此当 Retain As Publish 选项被设置为 0 时,客户端直接依靠消息中的 RETAIN 标识来区分这是一个正常的转发消息还是一个保留消息,而不是去判断消息是否是自己订阅后收到的第一个消息(转发消息甚至可能会先于保留消息被发送,视不同 Broker 的具体实现而定)。""" - -rap.label: -"""Retain As Publish""" - -rh.desc: -"""指定订阅建立时服务端是否向客户端发送保留消息, -可选值 0:只要客户端订阅成功,服务端就发送保留消息。 -可选值 1:客户端订阅成功且该订阅此前不存在,服务端才发送保留消息。毕竟有些时候客户端重新发起订阅可能只是为了改变一下 QoS,并不意味着它想再次接收保留消息。 -可选值 2:即便客户订阅成功,服务端也不会发送保留消息。""" - -rh.label: -"""Retain Handling""" - -topic.desc: -"""订阅标识符,支持使用占位符,例如 client/${clientid}/username/${username}/host/${host}/port/${port} -必填,且不可为空字符串""" - -topic.label: -"""订阅标识符""" - -} diff --git a/rel/i18n/zh/emqx_bridge_api.hocon b/rel/i18n/zh/emqx_bridge_api.hocon deleted file mode 100644 index 06887a711..000000000 --- a/rel/i18n/zh/emqx_bridge_api.hocon +++ /dev/null @@ -1,100 +0,0 @@ -emqx_bridge_api { - -desc_api1.desc: -"""列出所有 Bridge""" - -desc_api1.label: -"""列出所有 Bridge""" - -desc_api2.desc: -"""通过类型和名字创建 Bridge""" - -desc_api2.label: -"""创建 Bridge""" - -desc_api3.desc: -"""通过 ID 获取 Bridge""" - -desc_api3.label: -"""获取 Bridge""" - -desc_api4.desc: -"""通过 ID 更新 Bridge""" - -desc_api4.label: -"""更新 Bridge""" - -desc_api5.desc: -"""通过 ID 删除 Bridge""" - -desc_api5.label: -"""删除 Bridge""" - -desc_api6.desc: -"""通过 ID 重置 Bridge 的计数""" - -desc_api6.label: -"""重置 Bridge 计数""" - -desc_api7.desc: -"""停止或启用所有节点上的桥接""" - -desc_api7.label: -"""集群 Bridge 操作""" - -desc_api8.desc: -"""在某个节点上停止/重新启动 Bridge。""" - -desc_api8.label: -"""单节点 Bridge 操作""" - -desc_api9.desc: -"""通过给定的 ID 测试创建一个新的桥接。
-ID 的格式必须为 ’{type}:{name}”""" - -desc_api9.label: -"""测试桥接创建""" - -desc_bridge_metrics.desc: -"""通过 Id 来获取桥接的指标信息""" - -desc_bridge_metrics.label: -"""获取桥接的指标""" - -desc_enable_bridge.desc: -"""启用或禁用所有节点上的桥接""" - -desc_enable_bridge.label: -"""是否启用集群内的桥接""" - -desc_param_path_enable.desc: -"""是否启用桥接""" - -desc_param_path_enable.label: -"""启用桥接""" - -desc_param_path_id.desc: -"""Bridge ID , 格式为 {type}:{name}""" - -desc_param_path_id.label: -"""Bridge ID""" - -desc_param_path_node.desc: -"""节点名,比如 emqx@127.0.0.1""" - -desc_param_path_node.label: -"""节点名""" - -desc_param_path_operation_cluster.desc: -"""集群可用操作:停止、重新启动""" - -desc_param_path_operation_cluster.label: -"""集群可用操作""" - -desc_param_path_operation_on_node.desc: -"""节点可用操作:停止、重新启动""" - -desc_param_path_operation_on_node.label: -"""节点可用操作""" - -} diff --git a/rel/i18n/zh/emqx_bridge_clickhouse.hocon b/rel/i18n/zh/emqx_bridge_clickhouse.hocon deleted file mode 100644 index ff291f667..000000000 --- a/rel/i18n/zh/emqx_bridge_clickhouse.hocon +++ /dev/null @@ -1,46 +0,0 @@ -emqx_bridge_clickhouse { - -batch_value_separator.desc: -"""默认为逗号 ',',适用于 VALUE 格式。您也可以使用其他分隔符, 请参考 [INSERT INTO 语句](https://clickhouse.com/docs/en/sql-reference/statements/insert-into)。""" - -batch_value_separator.label: -"""分隔符""" - -config_enable.desc: -"""启用/禁用桥接""" - -config_enable.label: -"""启用/禁用桥接""" - -desc_config.desc: -"""Clickhouse 桥接配置""" - -desc_config.label: -"""Clickhouse 桥接配置""" - -desc_name.desc: -"""桥接名字""" - -desc_name.label: -"""桥接名字""" - -desc_type.desc: -"""Bridge 类型""" - -desc_type.label: -"""桥接类型""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 Clickhouse。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发。""" - -local_topic.label: -"""本地 Topic""" - -sql_template.desc: -"""可以使用 ${field} 占位符来引用消息与客户端上下文中的变量,请确保对应字段存在且数据格式符合预期。此处不支持 [SQL 预处理](https://docs.emqx.com/zh/enterprise/v5.0/data-integration/data-bridges.html#sql-预处理)。""" - -sql_template.label: -"""SQL 模板""" - -} diff --git a/rel/i18n/zh/emqx_bridge_clickhouse_connector.hocon b/rel/i18n/zh/emqx_bridge_clickhouse_connector.hocon deleted file mode 100644 index b6fd7f3b7..000000000 --- a/rel/i18n/zh/emqx_bridge_clickhouse_connector.hocon +++ /dev/null @@ -1,15 +0,0 @@ -emqx_bridge_clickhouse_connector { - -base_url.desc: -"""你想连接到的Clickhouse服务器的HTTP URL(例如http://myhostname:8123)。""" - -base_url.label: -"""服务器 URL""" - -connect_timeout.desc: -"""连接HTTP服务器的超时时间。""" - -connect_timeout.label: -"""连接超时""" - -} diff --git a/rel/i18n/zh/emqx_bridge_dynamo.hocon b/rel/i18n/zh/emqx_bridge_dynamo.hocon deleted file mode 100644 index 6bf090c5d..000000000 --- a/rel/i18n/zh/emqx_bridge_dynamo.hocon +++ /dev/null @@ -1,40 +0,0 @@ -emqx_bridge_dynamo { - -config_enable.desc: -"""启用/禁用桥接""" - -config_enable.label: -"""启用/禁用桥接""" - -desc_config.desc: -"""DynamoDB 桥接配置""" - -desc_config.label: -"""DynamoDB 桥接配置""" - -desc_name.desc: -"""桥接名字""" - -desc_name.label: -"""桥接名字""" - -desc_type.desc: -"""Bridge 类型""" - -desc_type.label: -"""桥接类型""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 DynamoDB。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发。""" - -local_topic.label: -"""本地 Topic""" - -template.desc: -"""模板, 默认为空,为空时将会将整个消息存入数据库""" - -template.label: -"""模板""" - -} diff --git a/rel/i18n/zh/emqx_bridge_dynamo_connector.hocon b/rel/i18n/zh/emqx_bridge_dynamo_connector.hocon deleted file mode 100644 index ef7ee3462..000000000 --- a/rel/i18n/zh/emqx_bridge_dynamo_connector.hocon +++ /dev/null @@ -1,27 +0,0 @@ -emqx_bridge_dynamo_connector { - -aws_access_key_id.desc: -"""DynamoDB 的访问 ID。""" - -aws_access_key_id.label: -"""连接访问 ID""" - -aws_secret_access_key.desc: -"""DynamoDB 的访问密钥。""" - -aws_secret_access_key.label: -"""连接访问密钥""" - -table.desc: -"""DynamoDB 的表。""" - -table.label: -"""表""" - -url.desc: -"""DynamoDB 的地址。""" - -url.label: -"""DynamoDB 地址""" - -} diff --git a/rel/i18n/zh/emqx_bridge_gcp_pubsub.hocon b/rel/i18n/zh/emqx_bridge_gcp_pubsub.hocon deleted file mode 100644 index 19bd7058e..000000000 --- a/rel/i18n/zh/emqx_bridge_gcp_pubsub.hocon +++ /dev/null @@ -1,77 +0,0 @@ -emqx_bridge_gcp_pubsub { - -connect_timeout.desc: -"""连接 HTTP 服务器的超时时间。""" - -connect_timeout.label: -"""连接超时""" - -desc_config.desc: -"""GCP PubSub 桥接配置""" - -desc_config.label: -"""GCP PubSub 桥接配置""" - -desc_name.desc: -"""桥接名字,可读描述""" - -desc_name.label: -"""桥接名字""" - -desc_type.desc: -"""桥接类型""" - -desc_type.label: -"""桥接类型""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 GCP PubSub。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发到 GCP PubSub。""" - -local_topic.label: -"""本地 Topic""" - -max_retries.desc: -"""请求出错时的最大重试次数。""" - -max_retries.label: -"""最大重试次数""" - -payload_template.desc: -"""用于格式化外发信息的模板。 如果未定义,将以JSON格式发送所有可用的上下文。""" - -payload_template.label: -"""HTTP 请求消息体模板""" - -pipelining.desc: -"""正整数,设置最大可发送的异步 HTTP 请求数量。当设置为 1 时,表示每次发送完成 HTTP 请求后都需要等待服务器返回,再继续发送下一个请求。""" - -pipelining.label: -"""HTTP 流水线""" - -pool_size.desc: -"""连接池大小。""" - -pool_size.label: -"""连接池大小""" - -pubsub_topic.desc: -"""要发布消息的GCP PubSub主题。""" - -pubsub_topic.label: -"""GCP PubSub 主题""" - -request_timeout.desc: -"""废弃的。在缓冲区设置中配置请求超时。""" - -request_timeout.label: -"""HTTP 请求超时""" - -service_account_json.desc: -"""包含将与 PubSub 一起使用的 GCP 服务账户凭证的 JSON。 -当创建GCP服务账户时(如https://developers.google.com/identity/protocols/oauth2/service-account#creatinganaccount),可以选择下载 JSON 形式的凭证,然后在该配置项中使用。""" - -service_account_json.label: -"""GCP 服务账户凭证""" - -} diff --git a/rel/i18n/zh/emqx_bridge_influxdb.hocon b/rel/i18n/zh/emqx_bridge_influxdb.hocon deleted file mode 100644 index 350c68e39..000000000 --- a/rel/i18n/zh/emqx_bridge_influxdb.hocon +++ /dev/null @@ -1,47 +0,0 @@ -emqx_bridge_influxdb { - -config_enable.desc: -"""启用/禁用桥接。""" - -config_enable.label: -"""启用/禁用桥接""" - -desc_config.desc: -"""InfluxDB 桥接配置。""" - -desc_config.label: -"""InfluxDB 桥接配置""" - -desc_name.desc: -"""桥接名称。""" - -desc_name.label: -"""桥接名称""" - -desc_type.desc: -"""桥接类型。""" - -desc_type.label: -"""桥接类型""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 InfluxDB。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发到 InfluxDB。""" - -local_topic.label: -"""本地 Topic""" - -write_syntax.desc: -"""使用 InfluxDB API Line Protocol 写入 InfluxDB 的数据,支持占位符
-参考 [InfluxDB 2.3 Line Protocol](https://docs.influxdata.com/influxdb/v2.3/reference/syntax/line-protocol/) 及 -[InfluxDB 1.8 Line Protocol](https://docs.influxdata.com/influxdb/v1.8/write_protocols/line_protocol_tutorial/)
-TLDR:
-``` -[,=[,=]] =[,=] [] -``` -注意,整形数值占位符后需要添加一个字符 `i` 类型标识。例如 `${payload.int_value}i`""" - -write_syntax.label: -"""写语句""" - -} diff --git a/rel/i18n/zh/emqx_bridge_influxdb_connector.hocon b/rel/i18n/zh/emqx_bridge_influxdb_connector.hocon deleted file mode 100644 index 8477379a7..000000000 --- a/rel/i18n/zh/emqx_bridge_influxdb_connector.hocon +++ /dev/null @@ -1,71 +0,0 @@ -emqx_bridge_influxdb_connector { - -bucket.desc: -"""InfluxDB bucket 名称。""" - -bucket.label: -"""Bucket""" - -database.desc: -"""InfluxDB 数据库。""" - -database.label: -"""数据库""" - -influxdb_api_v1.desc: -"""InfluxDB HTTP API 协议。支持 Influxdb v1.8 以及之前的版本。""" - -influxdb_api_v1.label: -"""HTTP API 协议""" - -influxdb_api_v2.desc: -"""InfluxDB HTTP API V2 协议。支持 Influxdb v2.0 以及之后的版本。""" - -influxdb_api_v2.label: -"""HTTP API V2 协议""" - -org.desc: -"""InfluxDB 组织名称。""" - -org.label: -"""组织""" - -password.desc: -"""InfluxDB 密码。""" - -password.label: -"""密码""" - -precision.desc: -"""InfluxDB 时间精度。""" - -precision.label: -"""时间精度""" - -protocol.desc: -"""InfluxDB 协议。HTTP API 或 HTTP API V2。""" - -protocol.label: -"""协议""" - -server.desc: -"""将要连接的 IPv4 或 IPv6 地址,或者主机名。
-主机名具有以下形式:`Host[:Port]`。
-如果未指定 `[:Port]`,则使用 InfluxDB 默认端口 8086。""" - -server.label: -"""服务器地址""" - -token.desc: -"""InfluxDB token。""" - -token.label: -"""Token""" - -username.desc: -"""InfluxDB 用户名。""" - -username.label: -"""用户名""" - -} diff --git a/rel/i18n/zh/emqx_bridge_kafka.hocon b/rel/i18n/zh/emqx_bridge_kafka.hocon deleted file mode 100644 index 31bae51d3..000000000 --- a/rel/i18n/zh/emqx_bridge_kafka.hocon +++ /dev/null @@ -1,354 +0,0 @@ -emqx_bridge_kafka { - -connect_timeout.desc: -"""建立 TCP 连接时的最大等待时长(若启用认证,这个等待时长也包含完成认证所需时间)。""" - -connect_timeout.label: -"""连接超时""" - -producer_opts.desc: -"""本地 MQTT 数据源和 Kafka 桥接的配置。""" - -producer_opts.label: -"""MQTT 到 Kafka""" - -min_metadata_refresh_interval.desc: -"""刷新 Kafka broker 和 Kafka 主题元数据段最短时间间隔。设置太小可能会增加 Kafka 压力。""" - -min_metadata_refresh_interval.label: -"""元数据刷新最小间隔""" - -kafka_producer.desc: -"""Kafka Producer 配置。""" - -kafka_producer.label: -"""Kafka Producer""" - -producer_buffer.desc: -"""配置消息缓存的相关参数。 - -当 EMQX 需要发送的消息超过 Kafka 处理能力,或者当 Kafka 临时下线时,EMQX 内部会将消息缓存起来。""" - -producer_buffer.label: -"""消息缓存""" - -socket_send_buffer.desc: -"""TCP socket 的发送缓存调优。默认值是针对高吞吐量的一个推荐值。""" - -socket_send_buffer.label: -"""Socket 发送缓存大小""" - -desc_name.desc: -"""桥接名字,可读描述""" - -desc_name.label: -"""桥接名字""" - -consumer_offset_commit_interval_seconds.desc: -"""指定 Kafka 消费组偏移量提交的时间间隔。""" - -consumer_offset_commit_interval_seconds.label: -"""偏移提交间隔""" - -consumer_max_batch_bytes.desc: -"""设置每次从 Kafka 拉取数据的字节数。如该配置小于 Kafka 消息的大小,可能会影响消费性能。""" - -consumer_max_batch_bytes.label: -"""拉取字节数""" - -socket_receive_buffer.desc: -"""TCP socket 的收包缓存调优。默认值是针对高吞吐量的一个推荐值。""" - -socket_receive_buffer.label: -"""Socket 收包缓存大小""" - -consumer_topic_mapping.desc: -"""指定 Kafka 主题和 MQTT 主题之间的映射关系。 应至少包含一项。""" - -consumer_topic_mapping.label: -"""主题映射关系""" - -producer_kafka_opts.desc: -"""Kafka 生产者参数。""" - -producer_kafka_opts.label: -"""生产者参数""" - -kafka_topic.desc: -"""Kafka 主题名称""" - -kafka_topic.label: -"""Kafka 主题名称""" - -consumer_kafka_topic.desc: -"""指定从哪个 Kafka 主题消费消息。""" - -consumer_kafka_topic.label: -"""Kafka 主题""" - -auth_username_password.desc: -"""基于用户名密码的认证。""" - -auth_username_password.label: -"""用户名密码认证""" - -auth_sasl_password.desc: -"""SASL 认证的密码。""" - -auth_sasl_password.label: -"""密码""" - -kafka_message_timestamp.desc: -"""生成 Kafka 消息时间戳的模版。该时间必需是一个整型数值(可以是字符串格式)例如 1661326462115'1661326462115'。当所需的输入字段不存在,或不是一个整型时,则会使用当前系统时间。""" - -kafka_message_timestamp.label: -"""消息的时间戳""" - -buffer_mode.desc: -"""消息缓存模式。 -memory: 所有的消息都缓存在内存里。如果 EMQX 服务重启,缓存的消息会丢失。 -disk: 缓存到磁盘上。EMQX 重启后会继续发送重启前未发送完成的消息。 -hybrid: 先将消息缓存在内存中,当内存中的消息堆积超过一定限制(配置项 segment_bytes 描述了该限制)后,后续的消息会缓存到磁盘上。与 memory 模式一样,如果 EMQX 服务重启,缓存的消息会丢失。""" - -buffer_mode.label: -"""缓存模式""" - -consumer_mqtt_qos.desc: -"""转发 MQTT 消息时使用的 QoS。""" - -consumer_mqtt_qos.label: -"""QoS""" - -consumer_key_encoding_mode.desc: -"""通过 MQTT 转发之前,如何处理 Kafka 消息的 Key。none 使用 Kafka 消息中的 Key 原始值,不进行编码。 注意:在这种情况下,Key 必须是一个有效的 UTF-8 字符串。 -base64 对收到的密钥或值使用 base-64 编码。""" - -consumer_key_encoding_mode.label: -"""Key 编码模式""" - -auth_gssapi_kerberos.desc: -"""使用 GSSAPI/Kerberos 认证。""" - -auth_gssapi_kerberos.label: -"""GSSAPI/Kerberos""" - -consumer_mqtt_opts.desc: -"""本地 MQTT 消息转发。""" - -consumer_mqtt_opts.label: -"""MQTT 转发""" - -auth_kerberos_principal.desc: -"""SASL GSSAPI 认证方法的 Kerberos principal,例如 client_name@MY.KERBEROS.REALM.MYDOMAIN.COM注意:这里使用的 realm 需要配置在 EMQX 服务器的 /etc/krb5.conf 中""" - -auth_kerberos_principal.label: -"""Kerberos Principal""" - -socket_opts.desc: -"""更多 Socket 参数设置。""" - -socket_opts.label: -"""Socket 参数""" - -consumer_mqtt_topic.desc: -"""设置 Kafka 消息向哪个本地 MQTT 主题转发消息。""" - -consumer_mqtt_topic.label: -"""MQTT主题""" - -consumer_offset_reset_policy.desc: -"""如不存在偏移量历史记录或历史记录失效,消费者应使用哪个偏移量开始消费。""" - -consumer_offset_reset_policy.label: -"""偏移重置策略""" - -partition_count_refresh_interval.desc: -"""配置 Kafka 刷新分区数量的时间间隔。 -EMQX 发现 Kafka 分区数量增加后,会开始按 partition_strategy 配置,把消息发送到新的分区中。""" - -partition_count_refresh_interval.label: -"""分区数量刷新间隔""" - -max_batch_bytes.desc: -"""最大消息批量字节数。大多数 Kafka 环境的默认最低值是 1 MB,EMQX 的默认值比 1 MB 更小是因为需要补偿 Kafka 消息编码所需要的额外字节(尤其是当每条消息都很小的情况下)。当单个消息的大小超过该限制时,它仍然会被发送,(相当于该批量中只有单个消息)。""" - -max_batch_bytes.label: -"""最大批量字节数""" - -required_acks.desc: -"""设置 Kafka leader 在返回给 EMQX 确认之前需要等待多少个 follower 的确认。 - -all_isr: 需要所有的在线复制者都确认。 -leader_only: 仅需要分区 leader 确认。 -none: 无需 Kafka 回复任何确认。""" - -required_acks.label: -"""Kafka 确认数量""" - -metadata_request_timeout.desc: -"""刷新元数据时最大等待时长。""" - -metadata_request_timeout.label: -"""元数据请求超时""" - -desc_type.desc: -"""桥接类型""" - -desc_type.label: -"""桥接类型""" - -socket_nodelay.desc: -"""设置‘true’让系统内核立即发送。否则当需要发送的内容很少时,可能会有一定延迟(默认 40 毫秒)。""" - -socket_nodelay.label: -"""是否关闭延迟发送""" - -authentication.desc: -"""认证参数。""" - -authentication.label: -"""认证""" - -buffer_memory_overload_protection.desc: -"""缓存模式是 memoryhybrid 时适用。当系统处于高内存压力时,从队列中丢弃旧的消息以减缓内存增长。内存压力值由配置项 sysmon.os.sysmem_high_watermark 决定。注意,该配置仅在 Linux 系统中有效。""" - -buffer_memory_overload_protection.label: -"""内存过载保护""" - -auth_sasl_mechanism.desc: -"""SASL 认证方法名称。""" - -auth_sasl_mechanism.label: -"""认证方法""" - -config_enable.desc: -"""启用(true)或停用该(false)Kafka 数据桥接。""" - -config_enable.label: -"""启用或停用""" - -consumer_mqtt_payload.desc: -"""用于转换收到的 Kafka 消息的模板。 默认情况下,它将使用 JSON 格式来序列化来自 Kafka 的所有字段。 这些字段包括:headers:一个包含字符串键值对的 JSON 对象。 -key:Kafka 消息的键(使用选择的编码方式编码)。 -offset:消息的偏移量。 -topic:Kafka 主题。 -ts: 消息的时间戳。 -ts_type:消息的时间戳类型,值可能是: createappendundefined。 -value: Kafka 消息值(使用选择的编码方式编码)。""" - -consumer_mqtt_payload.label: -"""MQTT Payload Template""" - -consumer_opts.desc: -"""本地 MQTT 转发 和 Kafka 消费者配置。""" - -consumer_opts.label: -"""MQTT 到 Kafka""" - -kafka_consumer.desc: -"""Kafka 消费者配置。""" - -kafka_consumer.label: -"""Kafka 消费者""" - -desc_config.desc: -"""Kafka 桥接配置""" - -desc_config.label: -"""Kafka 桥接配置""" - -consumer_value_encoding_mode.desc: -"""通过 MQTT 转发之前,如何处理 Kafka 消息的 Value。none 使用 Kafka 消息中的 Value 原始值,不进行编码。 注意:在这种情况下,Value 必须是一个有效的 UTF-8 字符串。 -base64 对收到的 Value 使用 base-64 编码。""" - -consumer_value_encoding_mode.label: -"""Value 编码模式""" - -buffer_per_partition_limit.desc: -"""为每个 Kafka 分区设置的最大缓存字节数。当超过这个上限之后,老的消息会被丢弃,为新的消息腾出空间。""" - -buffer_per_partition_limit.label: -"""Kafka 分区缓存上限""" - -bootstrap_hosts.desc: -"""用逗号分隔的 host[:port] 主机列表。默认端口号为 9092。""" - -bootstrap_hosts.label: -"""主机列表""" - -consumer_max_rejoin_attempts.desc: -"""消费组成员允许重新加入小组的最大次数。如超过该配置次数后仍未能成功加入消费组,则会在等待一段时间后重试。""" - -consumer_max_rejoin_attempts.label: -"""最大的重新加入尝试""" - -kafka_message_key.desc: -"""生成 Kafka 消息 Key 的模版。如果模版生成后为空值,则会使用 Kafka 的 NULL ,而非空字符串。""" - -kafka_message_key.label: -"""消息的 Key""" - -kafka_message.desc: -"""用于生成 Kafka 消息的模版。""" - -kafka_message.label: -"""Kafka 消息模版""" - -mqtt_topic.desc: -"""MQTT 主题数据源由桥接指定,或留空由规则动作指定。""" - -mqtt_topic.label: -"""源 MQTT 主题""" - -kafka_message_value.desc: -"""生成 Kafka 消息 Value 的模版。如果模版生成后为空值,则会使用 Kafka 的 NULL,而非空字符串。""" - -kafka_message_value.label: -"""消息的 Value""" - -partition_strategy.desc: -"""设置消息发布时应该如何选择 Kafka 分区。 - -random: 为每个消息随机选择一个分区。 -key_dispatch: Hash Kafka message key to a partition number""" - -partition_strategy.label: -"""分区选择策略""" - -buffer_segment_bytes.desc: -"""当缓存模式是 diskhybrid 时适用。该配置用于指定缓存到磁盘上的文件的大小。""" - -buffer_segment_bytes.label: -"""缓存文件大小""" - -consumer_kafka_opts.desc: -"""Kafka消费者配置。""" - -consumer_kafka_opts.label: -"""Kafka 消费者""" - -max_inflight.desc: -"""设置 Kafka 生产者(每个分区一个)在收到 Kafka 的确认前最多发送多少个请求(批量)。调大这个值通常可以增加吞吐量,但是,当该值设置大于 1 时存在消息乱序的风险。""" - -max_inflight.label: -"""飞行窗口""" - -auth_sasl_username.desc: -"""SASL 认证的用户名。""" - -auth_sasl_username.label: -"""用户名""" - -auth_kerberos_keytab_file.desc: -"""SASL GSSAPI 认证方法的 Kerberos keytab 文件。注意:该文件需要上传到 EMQX 服务器中,且运行 EMQX 服务的系统账户需要有读取权限。""" - -auth_kerberos_keytab_file.label: -"""Kerberos keytab 文件""" - -compression.desc: -"""压缩方法。""" - -compression.label: -"""压缩""" - -} diff --git a/rel/i18n/zh/emqx_bridge_mqtt_schema.hocon b/rel/i18n/zh/emqx_bridge_mqtt_schema.hocon deleted file mode 100644 index 669d50398..000000000 --- a/rel/i18n/zh/emqx_bridge_mqtt_schema.hocon +++ /dev/null @@ -1,21 +0,0 @@ -emqx_bridge_mqtt_schema { - -config.desc: -"""MQTT Bridge 的配置。""" - -config.label: -"""配置""" - -desc_name.desc: -"""Bridge 名字,Bridge 的可读描述""" - -desc_name.label: -"""Bridge 名字""" - -desc_type.desc: -"""Bridge 的类型""" - -desc_type.label: -"""Bridge 类型""" - -} diff --git a/rel/i18n/zh/emqx_bridge_opents.hocon b/rel/i18n/zh/emqx_bridge_opents.hocon deleted file mode 100644 index 137e687df..000000000 --- a/rel/i18n/zh/emqx_bridge_opents.hocon +++ /dev/null @@ -1,26 +0,0 @@ -emqx_bridge_opents { - - config_enable.desc: - """启用/禁用桥接""" - - config_enable.label: - "启用/禁用桥接" - - desc_config.desc: - """OpenTSDB 桥接配置""" - - desc_config.label: - "OpenTSDB 桥接配置" - - desc_type.desc: - """Bridge 类型""" - - desc_type.label: - "桥接类型" - - desc_name.desc: - """桥接名字""" - - desc_name.label: - "桥接名字" -} diff --git a/rel/i18n/zh/emqx_bridge_opents_connector.hocon b/rel/i18n/zh/emqx_bridge_opents_connector.hocon deleted file mode 100644 index f8a80b10e..000000000 --- a/rel/i18n/zh/emqx_bridge_opents_connector.hocon +++ /dev/null @@ -1,20 +0,0 @@ -emqx_bridge_opents_connector { - - server.desc: - """服务器的地址。""" - - server.label: - "服务器地址" - - summary.desc: - """是否返回摘要信息。""" - - summary.label: - "摘要信息" - - details.desc: - """是否返回详细信息。""" - - details.label: - "详细信息" -} diff --git a/rel/i18n/zh/emqx_bridge_oracle.hocon b/rel/i18n/zh/emqx_bridge_oracle.hocon deleted file mode 100644 index 5f69c6171..000000000 --- a/rel/i18n/zh/emqx_bridge_oracle.hocon +++ /dev/null @@ -1,56 +0,0 @@ -emqx_bridge_oracle { - - local_topic { - desc = "发送到 'local_topic' 的消息都会转发到 Oracle Database。
" - "注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发。" - label = "本地 Topic" - } - - sql_template { - desc = "SQL模板。模板字符串可以包含消息元数据和有效载荷字段的占位符。占位符" - "的插入不需要任何检查和特殊格式化,因此必须确保插入的数值格式化和转义正确。模板字符串可以包含占位符" - "模板字符串可以包含消息元数据和有效载荷字段的占位符。这些占位符被插入" - "所以必须确保插入的值的格式正确。因此,确保插入的值格式化和转义正确是非常重要的。模板字符串可以包含占位符" - "模板字符串可以包含消息元数据和有效载荷字段的占位符。这些占位符被插入" - "所以必须确保插入的值的格式正确。确保插入的值被正确地格式化和转义。" - label = "SQL 模板" - } - - server { - desc = "将要连接的 IPv4 或 IPv6 地址,或者主机名。
" - "主机名具有以下形式:`Host[:Port]`。
" - "如果未指定 `[:Port]`,则使用 Oracle Database 默认端口 1521。" - label = "服务器地址" - } - - sid { - desc = "Oracle Database Sid 名称" - label = "Oracle Database Sid" - } - - service_name { - desc = "Oracle Database 服务名称。" - label = "Oracle Database 服务名称" - } - - config_enable { - desc = "启用/禁用桥接" - label = "启用/禁用桥接" - } - - desc_config { - desc = "Oracle Database 桥接配置" - label = "Oracle Database 桥接配置" - } - - desc_type { - desc = "Bridge 类型" - label = "桥接类型" - } - - desc_name { - desc = "桥接名字" - label = "桥接名字" - } - -} diff --git a/rel/i18n/zh/emqx_bridge_pgsql.hocon b/rel/i18n/zh/emqx_bridge_pgsql.hocon deleted file mode 100644 index 2f233d833..000000000 --- a/rel/i18n/zh/emqx_bridge_pgsql.hocon +++ /dev/null @@ -1,40 +0,0 @@ -emqx_bridge_pgsql { - -config_enable.desc: -"""启用/禁用桥接""" - -config_enable.label: -"""启用/禁用桥接""" - -desc_config.desc: -"""PostgreSQL 桥接配置""" - -desc_config.label: -"""PostgreSQL 桥接配置""" - -desc_name.desc: -"""桥接名字""" - -desc_name.label: -"""桥接名字""" - -desc_type.desc: -"""Bridge 类型""" - -desc_type.label: -"""桥接类型""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 PostgreSQL。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发。""" - -local_topic.label: -"""本地 Topic""" - -sql_template.desc: -"""SQL 模板""" - -sql_template.label: -"""SQL 模板""" - -} diff --git a/rel/i18n/zh/emqx_bridge_pulsar.hocon b/rel/i18n/zh/emqx_bridge_pulsar.hocon deleted file mode 100644 index 4e2fd5c9f..000000000 --- a/rel/i18n/zh/emqx_bridge_pulsar.hocon +++ /dev/null @@ -1,178 +0,0 @@ -emqx_bridge_pulsar { - - pulsar_producer_struct { - desc = "Pulsar 桥接配置" - label = "Pulsar 桥接配置" - } - - desc_type { - desc = "桥接类型" - label = "桥接类型" - } - - desc_name { - desc = "桥接名字,可读描述" - label = "桥接名字" - } - - config_enable { - desc = "启用(true)或停用(false)该 Pulsar 数据桥接。" - label = "启用或停用" - } - - connect_timeout { - desc = "建立 TCP 连接时的最大等待时长(若启用认证,这个等待时长也包含完成认证所需时间)。" - label = "连接超时时间" - } - - servers { - desc = "以逗号分隔的 scheme://host[:port] 格式的 Pulsar URL 列表," - "支持的 scheme 有 pulsar:// (默认)" - "和pulsar+ssl://。默认的端口是6650。" - label = "服务员" - } - - authentication { - desc = "认证参数。" - label = "认证" - } - - producer_batch_size { - desc = "在一个Pulsar消息中批处理的单个请求的最大数量。" - label = "批量大小" - } - - producer_compression { - desc = "压缩方法。" - label = "压缩" - } - - producer_send_buffer { - desc = "TCP socket 的发送缓存调优。默认值是针对高吞吐量的一个推荐值。" - label = "Socket 发送缓存大小" - } - - producer_sync_timeout { - desc = "同步发布时,从Pulsar接收发送回执的最长等待时间。" - label = "同步发布超时" - } - - auth_basic_username { - desc = "基本认证用户名。" - label = "用户名" - } - - auth_basic_password { - desc = "基本认证密码。" - label = "密码" - } - - auth_token_jwt { - desc = "JWT认证令牌。" - label = "JWT" - } - - producer_max_batch_bytes { - desc = "最大消息批量字节数。" - "大多数 Pulsar 环境的默认最低值是 5 MB,EMQX 的默认值比 5 MB 更小是因为需要" - "补偿 Pulsar 消息编码所需要的额外字节(尤其是当每条消息都很小的情况下)。" - "当单个消息的大小超过该限制时,它仍然会被发送,(相当于该批量中只有单个消息)。" - label = "最大批量字节数" - } - - producer_retention_period { - desc = "当没有连接到Pulsar代理时,信息将被缓冲的时间。 较长的时间意味着将使用更多的内存/磁盘" - label = "保留期" - } - - producer_local_topic { - desc = "MQTT 主题数据源由桥接指定,或留空由规则动作指定。" - label = "源 MQTT 主题" - } - - producer_pulsar_topic { - desc = "Pulsar 主题名称" - label = "Pulsar 主题名称" - } - - producer_strategy { - desc = "设置消息发布时应该如何选择 Pulsar 分区。\n\n" - "random: 为每个消息随机选择一个分区。\n" - "roundrobin: 依次为每条信息挑选可用的生产商。\n" - "key_dispatch: 将一批信息中的第一条信息的Pulsar信息密钥哈希到一个分区编号。" - label = "分区选择策略" - } - - producer_buffer { - desc = "配置消息缓存的相关参数。\n\n" - "当 EMQX 需要发送的消息超过 Pulsar 处理能力,或者当 Pulsar 临时下线时,EMQX 内部会将消息缓存起来。" - label = "消息缓存" - } - - buffer_mode { - desc = "消息缓存模式。\n" - "memory: 所有的消息都缓存在内存里。如果 EMQX 服务重启,缓存的消息会丢失。\n" - "disk: 缓存到磁盘上。EMQX 重启后会继续发送重启前未发送完成的消息。\n" - "hybrid: 先将消息缓存在内存中,当内存中的消息堆积超过一定限制" - "(配置项 segment_bytes 描述了该限制)后,后续的消息会缓存到磁盘上。" - "与 memory 模式一样,如果 EMQX 服务重启,缓存的消息会丢失。" - label = "缓存模式" - } - - buffer_per_partition_limit { - desc = "为每个 Pulsar 分区设置的最大缓存字节数。当超过这个上限之后,老的消息会被丢弃," - "为新的消息腾出空间。" - label = "Pulsar 分区缓存上限" - } - - buffer_segment_bytes { - desc = "当缓存模式是 diskhybrid 时适用。" - "该配置用于指定缓存到磁盘上的文件的大小。" - label = "缓存文件大小" - } - - buffer_memory_overload_protection { - desc = "缓存模式是 memoryhybrid 时适用。" - "当系统处于高内存压力时,从队列中丢弃旧的消息以减缓内存增长。" - "内存压力值由配置项 sysmon.os.sysmem_high_watermark 决定。" - "注意,该配置仅在 Linux 系统中有效。" - label = "内存过载保护" - } - - producer_message_opts { - desc = "用于生成 Pulsar 消息的模版。" - label = "Pulsar 消息模版" - } - - producer_key_template { - desc = "生成 Pulsar 消息 Key 的模版。" - label = "消息的 Key" - } - - producer_value_template { - desc = "生成 Pulsar 消息 Value 的模版。" - label = "消息的 Value" - } - - auth_basic { - desc = "基本认证的参数。" - label = "基本认证参数" - } - - auth_token { - desc = "令牌认证的参数。" - label = "Token auth params" - } - - producer_buffer { - desc = "配置消息缓存的相关参数。\n\n" - "当 EMQX 需要发送的消息超过 Pulsar 处理能力,或者当 Pulsar 临时下线时,EMQX 内部会将消息缓存起来。" - label = "消息缓存" - } - - producer_pulsar_message { - desc = "用于生成 Pulsar 消息的模版。" - label = "Pulsar 消息模版" - } - -} diff --git a/rel/i18n/zh/emqx_bridge_rocketmq.hocon b/rel/i18n/zh/emqx_bridge_rocketmq.hocon deleted file mode 100644 index 75d2588de..000000000 --- a/rel/i18n/zh/emqx_bridge_rocketmq.hocon +++ /dev/null @@ -1,43 +0,0 @@ -emqx_bridge_rocketmq { - -config_enable.desc: -"""启用/禁用桥接""" - -config_enable.label: -"""启用/禁用桥接""" - -desc_config.desc: -"""RocketMQ 桥接配置""" - -desc_config.label: -"""RocketMQ 桥接配置""" - -desc_name.desc: -"""桥接名字""" - -desc_name.label: -"""桥接名字""" - -desc_type.desc: -"""Bridge 类型""" - -desc_type.label: -"""桥接类型""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 RocketMQ。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发。""" - -local_topic.label: -"""本地 Topic""" - -template.desc: -"""模板, 默认为空,为空时将会将整个消息转发给 RocketMQ。
- 模板可以是任意带有占位符的合法字符串, 例如:
- - ${id}, ${username}, ${clientid}, ${timestamp}
- - {"id" : ${id}, "username" : ${username}}""" - -template.label: -"""模板""" - -} diff --git a/rel/i18n/zh/emqx_bridge_rocketmq_connector.hocon b/rel/i18n/zh/emqx_bridge_rocketmq_connector.hocon deleted file mode 100644 index abc7bcdce..000000000 --- a/rel/i18n/zh/emqx_bridge_rocketmq_connector.hocon +++ /dev/null @@ -1,53 +0,0 @@ -emqx_bridge_rocket_connector { - -access_key.desc: -"""RocketMQ 服务器的 `accessKey`。""" - -access_key.label: -"""AccessKey""" - -refresh_interval.desc: -"""RocketMQ 主题路由更新间隔。""" - -refresh_interval.label: -"""主题路由更新间隔""" - -secret_key.desc: -"""RocketMQ 服务器的 `secretKey`。""" - -secret_key.label: -"""SecretKey""" - -security_token.desc: -"""RocketMQ 服务器安全令牌""" - -security_token.label: -"""安全令牌""" - -send_buffer.desc: -"""RocketMQ 驱动的套字节发送消息的缓冲区大小""" - -send_buffer.label: -"""发送消息的缓冲区大小""" - -servers.desc: -"""将要连接的 IPv4 或 IPv6 地址,或者主机名。
-主机名具有以下形式:`Host[:Port]`。
-如果未指定 `[:Port]`,则使用 RocketMQ 默认端口 9876。""" - -servers.label: -"""服务器地址""" - -sync_timeout.desc: -"""RocketMQ 驱动同步调用的超时时间。""" - -sync_timeout.label: -"""同步调用超时时间""" - -topic.desc: -"""RocketMQ 主题""" - -topic.label: -"""RocketMQ 主题""" - -} diff --git a/rel/i18n/zh/emqx_bridge_schema.hocon b/rel/i18n/zh/emqx_bridge_schema.hocon deleted file mode 100644 index 7512efa67..000000000 --- a/rel/i18n/zh/emqx_bridge_schema.hocon +++ /dev/null @@ -1,158 +0,0 @@ -emqx_bridge_schema { - -bridges_mqtt.desc: -"""桥接到另一个 MQTT Broker 的 MQTT Bridge""" - -bridges_mqtt.label: -"""MQTT Bridge""" - -bridges_webhook.desc: -"""转发消息到 HTTP 服务器的 WebHook""" - -bridges_webhook.label: -"""WebHook""" - -desc_bridges.desc: -"""MQTT Bridge 配置""" - -desc_bridges.label: -"""MQTT Bridge 配置""" - -desc_enable.desc: -"""启用/禁用 Bridge""" - -desc_enable.label: -"""启用/禁用 Bridge""" - -desc_metrics.desc: -"""Bridge 计数""" - -desc_metrics.label: -"""Bridge 计数""" - -desc_node_metrics.desc: -"""节点的计数器""" - -desc_node_metrics.label: -"""节点的计数器""" - -desc_node_name.desc: -"""节点的名字""" - -desc_node_name.label: -"""节点名字""" - -desc_node_status.desc: -"""节点的状态""" - -desc_node_status.label: -"""节点的状态""" - -desc_status.desc: -"""Bridge 的连接状态
-- connecting: 启动时的初始状态。
-- connected: 桥接驱动健康检查正常。
-- disconnected: 当桥接无法通过健康检查。
-- stopped: 桥接处于停用状态。
-- inconsistent: 集群中有各节点汇报的状态不一致。""" - -desc_status.label: -"""Bridge 状态""" - -desc_status_reason.desc: -"""桥接连接失败的原因。""" - -desc_status_reason.label: -"""失败原因""" - -metric_dropped.desc: -"""被丢弃的消息个数。""" - -metric_dropped.label: -"""丢弃""" - -metric_dropped_other.desc: -"""因为其他原因被丢弃的消息个数。""" - -metric_dropped_other.label: -"""其他丢弃""" - -metric_dropped_queue_full.desc: -"""因为队列已满被丢弃的消息个数。""" - -metric_dropped_queue_full.label: -"""队列已满被丢弃""" - -metric_dropped_resource_not_found.desc: -"""因为资源不存在被丢弃的消息个数。""" - -metric_dropped_resource_not_found.label: -"""资源不存在被丢弃""" - -metric_dropped_resource_stopped.desc: -"""因为资源已停用被丢弃的消息个数。""" - -metric_dropped_resource_stopped.label: -"""资源停用被丢弃""" - -metric_inflight.desc: -"""已异步地发送但没有收到 ACK 的消息个数。""" - -metric_inflight.label: -"""已发送未确认""" - -metric_matched.desc: -"""Bridge 被匹配到(被请求)的次数。""" - -metric_matched.label: -"""匹配次数""" - -metric_queuing.desc: -"""当前被缓存到磁盘队列的消息个数。""" - -metric_queuing.label: -"""被缓存""" - -metric_rate.desc: -"""执行操作的速率,次/秒""" - -metric_rate.label: -"""速率""" - -metric_rate_last5m.desc: -"""5 分钟平均速率,次/秒""" - -metric_rate_last5m.label: -"""5 分钟平均速率""" - -metric_rate_max.desc: -"""执行操作的最大速率,次/秒""" - -metric_rate_max.label: -"""执行操作的最大速率""" - -metric_received.desc: -"""从远程系统收到的消息个数。""" - -metric_received.label: -"""已接收""" - -metric_retried.desc: -"""重试的次数。""" - -metric_retried.label: -"""已重试""" - -metric_sent_failed.desc: -"""发送失败的消息个数。""" - -metric_sent_failed.label: -"""发送失败""" - -metric_sent_success.desc: -"""已经发送成功的消息个数。""" - -metric_sent_success.label: -"""发送成功""" - -} diff --git a/rel/i18n/zh/emqx_bridge_sqlserver.hocon b/rel/i18n/zh/emqx_bridge_sqlserver.hocon deleted file mode 100644 index b4750ac0d..000000000 --- a/rel/i18n/zh/emqx_bridge_sqlserver.hocon +++ /dev/null @@ -1,46 +0,0 @@ -emqx_bridge_sqlserver { - -config_enable.desc: -"""启用/禁用桥接""" - -config_enable.label: -"""启用/禁用桥接""" - -desc_config.desc: -"""Microsoft SQL Server 桥接配置""" - -desc_config.label: -"""Microsoft SQL Server 桥接配置""" - -desc_name.desc: -"""桥接名字""" - -desc_name.label: -"""桥接名字""" - -desc_type.desc: -"""Bridge 类型""" - -desc_type.label: -"""桥接类型""" - -driver.desc: -"""SQL Server Driver 名称""" - -driver.label: -"""SQL Server Driver 名称""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 Microsoft SQL Server。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发。""" - -local_topic.label: -"""本地 Topic""" - -sql_template.desc: -"""SQL 模板""" - -sql_template.label: -"""SQL 模板""" - -} diff --git a/rel/i18n/zh/emqx_bridge_sqlserver_connector.hocon b/rel/i18n/zh/emqx_bridge_sqlserver_connector.hocon deleted file mode 100644 index c326d153c..000000000 --- a/rel/i18n/zh/emqx_bridge_sqlserver_connector.hocon +++ /dev/null @@ -1,11 +0,0 @@ -emqx_bridge_sqlserver_connector { - -server.desc: -"""将要连接的 IPv4 或 IPv6 地址,或者主机名。
-主机名具有以下形式:`Host[:Port]`。
-如果未指定 `[:Port]`,则使用 SQL Server 默认端口 1433。""" - -server.label: -"""服务器地址""" - -} diff --git a/rel/i18n/zh/emqx_bridge_tdengine.hocon b/rel/i18n/zh/emqx_bridge_tdengine.hocon deleted file mode 100644 index 8d0c7a24e..000000000 --- a/rel/i18n/zh/emqx_bridge_tdengine.hocon +++ /dev/null @@ -1,40 +0,0 @@ -emqx_bridge_tdengine { - -config_enable.desc: -"""启用/禁用桥接""" - -config_enable.label: -"""启用/禁用桥接""" - -desc_config.desc: -"""TDengine 桥接配置""" - -desc_config.label: -"""TDengine 桥接配置""" - -desc_name.desc: -"""桥接名字""" - -desc_name.label: -"""桥接名字""" - -desc_type.desc: -"""Bridge 类型""" - -desc_type.label: -"""桥接类型""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 TDengine。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发。""" - -local_topic.label: -"""本地 Topic""" - -sql_template.desc: -"""SQL 模板""" - -sql_template.label: -"""SQL 模板""" - -} diff --git a/rel/i18n/zh/emqx_bridge_tdengine_connector.hocon b/rel/i18n/zh/emqx_bridge_tdengine_connector.hocon deleted file mode 100644 index 6465bff35..000000000 --- a/rel/i18n/zh/emqx_bridge_tdengine_connector.hocon +++ /dev/null @@ -1,11 +0,0 @@ -emqx_bridge_tdengine_connector { - -server.desc: -"""将要连接的 IPv4 或 IPv6 地址,或者主机名。
-主机名具有以下形式:`Host[:Port]`。
-如果未指定 `[:Port]`,则使用 TDengine 默认端口 6041。""" - -server.label: -"""服务器地址""" - -} diff --git a/rel/i18n/zh/emqx_bridge_webhook_schema.hocon b/rel/i18n/zh/emqx_bridge_webhook_schema.hocon deleted file mode 100644 index d7dd9dae0..000000000 --- a/rel/i18n/zh/emqx_bridge_webhook_schema.hocon +++ /dev/null @@ -1,87 +0,0 @@ -emqx_bridge_webhook_schema { - -config_body.desc: -"""HTTP 请求的正文。
-如果没有设置该字段,请求正文将是包含所有可用字段的 JSON object。
-如果该 webhook 是由于收到 MQTT 消息触发的,'所有可用字段' 将是 MQTT 消息的 -上下文信息;如果该 webhook 是由于规则触发的,'所有可用字段' 则为触发事件的上下文信息。
-允许使用带有变量的模板。""" - -config_body.label: -"""HTTP 请求正文""" - -config_direction.desc: -"""已废弃,Bridge 的方向,必须是 egress""" - -config_direction.label: -"""Bridge 方向""" - -config_enable.desc: -"""启用/禁用 Bridge""" - -config_enable.label: -"""启用/禁用 Bridge""" - -config_headers.desc: -"""HTTP 请求的标头。
-允许使用带有变量的模板。""" - -config_headers.label: -"""HTTP 请求标头""" - -config_local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 HTTP 服务器。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发到 HTTP 服务器。""" - -config_local_topic.label: -"""本地 Topic""" - -config_max_retries.desc: -"""HTTP 请求失败最大重试次数""" - -config_max_retries.label: -"""HTTP 请求重试次数""" - -config_method.desc: -"""HTTP 请求的方法。 所有可用的方法包括:post、put、get、delete。
-允许使用带有变量的模板。""" - -config_method.label: -"""HTTP 请求方法""" - -config_request_timeout.desc: -"""HTTP 请求超时""" - -config_request_timeout.label: -"""HTTP 请求超时""" - -config_url.desc: -"""HTTP Bridge 的 URL。
-路径中允许使用带变量的模板,但是 host, port 不允许使用变量模板。
-例如, http://localhost:9901/${topic} 是允许的, -但是 http://${host}:9901/message -或 http://localhost:${port}/message -不允许。""" - -config_url.label: -"""HTTP Bridge""" - -desc_config.desc: -"""HTTP Bridge 配置""" - -desc_config.label: -"""HTTP Bridge 配置""" - -desc_name.desc: -"""Bridge 名字,Bridge 的可读描述""" - -desc_name.label: -"""Bridge 名字""" - -desc_type.desc: -"""Bridge 类型""" - -desc_type.label: -"""Bridge 类型""" - -} diff --git a/rel/i18n/zh/emqx_coap_api.hocon b/rel/i18n/zh/emqx_coap_api.hocon deleted file mode 100644 index f3bee543e..000000000 --- a/rel/i18n/zh/emqx_coap_api.hocon +++ /dev/null @@ -1,27 +0,0 @@ -emqx_coap_api { - -content_type.desc: -"""Payload 类型""" - -message_id.desc: -"""消息 ID""" - -method.desc: -"""请求 Method 类型""" - -payload.desc: -"""Payload 内容""" - -response_code.desc: -"""应答码""" - -send_coap_request.desc: -"""发送 CoAP 消息到指定客户端""" - -timeout.desc: -"""请求超时时间""" - -token.desc: -"""消息 Token, 可以为空""" - -} diff --git a/rel/i18n/zh/emqx_coap_schema.hocon b/rel/i18n/zh/emqx_coap_schema.hocon deleted file mode 100644 index 132c0f03a..000000000 --- a/rel/i18n/zh/emqx_coap_schema.hocon +++ /dev/null @@ -1,37 +0,0 @@ -emqx_coap_schema { - -coap.desc: -"""CoAP 网关配置。 -该网关的实现基于 RFC-7252 和 https://core-wg.github.io/coap-pubsub/draft-ietf-core-pubsub.html""" - -coap_connection_required.desc: -"""是否开启连接模式。 -连接模式是非标准协议的功能。它维护 CoAP 客户端上线、认证、和连接状态的保持""" - -coap_heartbeat.desc: -"""CoAP 网关要求客户端的最小心跳间隔时间。 -当 connection_required 开启后,该参数用于检查客户端连接是否存活""" - -coap_notify_type.desc: -"""投递给 CoAP 客户端的通知消息类型。当客户端 Observe 一个资源(或订阅某个主题)时,网关会向客户端推送新产生的消息。其消息类型可设置为:
- - non: 不需要客户端返回确认消息;
- - con: 需要客户端返回一个确认消息;
- - qos: 取决于消息的 QoS 等级; QoS 0 会以 `non` 类型下发,QoS 1/2 会以 `con` 类型下发""" - -coap_publish_qos.desc: -"""客户端发布请求的默认 QoS 等级。 -当 CoAP 客户端发起发布请求时,如果未携带 `qos` 参数则会使用该默认值。默认值可设置为:
- - qos0、qos1、qos2: 设置为固定的 QoS 等级
- - coap: 依据发布操作的 CoAP 报文类型来动态决定
- * 当发布请求为 `non-confirmable` 类型时,取值为 qos0
- * 当发布请求为 `confirmable` 类型时,取值为 qos1""" - -coap_subscribe_qos.desc: -"""客户端订阅请求的默认 QoS 等级。 -当 CoAP 客户端发起订阅请求时,如果未携带 `qos` 参数则会使用该默认值。默认值可设置为:
- - qos0、 qos1、qos2: 设置为固定的 QoS 等级
- - coap: 依据订阅操作的 CoAP 报文类型来动态决定
- * 当订阅请求为 `non-confirmable` 类型时,取值为 qos0
- * 当订阅请求为 `confirmable` 类型时,取值为 qos1""" - -} diff --git a/rel/i18n/zh/emqx_conf_schema.hocon b/rel/i18n/zh/emqx_conf_schema.hocon deleted file mode 100644 index d52ae44cc..000000000 --- a/rel/i18n/zh/emqx_conf_schema.hocon +++ /dev/null @@ -1,775 +0,0 @@ -emqx_conf_schema { - -common_handler_drop_mode_qlen.desc: -"""当缓冲的日志事件数大于此值时,新的日志事件将被丢弃。起到过载保护的功能。 -为了使过载保护算法正常工作必须要: sync_mode_qlen =< drop_mode_qlen =< flush_qlen 且 drop_mode_qlen > 1 -要禁用某些模式,请执行以下操作。 -- 如果sync_mode_qlen被设置为0,所有的日志事件都被同步处理。也就是说,异步日志被禁用。 -- 如果sync_mode_qlen被设置为与drop_mode_qlen相同的值,同步模式被禁用。也就是说,处理程序总是以异步模式运行,除非调用drop或flushing。 -- 如果drop_mode_qlen被设置为与flush_qlen相同的值,则drop模式被禁用,永远不会发生。""" - -common_handler_drop_mode_qlen.label: -"""进入丢弃模式的队列长度""" - -cluster_mcast_addr.desc: -"""指定多播 IPv4 地址。 -当 cluster.discovery_strategy 为 mcast 时,此配置项才有效。""" - -cluster_mcast_addr.label: -"""多播地址""" - -desc_cluster_dns.desc: -"""DNS SRV 记录服务发现。""" - -desc_cluster_dns.label: -"""DNS SRV 记录服务发现""" - -cluster_dns_name.desc: -"""指定 DNS A 记录的名字。emqx 会通过访问这个 DNS A 记录来获取 IP 地址列表。 -当cluster.discovery_strategydns 时有效。""" - -cluster_dns_name.label: -"""DNS名称""" - -rpc_keyfile.desc: -"""rpc.certfile 的私钥文件的路径。
-注意:此文件内容是私钥,所以需要设置权限为 600。""" - -rpc_keyfile.label: -"""RPC 私钥文件""" - -cluster_mcast_recbuf.desc: -"""接收数据报的内核级缓冲区的大小。 -当 cluster.discovery_strategy 为 mcast 时,此配置项才有效。""" - -cluster_mcast_recbuf.label: -"""多播接收数据缓冲区""" - -cluster_autoheal.desc: -"""集群脑裂自动恢复机制开关。""" - -cluster_autoheal.label: -"""节点脑裂自动修复机制""" - -log_overload_kill_enable.desc: -"""日志处理进程过载时为保护自己节点其它的业务能正常,强制杀死日志处理进程。""" - -log_overload_kill_enable.label: -"""日志处理进程过载保护""" - -node_etc_dir.desc: -"""etc 存放目录""" - -node_etc_dir.label: -"""Etc 目录""" - -cluster_proto_dist.desc: -"""分布式 Erlang 集群协议类型。可选值为:
-- inet_tcp: 使用 IPv4
-- inet_tls: 使用 TLS,需要配合 etc/ssl_dist.conf 一起使用。""" - -cluster_proto_dist.label: -"""集群内部通信协议""" - -log_burst_limit_enable.desc: -"""启用日志限流保护机制。""" - -log_burst_limit_enable.label: -"""日志限流保护""" - -dist_buffer_size.desc: -"""Erlang分布式缓冲区的繁忙阈值,单位是KB。""" - -dist_buffer_size.label: -"""Erlang分布式缓冲区的繁忙阈值(KB)""" - -common_handler_max_depth.desc: -"""Erlang 内部格式日志格式化和 Erlang 进程消息队列检查的最大深度。""" - -common_handler_max_depth.label: -"""最大深度""" - -desc_log.desc: -"""EMQX 支持同时多个日志输出,一个控制台输出,和多个文件输出。 -默认情况下,EMQX 运行在容器中,或者在 'console' 或 'foreground' 模式下运行时,会输出到 控制台,否则输出到文件。""" - -desc_log.label: -"""日志""" - -common_handler_flush_qlen.desc: -"""如果缓冲日志事件的数量增长大于此阈值,则会发生冲刷(删除)操作。 日志处理进程会丢弃缓冲的日志消息。 -来缓解自身不会由于内存瀑涨而影响其它业务进程。日志内容会提醒有多少事件被删除。""" - -common_handler_flush_qlen.label: -"""冲刷阈值""" - -common_handler_chars_limit.desc: -"""设置单个日志消息的最大长度。 如果超过此长度,则日志消息将被截断。最小可设置的长度为100。 -注意:如果日志格式为 JSON,限制字符长度可能会导致截断不完整的 JSON 数据。""" - -common_handler_chars_limit.label: -"""单条日志长度限制""" - -cluster_k8s_namespace.desc: -"""当使用 k8s 方式并且 cluster.k8s.address_type 指定为 dns 类型时, -可设置 emqx 节点名的命名空间。与 cluster.k8s.suffix 一起使用用以拼接得到节点名列表。""" - -cluster_k8s_namespace.label: -"""K8s 命名空间""" - -node_name.desc: -"""节点名。格式为 \@\。其中 可以是 IP 地址,也可以是 FQDN。 -详见 http://erlang.org/doc/reference_manual/distributed.html。""" - -node_name.label: -"""节点名""" - -rpc_port_discovery.desc: -"""manual: 通过 tcp_server_port 来发现端口。 -
stateless: 使用无状态的方式来发现端口,使用如下算法。如果节点名称是 -emqxN@127.0.0.1, N 是一个数字,那么监听端口就是 5370 + N。""" - -rpc_port_discovery.label: -"""RPC 端口发现策略""" - -log_overload_kill_restart_after.desc: -"""处理进程停止后,会在该延迟时间后自动重新启动。除非该值设置为 infinity,这会阻止任何后续的重启。""" - -log_overload_kill_restart_after.label: -"""处理进程重启延迟""" - -log_file_handler_max_size.desc: -"""此参数控制日志文件轮换。 `infinity` 意味着日志文件将无限增长,否则日志文件将在达到 `max_size`(以字节为单位)时进行轮换。 -与 rotation count配合使用。如果 counter 为 10,则是10个文件轮换。""" - -log_file_handler_max_size.label: -"""日志文件轮换大小""" - -desc_log_file_handler.desc: -"""日志处理进程将日志事件打印到文件。""" - -desc_log_file_handler.label: -"""文件日志处理进程""" - -rpc_socket_keepalive_count.desc: -"""keepalive 探测消息发送失败的次数,直到 RPC 连接被认为已经断开。""" - -rpc_socket_keepalive_count.label: -"""RPC Socket Keepalive 次数""" - -cluster_etcd_server.desc: -"""指定 etcd 服务的地址。如有多个服务使用逗号 , 分隔。 -当 cluster.discovery_strategy 为 etcd 时,此配置项才有效。""" - -cluster_etcd_server.label: -"""Etcd 服务器地址""" - -db_backend.desc: -"""配置后端数据库驱动,默认值为 rlog 它适用于大规模的集群。 -mnesia 是备选数据库,适合中小集群。""" - -db_backend.label: -"""内置数据库""" - -desc_authorization.desc: -"""授权相关""" - -desc_authorization.label: -"""授权""" - -cluster_etcd_ssl.desc: -"""当使用 TLS 连接 etcd 时的配置选项。 -当 cluster.discovery_strategy 为 etcd 时,此配置项才有效。""" - -cluster_etcd_ssl.label: -"""Etcd SSL 选项""" - -rpc_insecure_fallback.desc: -"""兼容旧的无鉴权模式""" - -rpc_insecure_fallback.label: -"""向后兼容旧的无鉴权模式""" - -cluster_mcast_buffer.desc: -"""用户级缓冲区的大小。 -当 cluster.discovery_strategy 为 mcast 时,此配置项才有效。""" - -cluster_mcast_buffer.label: -"""多播用户级缓冲区""" - -rpc_authentication_timeout.desc: -"""远程节点认证的超时时间。""" - -rpc_authentication_timeout.label: -"""RPC 认证超时时间""" - -cluster_call_retry_interval.desc: -"""当集群间调用出错时,多长时间重试一次。""" - -cluster_call_retry_interval.label: -"""重试时间间隔""" - -cluster_mcast_sndbuf.desc: -"""外发数据报的内核级缓冲区的大小。 -当 cluster.discovery_strategy 为 mcast 时,此配置项才有效。""" - -cluster_mcast_sndbuf.label: -"""多播发送缓存区""" - -rpc_driver.desc: -"""集群间通信使用的传输协议。""" - -rpc_driver.label: -"""RPC 驱动""" - -max_ets_tables.desc: -"""Erlang ETS 表的最大数量""" - -max_ets_tables.label: -"""Erlang 表的最大数量""" - -desc_db.desc: -"""内置数据库的配置。""" - -desc_db.label: -"""数据库""" - -desc_cluster_etcd.desc: -"""使用 'etcd' 服务的服务发现。""" - -desc_cluster_etcd.label: -"""'etcd' 服务的服务发现""" - -cluster_name.desc: -"""EMQX集群名称。每个集群都有一个唯一的名称。服务发现时会用于做路径的一部分。""" - -cluster_name.label: -"""集群名称""" - -log_rotation_enable.desc: -"""启用日志轮换功能。启动后生成日志文件后缀会加上对应的索引数字,比如:log/emqx.log.1。 -系统会默认生成*.siz/*.idx用于记录日志位置,请不要手动修改这两个文件。""" - -log_rotation_enable.label: -"""日志轮换""" - -cluster_call_cleanup_interval.desc: -"""清理过期事务的时间间隔""" - -cluster_call_cleanup_interval.label: -"""清理间隔""" - -desc_cluster_static.desc: -"""静态节点服务发现。新节点通过连接一个节点来加入集群。""" - -desc_cluster_static.label: -"""静态节点服务发现""" - -db_default_shard_transport.desc: -"""定义用于推送事务日志的默认传输。
-这可以在 db.shard_transports 中基于每个分片被覆盖。 -gen_rpc 使用 gen_rpc 库, -distr 使用 Erlang 发行版。""" - -db_default_shard_transport.label: -"""事务日志传输默认协议""" - -cluster_static_seeds.desc: -"""集群中的EMQX节点名称列表, -指定固定的节点列表,多个节点间使用逗号 , 分隔。 -当 cluster.discovery_strategy 为 static 时,此配置项才有效。 -适合于节点数量较少且固定的集群。""" - -cluster_static_seeds.label: -"""集群静态节点""" - -log_overload_kill_qlen.desc: -"""允许的最大队列长度。""" - -log_overload_kill_qlen.label: -"""最大队列长度""" - -node_backtrace_depth.desc: -"""错误信息中打印的最大堆栈层数""" - -node_backtrace_depth.label: -"""最大堆栈导数""" - -desc_log_burst_limit.desc: -"""短时间内产生的大量日志事件可能会导致问题,例如: - - 日志文件变得非常大 - - 日志文件轮换过快,有用信息被覆盖 - - 对系统的整体性能影响 - -日志突发限制功能可以暂时禁用日志记录以避免这些问题。""" - -desc_log_burst_limit.label: -"""日志突发限制""" - -common_handler_enable.desc: -"""启用此日志处理进程。""" - -common_handler_enable.label: -"""启用日志处理进程""" - -cluster_k8s_service_name.desc: -"""指定 Kubernetes 中 EMQX 的服务名。 -当 cluster.discovery_strategy 为 k8s 时,此配置项才有效。""" - -cluster_k8s_service_name.label: -"""K8s 服务别名""" - -log_rotation_count.desc: -"""轮换的最大日志文件数。""" - -log_rotation_count.label: -"""最大日志文件数""" - -node_cookie.desc: -"""分布式 Erlang 集群使用的 cookie 值。集群间保持一致""" - -node_cookie.label: -"""节点 Cookie""" - -db_role.desc: -"""选择节点的角色。
-core 节点提供数据的持久性,并负责写入。建议将核心节点放置在不同的机架或不同的可用区。
-repliant 节点是临时工作节点。 从集群中删除它们,不影响数据库冗余
-建议复制节点多于核心节点。
-注意:该参数仅在设置backend时生效到 rlog。""" - -db_role.label: -"""数据库角色""" - -rpc_tcp_server_port.desc: -"""RPC 本地服务使用的 TCP 端口。
-只有当 rpc.port_discovery 设置为 manual 时,此配置才会生效。""" - -rpc_tcp_server_port.label: -"""RPC TCP 服务监听端口""" - -desc_console_handler.desc: -"""日志处理进程将日志事件打印到 EMQX 控制台。""" - -desc_console_handler.label: -"""Console Handler""" - -node_applications.desc: -"""当新EMQX 加入集群时,应重启的Erlang应用程序的列表。""" - -node_applications.label: -"""应用""" - -log_burst_limit_max_count.desc: -"""在 `window_time` 间隔内处理的最大日志事件数。 达到限制后,将丢弃连续事件,直到 `window_time` 结束。""" - -log_burst_limit_max_count.label: -"""日志事件数""" - -rpc_tcp_client_num.desc: -"""设置本节点与远程节点之间的 RPC 通信通道的最大数量。""" - -rpc_tcp_client_num.label: -"""RPC TCP 客户端数量""" - -cluster_k8s_address_type.desc: -"""当使用 k8s 方式集群时,address_type 用来从 Kubernetes 接口的应答里获取什么形式的 Host 列表。 -指定 cluster.k8s.address_typeip,则将从 Kubernetes 接口中获取集群中其他节点 -的IP地址。""" - -cluster_k8s_address_type.label: -"""K8s 地址类型""" - -rpc_socket_sndbuf.desc: -"""TCP 调节参数。TCP 发送缓冲区大小。""" - -rpc_socket_sndbuf.label: -"""RPC 套接字发送缓冲区大小""" - -cluster_mcast_ttl.desc: -"""指定多播的 Time-To-Live 值。 -当 cluster.discovery_strategy 为 mcast 时,此配置项才有效。""" - -cluster_mcast_ttl.label: -"""多播TTL""" - -db_core_nodes.desc: -"""当前节点连接的核心节点列表。
-注意:该参数仅在设置backend时生效到 rlog -并且设置rolereplicant时生效。
-该值需要在手动或静态集群发现机制下设置。
-如果使用了自动集群发现机制(如etcd),则不需要设置该值。""" - -db_core_nodes.label: -"""数据库核心节点""" - -log_file_handler_file.desc: -"""日志文件路径及名字。""" - -log_file_handler_file.label: -"""日志文件名字""" - -node_dist_net_ticktime.desc: -"""系统调优参数,此配置将覆盖 vm.args 文件里的 -kernel net_ticktime 参数。当一个节点持续无响应多久之后,认为其已经宕机并断开连接。""" - -node_dist_net_ticktime.label: -"""节点间心跳间隔""" - -desc_cluster_k8s.desc: -"""Kubernetes 服务发现。""" - -desc_cluster_k8s.label: -"""Kubernetes 服务发现""" - -desc_cluster_mcast.desc: -"""UDP 组播服务发现。""" - -desc_cluster_mcast.label: -"""UDP 组播服务发现""" - -rpc_cacertfile.desc: -"""验证 rpc.certfile 的 CA 证书文件的路径。
-注意:集群中所有节点的证书必须使用同一个 CA 签发。""" - -rpc_cacertfile.label: -"""RPC CA 证书文件""" - -desc_node.desc: -"""节点名称、Cookie、配置文件、数据目录和 Erlang 虚拟机(BEAM)启动参数。""" - -desc_node.label: -"""节点""" - -cluster_k8s_apiserver.desc: -"""指定 Kubernetes API Server。如有多个 Server 使用逗号 , 分隔。 -当 cluster.discovery_strategy 为 k8s 时,此配置项才有效。""" - -cluster_k8s_apiserver.label: -"""K8s 服务地址""" - -common_handler_supervisor_reports.desc: -"""Supervisor 报告的类型。默认为 error 类型。
- - error:仅记录 Erlang 进程中的错误。 - - progress:除了 error 信息外,还需要记录进程启动的详细信息。""" - -common_handler_supervisor_reports.label: -"""报告类型""" - -node_data_dir.desc: -"""节点数据存放目录,可能会自动创建的子目录如下:
-- `mnesia/`。EMQX的内置数据库目录。例如,`mnesia/emqx@127.0.0.1`。
-如果节点要被重新命名(例如,`emqx@10.0.1.1`)。旧目录应该首先被删除。
-- `configs`。在启动时生成的配置,以及集群/本地覆盖的配置。
-- `patches`: 热补丁文件将被放在这里。
-- `trace`: 日志跟踪文件。
- -**注意**: 一个数据dir不能被两个或更多的EMQX节点同时使用。""" - -node_data_dir.label: -"""节点数据目录""" - -cluster_k8s_suffix.desc: -"""当使用 k8s 方式并且 cluster.k8s.address_type 指定为 dns 类型时,可设置 emqx 节点名的后缀。 -与 cluster.k8s.namespace 一起使用用以拼接得到节点名列表。""" - -cluster_k8s_suffix.label: -"""K8s 前缀""" - -db_rpc_module.desc: -"""集群间推送事务日志到复制节点使用的协议。""" - -db_rpc_module.label: -"""RPC协议""" - -cluster_etcd_prefix.desc: -"""指定 etcd 路径的前缀。每个节点在 etcd 中都会创建一个路径: -v2/keys///
-当 cluster.discovery_strategy 为 etcd 时,此配置项才有效。""" - -cluster_etcd_prefix.label: -"""Etcd 路径前缀""" - -cluster_mcast_iface.desc: -"""指定节点发现服务需要绑定到本地 IP 地址。 -当 cluster.discovery_strategy 为 mcast 时,此配置项才有效。""" - -cluster_mcast_iface.label: -"""多播绑定地址""" - -log_burst_limit_window_time.desc: -"""参考 max_count。""" - -log_burst_limit_window_time.label: -"""Window Time""" - -cluster_dns_record_type.desc: -"""DNS 记录类型。""" - -cluster_dns_record_type.label: -"""DNS记录类型""" - -cluster_autoclean.desc: -"""指定多久之后从集群中删除离线节点。""" - -cluster_autoclean.label: -"""自动删除离线节点时间""" - -process_limit.desc: -"""Erlang系统同时存在的最大进程数。 -实际选择的最大值可能比设置的数字大得多。 -参考: https://www.erlang.org/doc/man/erl.html""" - -process_limit.label: -"""Erlang 最大进程数""" - -max_ports.desc: -"""Erlang系统同时存在的最大端口数。 -实际选择的最大值可能比设置的数字大得多。 -参考: https://www.erlang.org/doc/man/erl.html""" - -max_ports.label: -"""Erlang 最大端口数""" - -desc_log_rotation.desc: -"""默认情况下,日志存储在 `./log` 目录(用于从 zip 文件安装)或 `/var/log/emqx`(用于二进制安装)。
-这部分配置,控制每个日志处理进程保留的文件数量。""" - -desc_log_rotation.label: -"""日志轮换""" - -desc_log_overload_kill.desc: -"""日志过载终止,具有过载保护功能。当日志处理进程使用过多内存,或者缓存的日志消息过多时该功能被激活。
-检测到过载时,日志处理进程将终止,并在冷却期后重新启动。""" - -desc_log_overload_kill.label: -"""日志过载保护""" - -authorization.desc: -"""授权(ACL)。EMQX 支持完整的客户端访问控制(ACL)。""" - -authorization.label: -"""授权""" - -rpc_socket_keepalive_idle.desc: -"""broker 之间的连接在最后一条消息发送后保持打开的时间。""" - -rpc_socket_keepalive_idle.label: -"""RPC Socket Keepalive Idle""" - -desc_cluster_call.desc: -"""集群调用功能的选项。""" - -desc_cluster_call.label: -"""集群调用""" - -cluster_mcast_ports.desc: -"""指定多播端口。如有多个端口使用逗号 , 分隔。 -当 cluster.discovery_strategy 为 mcast 时,此配置项才有效。""" - -cluster_mcast_ports.label: -"""多播端口""" - -log_overload_kill_mem_size.desc: -"""日志处理进程允许使用的最大内存。""" - -log_overload_kill_mem_size.label: -"""日志处理进程允许使用的最大内存""" - -rpc_connect_timeout.desc: -"""建立 RPC 连接的超时时间。""" - -rpc_connect_timeout.label: -"""RPC 连接超时时间""" - -cluster_etcd_node_ttl.desc: -"""指定 etcd 中节点信息的过期时间。 -当 cluster.discovery_strategy 为 etcd 时,此配置项才有效。""" - -cluster_etcd_node_ttl.label: -"""Etcd 节点过期时间""" - -rpc_call_receive_timeout.desc: -"""同步 RPC 的回复超时时间。""" - -rpc_call_receive_timeout.label: -"""RPC 调用接收超时时间""" - -rpc_socket_recbuf.desc: -"""TCP 调节参数。TCP 接收缓冲区大小。""" - -rpc_socket_recbuf.label: -"""RPC 套接字接收缓冲区大小""" - -db_tlog_push_mode.desc: -"""同步模式下,核心节点等待复制节点的确认信息,然后再发送下一条事务日志。""" - -db_tlog_push_mode.label: -"""Tlog推送模式""" - -node_crash_dump_bytes.desc: -"""限制崩溃文件的大小,当崩溃时节点内存太大, -如果为了保存现场,需要全部存到崩溃文件中,此处限制最多能保存多大的文件。 -如果超过此限制,崩溃转储将被截断。如果设置为0,系统不会尝试写入崩溃转储文件。""" - -node_crash_dump_bytes.label: -"""崩溃文件最大容量""" - -rpc_certfile.desc: -"""TLS 证书文件的路径,用于验证集群节点的身份。 -只有当 rpc.driver 设置为 ssl 时,此配置才会生效。""" - -rpc_certfile.label: -"""RPC 证书文件""" - -node_crash_dump_seconds.desc: -"""该配置给出了运行时系统允许花费的写入崩溃转储的秒数。当给定的秒数已经过去,运行时系统将被终止。
-- 如果设置为0秒,运行时会立即终止,不会尝试写入崩溃转储文件。
-- 如果设置为一个正数 S,节点会等待 S 秒来完成崩溃转储文件,然后用SIGALRM信号终止运行时系统。
-- 如果设置为一个负值导致运行时系统的终止等待无限期地直到崩溃转储文件已经完全写入。""" - -node_crash_dump_seconds.label: -"""保存崩溃文件最长时间""" - -log_file_handlers.desc: -"""输出到文件的日志处理进程列表""" - -log_file_handlers.label: -"""File Handler""" - -node_global_gc_interval.desc: -"""系统调优参数,设置节点运行多久强制进行一次全局垃圾回收。禁用设置为 disabled。""" - -node_global_gc_interval.label: -"""全局垃圾回收""" - -common_handler_time_offset.desc: -"""日志中的时间戳使用的时间偏移量。 -可选值为: - - system: 本地系统使用的时区偏移量 - - utc: 0 时区的偏移量 - - +-[hh]:[mm]: 自定义偏移量,比如 "-02:00" 或者 "+00:00" -默认值为本地系统的时区偏移量:system。""" - -common_handler_time_offset.label: -"""时间偏移量""" - -rpc_mode.desc: -"""在 sync 模式下,发送端等待接收端的 ack信号。""" - -rpc_mode.label: -"""RPC 模式""" - -node_crash_dump_file.desc: -"""设置 Erlang crash_dump 文件的存储路径和文件名。""" - -node_crash_dump_file.label: -"""节点崩溃时的Dump文件""" - -cluster_mcast_loop.desc: -"""设置多播的报文是否投递到本地回环地址。 -当 cluster.discovery_strategy 为 mcast 时,此配置项才有效。""" - -cluster_mcast_loop.label: -"""多播回环开关""" - -rpc_socket_keepalive_interval.desc: -"""keepalive 消息的间隔。""" - -rpc_socket_keepalive_interval.label: -"""RPC Socket Keepalive 间隔""" - -common_handler_level.desc: -"""当前日志处理进程的日志级别。 -默认为 warning 级别。""" - -common_handler_level.label: -"""日志级别""" - -desc_rpc.desc: -"""EMQX 使用 gen_rpc 库来实现跨节点通信。
-大多数情况下,默认的配置应该可以工作,但如果你需要做一些性能优化或者实验,可以尝试调整这些参数。""" - -desc_rpc.label: -"""RPC""" - -rpc_ssl_server_port.desc: -"""RPC 本地服务使用的监听SSL端口。
-只有当 rpc.port_discovery 设置为 manual 且 dirver 设置为 ssl, -此配置才会生效。""" - -rpc_ssl_server_port.label: -"""RPC SSL 服务监听端口""" - -desc_cluster.desc: -"""EMQX 节点可以组成一个集群,以提高总容量。
这里指定了节点之间如何连接。""" - -desc_cluster.label: -"""集群""" - -common_handler_sync_mode_qlen.desc: -"""只要缓冲的日志事件的数量低于这个值,所有的日志事件都会被异步处理。 -这意味着,日志落地速度不会影响正常的业务进程,因为它们不需要等待日志处理进程的响应。 -如果消息队列的增长超过了这个值,处理程序开始同步处理日志事件。也就是说,发送事件的客户进程必须等待响应。 -当处理程序将消息队列减少到低于sync_mode_qlen阈值的水平时,异步操作就会恢复。 -默认为100条信息,当等待的日志事件大于100条时,就开始同步处理日志。""" - -common_handler_sync_mode_qlen.label: -"""进入异步模式的队列长度""" - -common_handler_formatter.desc: -"""选择日志格式类型。 text 用于纯文本,json 用于结构化日志记录。""" - -common_handler_formatter.label: -"""日志格式类型""" - -rpc_async_batch_size.desc: -"""异步模式下,发送的批量消息的最大数量。""" - -rpc_async_batch_size.label: -"""异步模式下的批量消息数量""" - -cluster_call_max_history.desc: -"""集群间调用最多保留的历史记录数。只用于排错时查看。""" - -cluster_call_max_history.label: -"""最大历史记录""" - -cluster_discovery_strategy.desc: -"""集群节点发现方式。可选值为: -- manual: 使用 emqx ctl cluster 命令管理集群。
-- static: 配置静态节点。配置几个固定的节点,新节点通过连接固定节点中的某一个来加入集群。
-- dns: 使用 DNS A 记录的方式发现节点。
-- etcd: 使用 etcd 发现节点。
-- k8s: 使用 Kubernetes API 发现节点。""" - -cluster_discovery_strategy.label: -"""集群服务发现策略""" - -rpc_send_timeout.desc: -"""发送 RPC 请求的超时时间。""" - -rpc_send_timeout.label: -"""RPC 发送超时时间""" - -common_handler_single_line.desc: -"""如果设置为 true,则单行打印日志。 否则,日志消息可能跨越多行。""" - -common_handler_single_line.label: -"""单行模式""" - -rpc_socket_buffer.desc: -"""TCP 调节参数。用户模式套接字缓冲区大小。""" - -rpc_socket_buffer.label: -"""RPC 套接字缓冲区大小""" - -db_shard_transports.desc: -"""允许为每个 shard 下的事务日志复制操作的传输方法进行调优。
-gen_rpc 使用 gen_rpc 库, -distr 使用 Erlang 自带的 rpc 库。
如果未指定, -默认是使用 db.default_shard_transport 中设置的值。""" - -db_shard_transports.label: -"""事务日志传输协议""" - -} diff --git a/rel/i18n/zh/emqx_connector_api.hocon b/rel/i18n/zh/emqx_connector_api.hocon deleted file mode 100644 index ab651c102..000000000 --- a/rel/i18n/zh/emqx_connector_api.hocon +++ /dev/null @@ -1,46 +0,0 @@ -emqx_connector_api { - -conn_get.desc: -"""列出所有连接器""" - -conn_get.label: -"""列出所有连接器""" - -conn_id_delete.desc: -"""通过 ID 删除一个连接器""" - -conn_id_delete.label: -"""删除连接器""" - -conn_id_get.desc: -"""通过 ID 获取连接器""" - -conn_id_get.label: -"""获取连接器""" - -conn_id_put.desc: -"""通过 ID 更新一个连接器""" - -conn_id_put.label: -"""更新连接器""" - -conn_post.desc: -"""创建一个新的连接器""" - -conn_post.label: -"""创建连接器""" - -conn_test_post.desc: -"""通过给定的 ID 测试创建一个新的连接器
-ID 的格式必须为“{type}:{name}”""" - -conn_test_post.label: -"""创建测试连接器""" - -id.desc: -"""连接器 ID, 格式必须为 {type}:{name}""" - -id.label: -"""连接器 ID""" - -} diff --git a/rel/i18n/zh/emqx_connector_http.hocon b/rel/i18n/zh/emqx_connector_http.hocon deleted file mode 100644 index af7869e12..000000000 --- a/rel/i18n/zh/emqx_connector_http.hocon +++ /dev/null @@ -1,69 +0,0 @@ -emqx_connector_http { - -body.desc: -"""HTTP请求报文主体。""" - -body.label: -"""HTTP请求报文主体""" - -connect_timeout.desc: -"""连接HTTP服务器的超时时间。""" - -connect_timeout.label: -"""连接超时""" - -enable_pipelining.desc: -"""正整数,设置最大可发送的异步 HTTP 请求数量。当设置为 1 时,表示每次发送完成 HTTP 请求后都需要等待服务器返回,再继续发送下一个请求。""" - -enable_pipelining.label: -"""HTTP 管道""" - -headers.desc: -"""HTTP 头字段列表。""" - -headers.label: -"""HTTP 头字段列表""" - -max_retries.desc: -"""请求出错时的最大重试次数。""" - -max_retries.label: -"""最大重试次数""" - -method.desc: -"""HTTP 请求方法。""" - -method.label: -"""HTTP 请求方法""" - -path.desc: -"""HTTP请求路径。""" - -path.label: -"""HTTP请求路径""" - -pool_size.desc: -"""连接池大小。""" - -pool_size.label: -"""连接池大小""" - -pool_type.desc: -"""连接池的类型,可用类型有`random`, `hash`。""" - -pool_type.label: -"""连接池类型""" - -request.desc: -"""设置 HTTP 请求的参数。""" - -request.label: -"""HTTP 请求""" - -request_timeout.desc: -"""HTTP 请求超时。""" - -request_timeout.label: -"""HTTP 请求超时""" - -} diff --git a/rel/i18n/zh/emqx_connector_ldap.hocon b/rel/i18n/zh/emqx_connector_ldap.hocon deleted file mode 100644 index 6826af6e6..000000000 --- a/rel/i18n/zh/emqx_connector_ldap.hocon +++ /dev/null @@ -1,21 +0,0 @@ -emqx_connector_ldap { - -bind_dn.desc: -"""LDAP 绑定的 DN 的值""" - -bind_dn.label: -"""Bind DN""" - -port.desc: -"""LDAP 端口""" - -port.label: -"""端口""" - -timeout.desc: -"""LDAP 查询超时时间""" - -timeout.label: -"""超时时间""" - -} diff --git a/rel/i18n/zh/emqx_connector_mongo.hocon b/rel/i18n/zh/emqx_connector_mongo.hocon deleted file mode 100644 index cd84a242e..000000000 --- a/rel/i18n/zh/emqx_connector_mongo.hocon +++ /dev/null @@ -1,152 +0,0 @@ -emqx_connector_mongo { - -auth_source.desc: -"""与用户证书关联的数据库名称。""" - -auth_source.label: -"""认证源""" - -connect_timeout.desc: -"""超时重连的等待时间。""" - -connect_timeout.label: -"""连接超时""" - -desc_rs.desc: -"""配置 Replica Set""" - -desc_rs.label: -"""配置 Replica Set""" - -desc_sharded.desc: -"""配置 Sharded Cluster""" - -desc_sharded.label: -"""配置 Sharded Cluster""" - -desc_single.desc: -"""配置 Single 模式""" - -desc_single.label: -"""配置 Single 模式""" - -desc_topology.desc: -"""配置 Topology""" - -desc_topology.label: -"""配置 Topology""" - -heartbeat_period.desc: -"""控制驱动程序何时检查MongoDB部署的状态。指定检查的间隔时间,从上一次检查结束到下一次检查开始计算。如果连接数增加(例如,如果你增加池子的大小,就会发生这种情况),你可能也需要增加这个周期,以避免在MongoDB日志文件中创建太多的日志条目。""" - -heartbeat_period.label: -"""心跳期""" - -local_threshold.desc: -"""在多个合适的MongoDB实例中进行选择的延迟窗口的大小。""" - -local_threshold.label: -"""本地阈值""" - -max_overflow.desc: -"""最大溢出。""" - -max_overflow.label: -"""最大溢出""" - -min_heartbeat_period.desc: -"""心跳间的最小间隙""" - -min_heartbeat_period.label: -"""最小心跳周期""" - -overflow_check_period.desc: -"""检查是否有超过配置的工人的周期("溢出")。""" - -overflow_check_period.label: -"""溢出检查周期""" - -overflow_ttl.desc: -"""当池内工人太多时,等待多久清除多余工人。""" - -overflow_ttl.label: -"""溢出TTL""" - -r_mode.desc: -"""读模式。""" - -r_mode.label: -"""读模式""" - -replica_set_name.desc: -"""副本集的名称。""" - -replica_set_name.label: -"""副本集名称""" - -rs_mongo_type.desc: -"""Replica set模式。当 MongoDB 服务运行在 replica-set 模式下,该配置必须设置为 'rs'。""" - -rs_mongo_type.label: -"""Replica set 模式""" - -server.desc: -"""将要连接的 IPv4 或 IPv6 地址,或者主机名。
-主机名具有以下形式:`Host[:Port]`。
-如果未指定 `[:Port]`,则使用 MongoDB 默认端口 27017。""" - -server.label: -"""服务器地址""" - -server_selection_timeout.desc: -"""指定在抛出异常之前为服务器选择阻断多长时间。""" - -server_selection_timeout.label: -"""服务器选择超时""" - -servers.desc: -"""集群将要连接的节点列表。 节点之间用逗号分隔,如:`Node[,Node].` -每个节点的配置为:将要连接的 IPv4 或 IPv6 地址或主机名。 -主机名具有以下形式:`Host[:Port]`。 -如果未指定 `[:Port]`,则使用 MongoDB 默认端口 27017。""" - -servers.label: -"""服务器列表""" - -sharded_mongo_type.desc: -"""Sharded cluster模式。当 MongoDB 服务运行在 sharded 模式下,该配置必须设置为 'sharded'。""" - -sharded_mongo_type.label: -"""Sharded cluster 模式""" - -single_mongo_type.desc: -"""Standalone 模式。当 MongoDB 服务运行在 standalone 模式下,该配置必须设置为 'single'。""" - -single_mongo_type.label: -"""Standalone 模式""" - -socket_timeout.desc: -"""在尝试超时之前,在套接字上尝试发送或接收的持续时间。""" - -socket_timeout.label: -"""套接字操作超时""" - -srv_record.desc: -"""使用 DNS SRV 记录。""" - -srv_record.label: -"""SRV 记录""" - -w_mode.desc: -"""写模式。""" - -w_mode.label: -"""写模式""" - -wait_queue_timeout.desc: -"""工作者等待连接可用的最长时间。""" - -wait_queue_timeout.label: -"""等待队列超时""" - -} diff --git a/rel/i18n/zh/emqx_connector_mqtt.hocon b/rel/i18n/zh/emqx_connector_mqtt.hocon deleted file mode 100644 index 1df6a89b6..000000000 --- a/rel/i18n/zh/emqx_connector_mqtt.hocon +++ /dev/null @@ -1,21 +0,0 @@ -emqx_connector_mqtt { - -name.desc: -"""连接器名称,人类可读的连接器描述。""" - -name.label: -"""连接器名称""" - -num_of_bridges.desc: -"""当前使用此连接器的网桥数量。""" - -num_of_bridges.label: -"""网桥数量""" - -type.desc: -"""连接器类型。""" - -type.label: -"""连接器类型""" - -} diff --git a/rel/i18n/zh/emqx_connector_mqtt_schema.hocon b/rel/i18n/zh/emqx_connector_mqtt_schema.hocon deleted file mode 100644 index 66ba2accf..000000000 --- a/rel/i18n/zh/emqx_connector_mqtt_schema.hocon +++ /dev/null @@ -1,170 +0,0 @@ -emqx_connector_mqtt_schema { - -bridge_mode.desc: -"""是否启用 Bridge Mode。 -注意:此设置只针对 MQTT 协议版本 < 5.0 有效,并且需要远程 MQTT Broker 支持 Bridge Mode。 -如果设置为 true ,桥接会告诉远端服务器当前连接是一个桥接而不是一个普通的客户端。 -这意味着消息回环检测会更加高效,并且远端服务器收到的保留消息的标志位会透传给本地。""" - -bridge_mode.label: -"""Bridge 模式""" - -clean_start.desc: -"""与 ingress MQTT 桥的远程服务器重连时是否清除老的 MQTT 会话。""" - -clean_start.label: -"""清除会话""" - -clientid_prefix.desc: -"""可选的前缀,用于在出口网桥使用的clientid前加上前缀。""" - -clientid_prefix.label: -"""客户ID前缀""" - -egress_desc.desc: -"""出口配置定义了该桥接如何将消息从本地 Broker 转发到远程 Broker。 -以下字段中允许使用带有变量的模板:'remote.topic', 'local.qos', 'local.retain', 'local.payload'。
-注意:如果此桥接被用作规则的动作,并且配置了 'local.topic',则从规则输出的数据以及匹配到 'local.topic' 的 MQTT 消息都会被转发。""" - -egress_desc.label: -"""出方向配置""" - -egress_local.desc: -"""如何从本地 Broker 接收消息相关的配置。""" - -egress_local.label: -"""本地配置""" - -egress_local_topic.desc: -"""要转发到远程broker的本地主题""" - -egress_local_topic.label: -"""本地主题""" - -egress_remote.desc: -"""发送消息到远程 Broker 相关的配置。""" - -egress_remote.label: -"""远程配置""" - -egress_remote_qos.desc: -"""待发送 MQTT 消息的 QoS。
-允许使用带有变量的模板。""" - -egress_remote_qos.label: -"""远程 QoS""" - -egress_remote_topic.desc: -"""转发到远程broker的哪个topic。
-允许使用带有变量的模板。""" - -egress_remote_topic.label: -"""远程主题""" - -ingress_desc.desc: -"""入口配置定义了该桥接如何从远程 MQTT Broker 接收消息,然后将消息发送到本地 Broker。
- 以下字段中允许使用带有变量的模板:'remote.qos', 'local.topic', 'local.qos', 'local.retain', 'local.payload'。
- 注意:如果此桥接被用作规则的输入,并且配置了 'local.topic',则从远程代理获取的消息将同时被发送到 'local.topic' 和规则。""" - -ingress_desc.label: -"""入方向配置""" - -ingress_local.desc: -"""发送消息到本地 Broker 相关的配置。""" - -ingress_local.label: -"""本地配置""" - -ingress_local_qos.desc: -"""待发送 MQTT 消息的 QoS。
-允许使用带有变量的模板。""" - -ingress_local_qos.label: -"""本地 QoS""" - -ingress_local_topic.desc: -"""向本地broker的哪个topic发送消息。
-允许使用带有变量的模板。""" - -ingress_local_topic.label: -"""本地主题""" - -ingress_remote.desc: -"""订阅远程 Broker 相关的配置。""" - -ingress_remote.label: -"""远程配置""" - -ingress_remote_qos.desc: -"""订阅远程borker时要使用的 QoS 级别""" - -ingress_remote_qos.label: -"""远程 QoS""" - -ingress_remote_topic.desc: -"""从远程broker的哪个topic接收消息""" - -ingress_remote_topic.label: -"""远程主题""" - -max_inflight.desc: -"""MQTT 协议的最大飞行(已发送但未确认)消息""" - -max_inflight.label: -"""最大飞行消息""" - -mode.desc: -"""MQTT 桥的模式。
-- cluster_shareload:在 emqx 集群的每个节点上创建一个 MQTT 连接。
-在“cluster_shareload”模式下,来自远程代理的传入负载通过共享订阅的方式接收。
-请注意,clientid 以节点名称为后缀,这是为了避免不同节点之间的 clientid 冲突。 -而且对于入口连接的 remote.topic,我们只能使用共享订阅主题过滤器。""" - -mode.label: -"""MQTT 桥接模式""" - -password.desc: -"""MQTT 协议的密码""" - -password.label: -"""密码""" - -payload.desc: -"""要发送的 MQTT 消息的负载。
-允许使用带有变量的模板。""" - -payload.label: -"""消息负载""" - -proto_ver.desc: -"""MQTT 协议版本""" - -proto_ver.label: -"""协议版本""" - -retain.desc: -"""要发送的 MQTT 消息的“保留”标志。
-允许使用带有变量的模板。""" - -retain.label: -"""保留消息标志""" - -server.desc: -"""远程 MQTT Broker的主机和端口。""" - -server.label: -"""Broker主机和端口""" - -server_configs.desc: -"""服务器相关的配置。""" - -server_configs.label: -"""服务配置。""" - -username.desc: -"""MQTT 协议的用户名""" - -username.label: -"""用户名""" - -} diff --git a/rel/i18n/zh/emqx_connector_mysql.hocon b/rel/i18n/zh/emqx_connector_mysql.hocon deleted file mode 100644 index a6900056f..000000000 --- a/rel/i18n/zh/emqx_connector_mysql.hocon +++ /dev/null @@ -1,11 +0,0 @@ -emqx_connector_mysql { - -server.desc: -"""将要连接的 IPv4 或 IPv6 地址,或者主机名。
-主机名具有以下形式:`Host[:Port]`。
-如果未指定 `[:Port]`,则使用 MySQL 默认端口 3306。""" - -server.label: -"""服务器地址""" - -} diff --git a/rel/i18n/zh/emqx_connector_pgsql.hocon b/rel/i18n/zh/emqx_connector_pgsql.hocon deleted file mode 100644 index c391034fd..000000000 --- a/rel/i18n/zh/emqx_connector_pgsql.hocon +++ /dev/null @@ -1,11 +0,0 @@ -emqx_connector_pgsql { - -server.desc: -"""将要连接的 IPv4 或 IPv6 地址,或者主机名。
-主机名具有以下形式:`Host[:Port]`。
-如果未指定 `[:Port]`,则使用 PostgreSQL 默认端口 5432。""" - -server.label: -"""服务器地址""" - -} diff --git a/rel/i18n/zh/emqx_connector_redis.hocon b/rel/i18n/zh/emqx_connector_redis.hocon deleted file mode 100644 index a0819876e..000000000 --- a/rel/i18n/zh/emqx_connector_redis.hocon +++ /dev/null @@ -1,50 +0,0 @@ -emqx_connector_redis { - -cluster.desc: -"""集群模式。当 Redis 服务运行在集群模式下,该配置必须设置为 'cluster'。""" - -cluster.label: -"""集群模式""" - -database.desc: -"""Redis 数据库 ID。""" - -database.label: -"""数据库 ID""" - -sentinel.desc: -"""哨兵模式。当 Redis 服务运行在哨兵模式下,该配置必须设置为 'sentinel'。""" - -sentinel.label: -"""哨兵模式""" - -sentinel_desc.desc: -"""Redis 哨兵模式下的集群名称。""" - -sentinel_desc.label: -"""集群名称""" - -server.desc: -"""将要连接的 IPv4 或 IPv6 地址,或者主机名。
-主机名具有以下形式:`Host[:Port]`。
-如果未指定 `[:Port]`,则使用 Redis 默认端口 6379。""" - -server.label: -"""服务器地址""" - -servers.desc: -"""集群将要连接的节点列表。 节点之间用逗号分隔,如:`Node[,Node].` -每个节点的配置为:将要连接的 IPv4 或 IPv6 地址或主机名。 -主机名具有以下形式:`Host[:Port]`。 -如果未指定 `[:Port]`,则使用 Redis 默认端口 6379。""" - -servers.label: -"""服务器列表""" - -single.desc: -"""单机模式。当 Redis 服务运行在单机模式下,该配置必须设置为 'single'。""" - -single.label: -"""单机模式""" - -} diff --git a/rel/i18n/zh/emqx_connector_schema_lib.hocon b/rel/i18n/zh/emqx_connector_schema_lib.hocon deleted file mode 100644 index 318b5c31c..000000000 --- a/rel/i18n/zh/emqx_connector_schema_lib.hocon +++ /dev/null @@ -1,45 +0,0 @@ -emqx_connector_schema_lib { - -auto_reconnect.desc: -"""已弃用。自动重连数据库。""" - -auto_reconnect.label: -"""已弃用。自动重连数据库""" - -database_desc.desc: -"""数据库名字。""" - -database_desc.label: -"""数据库名字""" - -password.desc: -"""内部数据库密码。""" - -password.label: -"""密码""" - -pool_size.desc: -"""桥接远端服务时使用的连接池大小。""" - -pool_size.label: -"""连接池大小""" - -prepare_statement.desc: -"""SQL 预处理语句列表。""" - -prepare_statement.label: -"""SQL 预处理语句列表""" - -ssl.desc: -"""启用 SSL 连接。""" - -ssl.label: -"""启用SSL""" - -username.desc: -"""内部数据库的用户名。""" - -username.label: -"""用户名""" - -} diff --git a/rel/i18n/zh/emqx_dashboard_api.hocon b/rel/i18n/zh/emqx_dashboard_api.hocon deleted file mode 100644 index 8292d8bba..000000000 --- a/rel/i18n/zh/emqx_dashboard_api.hocon +++ /dev/null @@ -1,66 +0,0 @@ -emqx_dashboard_api { - -change_pwd_api.desc: -"""更改 Dashboard 用户密码""" - -create_user_api.desc: -"""创建 Dashboard 用户""" - -create_user_api_success.desc: -"""创建 Dashboard 用户成功""" - -delete_user_api.desc: -"""删除 Dashboard 用户""" - -license.desc: -"""EMQX 许可类型。可为 opensource 或 enterprise""" - -list_users_api.desc: -"""Dashboard 用户列表""" - -login_api.desc: -"""获取 Dashboard 认证 Token。""" - -login_failed401.desc: -"""登录失败。用户名或密码错误""" - -login_failed_response400.desc: -"""登录失败。用户名或密码错误""" - -login_success.desc: -"""Dashboard 认证成功""" - -logout_api.desc: -"""Dashboard 用户登出""" - -new_pwd.desc: -"""新密码""" - -old_pwd.desc: -"""旧密码""" - -password.desc: -"""Dashboard 密码""" - -token.desc: -"""Dashboard 认证 Token""" - -update_user_api.desc: -"""更新 Dashboard 用户描述""" - -update_user_api200.desc: -"""更新 Dashboard 用户成功""" - -user_description.desc: -"""Dashboard 用户描述""" - -username.desc: -"""Dashboard 用户名""" - -users_api404.desc: -"""Dashboard 用户不存在""" - -version.desc: -"""EMQX 版本""" - -} diff --git a/rel/i18n/zh/emqx_dashboard_schema.hocon b/rel/i18n/zh/emqx_dashboard_schema.hocon deleted file mode 100644 index a07f3e6d8..000000000 --- a/rel/i18n/zh/emqx_dashboard_schema.hocon +++ /dev/null @@ -1,130 +0,0 @@ -emqx_dashboard_schema { - -backlog.desc: -"""排队等待连接的队列的最大长度。""" - -backlog.label: -"""排队长度""" - -bind.desc: -"""监听地址和端口,热更新此配置时,会重启 Dashboard 服务。""" - -bind.label: -"""绑定端口""" - -bootstrap_users_file.desc: -"""已废弃,请使用 api_key.bootstrap_file。""" - -bootstrap_users_file.label: -"""已废弃""" - -cors.desc: -"""支持跨域资源共享(CORS), -允许服务器指示任何来源(域名、协议或端口),除了本服务器之外的任何浏览器应允许加载资源。""" - -cors.label: -"""跨域资源共享""" - -default_password.desc: -"""Dashboard 的默认密码,为了安全,应该尽快修改密码。 -当通过网页首次登录 Dashboard 并按提示修改成复杂密码后,此值就会失效。""" - -default_password.label: -"""默认密码""" - -default_username.desc: -"""Dashboard 的默认用户名。""" - -default_username.label: -"""默认用户名""" - -desc_dashboard.desc: -"""EMQX Dashboard 配置。""" - -desc_dashboard.label: -"""Dashboard""" - -desc_http.desc: -"""Dashboard 监听器(HTTP)配置。""" - -desc_http.label: -"""HTTP""" - -desc_https.desc: -"""Dashboard 监听器(HTTPS)配置。""" - -desc_https.label: -"""HTTPS""" - -desc_listeners.desc: -"""Dashboard 监听器配置。""" - -desc_listeners.label: -"""监听器""" - -i18n_lang.desc: -"""设置 Swagger 多语言的版本,可为 en 或 zh。""" - -i18n_lang.label: -"""多语言支持""" - -inet6.desc: -"""启用IPv6, 如果机器不支持IPv6,请关闭此选项,否则会导致 Dashboard 无法使用。""" - -inet6.label: -"""IPv6""" - -ipv6_v6only.desc: -"""当开启 inet6 功能的同时禁用 IPv4-to-IPv6 映射。该配置仅在 inet6 功能开启时有效。""" - -ipv6_v6only.label: -"""IPv6 only""" - -listener_enable.desc: -"""忽略或启用该监听器。""" - -listener_enable.label: -"""启用""" - -listeners.desc: -"""Dashboard 监听器设置。监听器必须有唯一的端口号和IP地址的组合。 -例如,可以通过指定IP地址 0.0.0.0 来监听机器上给定端口上的所有配置的IP地址。 -或者,可以为每个监听器指定唯一的IP地址,但使用相同的端口。""" - -listeners.label: -"""监听器""" - -max_connections.desc: -"""同时处理的最大连接数。""" - -max_connections.label: -"""最大连接数""" - -num_acceptors.desc: -"""TCP协议的Socket acceptor池大小, 默认设置在线的调度器数量(通常为 CPU 核数)""" - -num_acceptors.label: -"""Acceptor 数量""" - -proxy_header.desc: -"""开启对 `HAProxy` 的支持,注意:一旦开启了这个功能,就无法再处理普通的 HTTP 请求了。""" - -proxy_header.label: -"""开启对 `HAProxy` 的支持""" - -sample_interval.desc: -"""Dashboard 中图表指标的时间间隔。必须小于60,且被60的整除,默认设置 10s。""" - -send_timeout.desc: -"""Socket发送超时时间。""" - -send_timeout.label: -"""发送超时时间""" - -token_expired_time.desc: -"""JWT token 过期时间。默认设置为 60 分钟。""" - -token_expired_time.label: -"""JWT 过期时间""" - -} diff --git a/rel/i18n/zh/emqx_delayed_api.hocon b/rel/i18n/zh/emqx_delayed_api.hocon deleted file mode 100644 index d783a17c3..000000000 --- a/rel/i18n/zh/emqx_delayed_api.hocon +++ /dev/null @@ -1,72 +0,0 @@ -emqx_delayed_api { - -bad_msgid_format.desc: -"""消息 ID 格式错误""" - -count.desc: -"""延迟消息总数""" - -delayed_interval.desc: -"""延迟时间(秒)""" - -delayed_remaining.desc: -"""剩余时间(秒)""" - -delete_api.desc: -"""删除延迟消息""" - -expected_at.desc: -"""期望的发送时间, RFC 3339 格式""" - -from_clientid.desc: -"""消息的 ClientID""" - -from_username.desc: -"""消息的 Username""" - -get_message_api.desc: -"""查看延迟消息""" - -illegality_limit.desc: -"""数量上限不合法""" - -list_api.desc: -"""查看延迟消息列表""" - -msgid.desc: -"""延迟消息 ID""" - -msgid_not_found.desc: -"""未找到对应消息""" - -node.desc: -"""消息的来源节点""" - -payload.desc: -"""消息内容, base64 格式。如果消息的大小超过 2048 字节,则消息内容会被设置为 'PAYLOAD_TO_LARGE'""" - -publish_at.desc: -"""客户端发送时间, RFC 3339 格式""" - -qos.desc: -"""QoS""" - -topic.desc: -"""主题""" - -update_api.desc: -"""开启或者关闭功能,或者设置延迟消息数量上限""" - -update_success.desc: -"""开启或者关闭功能操作成功""" - -view_limit.desc: -"""每页数量""" - -view_page.desc: -"""查看的页数""" - -view_status_api.desc: -"""查看慢订阅状态""" - -} diff --git a/rel/i18n/zh/emqx_ee_bridge_cassa.hocon b/rel/i18n/zh/emqx_ee_bridge_cassa.hocon deleted file mode 100644 index 2d1125a6b..000000000 --- a/rel/i18n/zh/emqx_ee_bridge_cassa.hocon +++ /dev/null @@ -1,40 +0,0 @@ -emqx_ee_bridge_cassa { - -config_enable.desc: -"""启用/禁用桥接""" - -config_enable.label: -"""启用/禁用桥接""" - -cql_template.desc: -"""CQL 模板""" - -cql_template.label: -"""CQL 模板""" - -desc_config.desc: -"""Cassandra 桥接配置""" - -desc_config.label: -"""Cassandra 桥接配置""" - -desc_name.desc: -"""桥接名字""" - -desc_name.label: -"""桥接名字""" - -desc_type.desc: -"""Bridge 类型""" - -desc_type.label: -"""桥接类型""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 Cassandra。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发。""" - -local_topic.label: -"""本地 Topic""" - -} diff --git a/rel/i18n/zh/emqx_ee_bridge_hstreamdb.hocon b/rel/i18n/zh/emqx_ee_bridge_hstreamdb.hocon deleted file mode 100644 index 55cdebe3c..000000000 --- a/rel/i18n/zh/emqx_ee_bridge_hstreamdb.hocon +++ /dev/null @@ -1,52 +0,0 @@ -emqx_ee_bridge_hstreamdb { - -config_direction.desc: -"""桥接的方向, 必须是 egress""" - -config_direction.label: -"""桥接方向""" - -config_enable.desc: -"""启用/禁用桥接""" - -config_enable.label: -"""启用/禁用桥接""" - -desc_config.desc: -"""HStreamDB 桥接配置""" - -desc_config.label: -"""HStreamDB 桥接配置""" - -desc_connector.desc: -"""连接器的通用配置。""" - -desc_connector.label: -"""连接器通用配置。""" - -desc_name.desc: -"""桥接名字,可读描述""" - -desc_name.label: -"""桥接名字""" - -desc_type.desc: -"""Bridge 类型""" - -desc_type.label: -"""桥接类型""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 HStreamDB。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发到 HStreamDB。""" - -local_topic.label: -"""本地 Topic""" - -payload.desc: -"""要转发到 HStreamDB 的数据内容,支持占位符""" - -payload.label: -"""消息内容""" - -} diff --git a/rel/i18n/zh/emqx_ee_bridge_mongodb.hocon b/rel/i18n/zh/emqx_ee_bridge_mongodb.hocon deleted file mode 100644 index 71703c662..000000000 --- a/rel/i18n/zh/emqx_ee_bridge_mongodb.hocon +++ /dev/null @@ -1,57 +0,0 @@ -emqx_ee_bridge_mongodb { - -collection.desc: -"""数据将被存储到的集合""" - -collection.label: -"""将要使用的集合(Collection)""" - -desc_config.desc: -"""为MongoDB桥配置""" - -desc_config.label: -"""MongoDB桥配置""" - -desc_name.desc: -"""桥接名称。""" - -desc_name.label: -"""桥接名称""" - -desc_type.desc: -"""桥接类型。""" - -desc_type.label: -"""桥接类型""" - -enable.desc: -"""启用或停用该MongoDB桥""" - -enable.label: -"""启用或禁用""" - -mongodb_rs_conf.desc: -"""MongoDB(Replica Set)配置""" - -mongodb_rs_conf.label: -"""MongoDB(Replica Set)配置""" - -mongodb_sharded_conf.desc: -"""MongoDB (Sharded)配置""" - -mongodb_sharded_conf.label: -"""MongoDB (Sharded)配置""" - -mongodb_single_conf.desc: -"""MongoDB(独立)配置""" - -mongodb_single_conf.label: -"""MongoDB(独立)配置""" - -payload_template.desc: -"""用于格式化写入 MongoDB 的消息模板。 如果未定义,规则引擎会使用 JSON 格式序列化所有的可见输入,例如 clientid, topic, payload 等。""" - -payload_template.label: -"""有效载荷模板""" - -} diff --git a/rel/i18n/zh/emqx_ee_bridge_mysql.hocon b/rel/i18n/zh/emqx_ee_bridge_mysql.hocon deleted file mode 100644 index e03b147b0..000000000 --- a/rel/i18n/zh/emqx_ee_bridge_mysql.hocon +++ /dev/null @@ -1,40 +0,0 @@ -emqx_ee_bridge_mysql { - -config_enable.desc: -"""启用/禁用桥接""" - -config_enable.label: -"""启用/禁用桥接""" - -desc_config.desc: -"""HStreamDB 桥接配置""" - -desc_config.label: -"""HStreamDB 桥接配置""" - -desc_name.desc: -"""桥接名字,可读描述""" - -desc_name.label: -"""桥接名字""" - -desc_type.desc: -"""Bridge 类型""" - -desc_type.label: -"""桥接类型""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 MySQL。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发。""" - -local_topic.label: -"""本地 Topic""" - -sql_template.desc: -"""SQL 模板""" - -sql_template.label: -"""SQL 模板""" - -} diff --git a/rel/i18n/zh/emqx_ee_bridge_redis.hocon b/rel/i18n/zh/emqx_ee_bridge_redis.hocon deleted file mode 100644 index 378df508f..000000000 --- a/rel/i18n/zh/emqx_ee_bridge_redis.hocon +++ /dev/null @@ -1,41 +0,0 @@ -emqx_ee_bridge_redis { - -command_template.desc: -"""用于推送数据的 Redis 命令模板。 每个列表元素代表一个命令名称或其参数。 -例如,要通过键值 `msgs` 将消息体推送到 Redis 列表中,数组元素应该是: `rpush`, `msgs`, `${payload}`。""" - -command_template.label: -"""Redis Command 模板""" - -config_enable.desc: -"""启用/禁用桥接""" - -config_enable.label: -"""启用/禁用桥接""" - -desc_config.desc: -"""Resis 桥接配置""" - -desc_config.label: -"""Redis 桥接配置""" - -desc_name.desc: -"""桥接名字,可读描述""" - -desc_name.label: -"""桥接名字""" - -desc_type.desc: -"""Bridge 类型""" - -desc_type.label: -"""桥接类型""" - -local_topic.desc: -"""发送到 'local_topic' 的消息都会转发到 Redis。
-注意:如果这个 Bridge 被用作规则(EMQX 规则引擎)的输出,同时也配置了 'local_topic' ,那么这两部分的消息都会被转发到 Redis。""" - -local_topic.label: -"""本地 Topic""" - -} diff --git a/rel/i18n/zh/emqx_ee_connector_cassa.hocon b/rel/i18n/zh/emqx_ee_connector_cassa.hocon deleted file mode 100644 index ffbadc7de..000000000 --- a/rel/i18n/zh/emqx_ee_connector_cassa.hocon +++ /dev/null @@ -1,17 +0,0 @@ -emqx_ee_connector_cassa { - -keyspace.desc: -"""要连接到的 Keyspace 名称。""" - -keyspace.label: -"""Keyspace""" - -servers.desc: -"""将要连接的 IPv4 或 IPv6 地址,或者主机名。
-主机名具有以下形式:`Host[:Port][,Host2:Port]`。
-如果未指定 `[:Port]`,则使用 Cassandra 默认端口 9042。""" - -servers.label: -"""Servers""" - -} diff --git a/rel/i18n/zh/emqx_ee_connector_hstreamdb.hocon b/rel/i18n/zh/emqx_ee_connector_hstreamdb.hocon deleted file mode 100644 index d9d8fb3ed..000000000 --- a/rel/i18n/zh/emqx_ee_connector_hstreamdb.hocon +++ /dev/null @@ -1,45 +0,0 @@ -emqx_ee_connector_hstreamdb { - -config.desc: -"""HStreamDB 连接配置。""" - -config.label: -"""连接配置""" - -name.desc: -"""连接器名称,人类可读的连接器描述。""" - -name.label: -"""连接器名称""" - -ordering_key.desc: -"""HStreamDB 分区键""" - -ordering_key.label: -"""HStreamDB 分区键""" - -pool_size.desc: -"""HStreamDB 连接池大小""" - -pool_size.label: -"""HStreamDB 连接池大小""" - -stream_name.desc: -"""HStreamDB 流名称""" - -stream_name.label: -"""HStreamDB 流名称""" - -type.desc: -"""连接器类型。""" - -type.label: -"""连接器类型""" - -url.desc: -"""HStreamDB 服务器 URL""" - -url.label: -"""HStreamDB 服务器 URL""" - -} diff --git a/rel/i18n/zh/emqx_ee_schema_registry_http_api.hocon b/rel/i18n/zh/emqx_ee_schema_registry_http_api.hocon deleted file mode 100644 index 1dab50a3f..000000000 --- a/rel/i18n/zh/emqx_ee_schema_registry_http_api.hocon +++ /dev/null @@ -1,39 +0,0 @@ -emqx_ee_schema_registry_http_api { - -desc_param_path_schema_name.desc: -"""模式名称""" - -desc_param_path_schema_name.label: -"""模式名称""" - -desc_schema_registry_api_delete.desc: -"""删除一个模式""" - -desc_schema_registry_api_delete.label: -"""删除模式""" - -desc_schema_registry_api_get.desc: -"""通过名称获取模式""" - -desc_schema_registry_api_get.label: -"""获取模式""" - -desc_schema_registry_api_list.desc: -"""列出所有注册的模式""" - -desc_schema_registry_api_list.label: -"""列表模式""" - -desc_schema_registry_api_post.desc: -"""注册一个新的模式""" - -desc_schema_registry_api_post.label: -"""注册模式""" - -desc_schema_registry_api_put.desc: -"""更新一个现有的模式""" - -desc_schema_registry_api_put.label: -"""更新模式""" - -} diff --git a/rel/i18n/zh/emqx_ee_schema_registry_schema.hocon b/rel/i18n/zh/emqx_ee_schema_registry_schema.hocon deleted file mode 100644 index 2f4c972ec..000000000 --- a/rel/i18n/zh/emqx_ee_schema_registry_schema.hocon +++ /dev/null @@ -1,51 +0,0 @@ -emqx_ee_schema_registry_schema { - -avro_type.desc: -"""[阿帕奇-阿夫罗](https://avro.apache.org/) 序列化格式。""" - -avro_type.label: -"""阿帕奇-阿夫罗""" - -protobuf_type.desc: -"""[协议缓冲器](https://protobuf.dev/) 序列化格式。""" - -protobuf_type.label: -"""协议缓冲器""" - -schema_description.desc: -"""对该模式的描述。""" - -schema_description.label: -"""模式描述""" - -schema_name.desc: -"""模式的一个名称,将作为其标识符。""" - -schema_name.label: -"""模式名称""" - -schema_registry_root.desc: -"""模式注册表的配置。""" - -schema_registry_root.label: -"""模式注册表""" - -schema_registry_schemas.desc: -"""注册的模式。""" - -schema_registry_schemas.label: -"""注册的模式""" - -schema_source.desc: -"""模式的源文本。""" - -schema_source.label: -"""模式来源""" - -schema_type.desc: -"""模式类型。""" - -schema_type.label: -"""模式类型""" - -} diff --git a/rel/i18n/zh/emqx_eviction_agent_api.hocon b/rel/i18n/zh/emqx_eviction_agent_api.hocon deleted file mode 100644 index a4d9f5c12..000000000 --- a/rel/i18n/zh/emqx_eviction_agent_api.hocon +++ /dev/null @@ -1,9 +0,0 @@ -emqx_eviction_agent_api { - -node_eviction_status_get.desc: -"""获取节点驱逐状态""" - -node_eviction_status_get.label: -"""节点驱逐状态""" - -} diff --git a/rel/i18n/zh/emqx_exhook_api.hocon b/rel/i18n/zh/emqx_exhook_api.hocon deleted file mode 100644 index aba0e3e58..000000000 --- a/rel/i18n/zh/emqx_exhook_api.hocon +++ /dev/null @@ -1,81 +0,0 @@ -emqx_exhook_api { - -add_server.desc: -"""添加 ExHook 服务器""" - -delete_server.desc: -"""删除 Exhook 服务器""" - -get_detail.desc: -"""查看 Exhook 服务器详细信息""" - -get_hooks.desc: -"""获取 Exhook 服务器的钩子信息""" - -hook_metrics.desc: -"""当前节点中该钩子的指标信息""" - -hook_name.desc: -"""钩子的名称""" - -hook_params.desc: -"""钩子注册时使用的参数""" - -list_all_servers.desc: -"""查看ExHook 服务器列表""" - -metric_failed.desc: -"""钩子执行失败的次数""" - -metric_max_rate.desc: -"""钩子的最大调用速率""" - -metric_rate.desc: -"""钩子的调用速率""" - -metric_succeed.desc: -"""钩子执行成功的次数""" - -metrics.desc: -"""指标信息""" - -move_api.desc: -"""移动 Exhook 服务器顺序。 -注意: 移动的参数只能是:front | rear | before:{name} | after:{name}""" - -move_api.label: -"""改变已注册的Exhook服务器的执行顺序""" - -move_position.desc: -"""移动的方向""" - -node.desc: -"""节点名称""" - -node_hook_metrics.desc: -"""所有节点中该钩子的指标信息""" - -node_metrics.desc: -"""所有节点中该服务器的指标信息""" - -node_status.desc: -"""所有节点中该服务器的状态信息""" - -server_metrics.desc: -"""当前节点中该服务器的指标信息""" - -server_name.desc: -"""Exhook 服务器的名称""" - -status.desc: -"""Exhook 服务器的状态。
-connected: 连接成功
-connecting: 连接失败,重连中
-disconnected: 连接失败,且未设置自动重连
-disabled: 该服务器未开启
-error: 查看该服务器状态时发生错误""" - -update_server.desc: -"""更新 Exhook 服务器""" - -} diff --git a/rel/i18n/zh/emqx_exhook_schema.hocon b/rel/i18n/zh/emqx_exhook_schema.hocon deleted file mode 100644 index 54d86169b..000000000 --- a/rel/i18n/zh/emqx_exhook_schema.hocon +++ /dev/null @@ -1,43 +0,0 @@ -emqx_exhook_schema { - -auto_reconnect.desc: -"""自动重连到 gRPC 服务器的设置。 -当 gRPC 服务器不可用时,Exhook 将会按照这里设置的间隔时间进行重连,并重新初始化注册的钩子""" - -enable.desc: -"""开启这个 Exhook 服务器""" - -failed_action.desc: -"""当 gRPC 请求失败后的操作""" - -keepalive.desc: -"""当没有其他数据交换时,是否向连接的对端套接字定期的发送探测包。如果另一端没有响应,则认为连接断开,并向控制进程发送错误消息""" - -name.desc: -"""ExHook 服务器名称""" - -nodelay.desc: -"""如果为 true,则为套接字设置 TCP_NODELAY 选项,这意味着会立即发送数据包""" - -pool_size.desc: -"""gRPC 客户端进程池大小""" - -recbuf.desc: -"""套接字的最小接收缓冲区大小""" - -request_timeout.desc: -"""gRPC 服务器请求超时时间""" - -servers.desc: -"""ExHook 服务器列表""" - -sndbuf.desc: -"""套接字的最小发送缓冲区大小""" - -socket_options.desc: -"""连接套接字设置""" - -url.desc: -"""gRPC 服务器地址""" - -} diff --git a/rel/i18n/zh/emqx_exproto_schema.hocon b/rel/i18n/zh/emqx_exproto_schema.hocon deleted file mode 100644 index 794a563f3..000000000 --- a/rel/i18n/zh/emqx_exproto_schema.hocon +++ /dev/null @@ -1,26 +0,0 @@ -emqx_exproto_schema { - -exproto.desc: -"""ExProto 网关""" - -exproto_grpc_handler_address.desc: -"""对端 gRPC 服务器地址。""" - -exproto_grpc_handler_ssl.desc: -"""gRPC 客户端的 SSL 配置。""" - -exproto_grpc_server_bind.desc: -"""服务监听地址和端口。""" - -exproto_grpc_server_ssl.desc: -"""服务 SSL 配置。""" - -exproto_handler.desc: -"""配置 ExProto 网关需要请求的 ConnectionHandler 服务地址。 -该服务用于给 ExProto 提供客户端的 Socket 事件处理、字节解码、订阅消息接收等功能。""" - -exproto_server.desc: -"""配置 ExProto 网关需要启动的 ConnectionAdapter 服务。 -该服务用于提供客户端的认证、发布、订阅和数据下行等功能。""" - -} diff --git a/rel/i18n/zh/emqx_gateway_api.hocon b/rel/i18n/zh/emqx_gateway_api.hocon deleted file mode 100644 index c9ea582a3..000000000 --- a/rel/i18n/zh/emqx_gateway_api.hocon +++ /dev/null @@ -1,73 +0,0 @@ -emqx_gateway_api { - -delete_gateway.desc: -"""停用指定网关""" - -enable_gateway.desc: -"""使用配置启动某一网关。""" - -gateway_created_at.desc: -"""网关创建时间""" - -gateway_current_connections.desc: -"""当前连接数""" - -gateway_enable_in_path.desc: -"""是否开启此网关""" - -gateway_listener_id.desc: -"""监听器 ID""" - -gateway_listener_name.desc: -"""监听器名称""" - -gateway_listener_running.desc: -"""监听器运行状态""" - -gateway_listener_type.desc: -"""监听器类型""" - -gateway_listeners.desc: -"""网关监听器列表""" - -gateway_max_connections.desc: -"""最大连接数""" - -gateway_name.desc: -"""网关名称""" - -gateway_name_in_qs.desc: -"""网关名称.
-可取值为 `stomp`、`mqttsn`、`coap`、`lwm2m`、`exproto`""" - -gateway_node_status.desc: -"""网关在集群中每个节点上的状态""" - -gateway_started_at.desc: -"""网关启用时间""" - -gateway_status.desc: -"""网关启用状态""" - -gateway_status_in_qs.desc: -"""通过网关状态筛选
-可选值为 `running`、`stopped`、`unloaded`""" - -gateway_stopped_at.desc: -"""网关停用时间""" - -get_gateway.desc: -"""获取网关配置详情""" - -list_gateway.desc: -"""该接口会返回指定或所有网关的概览状态, -包括当前状态、连接数、监听器状态等。""" - -node.desc: -"""节点名称""" - -update_gateway.desc: -"""更新指定网关的基础配置、和启用的状态。
-注:认证、和监听器的配置更新需参考对应的 API 接口。""" - -} diff --git a/rel/i18n/zh/emqx_gateway_api_authn.hocon b/rel/i18n/zh/emqx_gateway_api_authn.hocon deleted file mode 100644 index 662789551..000000000 --- a/rel/i18n/zh/emqx_gateway_api_authn.hocon +++ /dev/null @@ -1,45 +0,0 @@ -emqx_gateway_api_authn { - -add_authn.desc: -"""为指定网关开启认证器实现客户端认证的功能。
-当未配置认证器或关闭认证器时,则认为允许所有客户端的连接。
-注:在网关中仅支持添加一个认证器,而不是像 MQTT 一样允许配置多个认证器构成认证链。""" - -add_user.desc: -"""添加用户(仅支持 built_in_database 类型的认证器)""" - -delete_authn.desc: -"""删除指定网关的认证器。""" - -delete_user.desc: -"""删除用户(仅支持 built_in_database 类型的认证器)""" - -get_authn.desc: -"""获取指定网关认证器的配置 -当网关或认证未启用时,返回 404。""" - -get_user.desc: -"""获取用户信息(仅支持 built_in_database 类型的认证器)""" - -import_users.desc: -"""导入用户(仅支持 built_in_database 类型的认证器)""" - -is_superuser.desc: -"""是否是超级用户""" - -like_user_id.desc: -"""使用用户 ID (username 或 clientid)模糊搜索,仅支持按子串的方式进行搜索。""" - -list_users.desc: -"""获取用户列表(仅支持 built_in_database 类型的认证器)""" - -update_authn.desc: -"""更新指定网关认证器的配置,或停用认证器。""" - -update_user.desc: -"""更新用户信息(仅支持 built_in_database 类型的认证器)""" - -user_id.desc: -"""用户 ID""" - -} diff --git a/rel/i18n/zh/emqx_gateway_api_clients.hocon b/rel/i18n/zh/emqx_gateway_api_clients.hocon deleted file mode 100644 index 5dc7ff22a..000000000 --- a/rel/i18n/zh/emqx_gateway_api_clients.hocon +++ /dev/null @@ -1,207 +0,0 @@ -emqx_gateway_api_clients { - -disconnected_at.desc: -"""客户端连接断开时间""" - -heap_size.desc: -"""进程堆内存大小,单位:字节""" - -send_oct.desc: -"""已发送字节数""" - -get_client.desc: -"""获取客户端信息""" - -param_gte_created_at.desc: -"""匹配会话创建时间大于等于指定值的客户端""" - -param_conn_state.desc: -"""匹配客户端连接状态""" - -send_pkt.desc: -"""已发送应用层协议控制报文数""" - -clean_start.desc: -"""标识客户端是否以 clean_start 的标志连接到网关""" - -inflight_cnt.desc: -"""客户端当前飞行窗口大小""" - -delete_subscription.desc: -"""为某客户端删除某订阅关系""" - -param_lte_connected_at.desc: -"""匹配连接创建时间小于等于指定值的客户端""" - -node.desc: -"""客户端连接到的节点名称""" - -awaiting_rel_cnt.desc: -"""客户端当前等待 PUBREL 确认的 PUBREC 消息的条数""" - -rap.desc: -"""Retain as Published 选项,枚举:0,1""" - -inflight_max.desc: -"""客户端允许的飞行窗口最大值""" - -param_username.desc: -"""匹配客户端 Username""" - -param_like_endpoint_name.desc: -"""子串匹配 LwM2M 客户端 Endpoint Name""" - -created_at.desc: -"""会话创建时间""" - -sub_props.desc: -"""订阅属性""" - -list_clients.desc: -"""获取指定网关的客户端列表""" - -subscriptions_cnt.desc: -"""客户端已订阅主题数""" - -mailbox_len.desc: -"""进程邮箱大小""" - -send_cnt.desc: -"""已发送 Socket 报文次数""" - -rh.desc: -"""Retain Handling 选项,枚举:0,1,2""" - -connected.desc: -"""标识客户端是否已连接到网关""" - -qos.desc: -"""QoS 等级,枚举:0,1,2""" - -ip_address.desc: -"""客户端 IP 地址""" - -param_gte_connected_at.desc: -"""匹配连接创建时间大于等于指定值的客户端""" - -awaiting_rel_max.desc: -"""客户端允许的最大 PUBREC 等待队列长度""" - -param_like_username.desc: -"""子串匹配 客户端 Username""" - -nl.desc: -"""No Local 选项,枚举:0,1""" - -param_like_clientid.desc: -"""子串匹配客户端 ID""" - -param_lte_created_at.desc: -"""匹配会话创建时间小于等于指定值的客户端""" - -topic.desc: -"""主题过滤器或主题名称""" - -proto_ver.desc: -"""客户端使用的协议版本""" - -mountpoint.desc: -"""主题固定前缀""" - -proto_name.desc: -"""客户端使用的协议名称""" - -param_lte_lifetime.desc: -"""匹配心跳时间小于等于指定值的 LwM2M 客户端""" - -port.desc: -"""客户端端口""" - -connected_at.desc: -"""客户端连接时间""" - -expiry_interval.desc: -"""会话超期时间,单位:秒""" - -username.desc: -"""客户端连接的用户名""" - -param_clean_start.desc: -"""匹配客户端 `clean_start` 标记""" - -recv_msg.desc: -"""已接收上行的消息条数""" - -list_subscriptions.desc: -"""获取某客户端的主题订阅列表""" - -recv_oct.desc: -"""已接收的字节数""" - -keepalive.desc: -"""Keepalive 时间,单位:秒""" - -param_clientid.desc: -"""匹配客户端 ID""" - -subscriptions_max.desc: -"""客户端允许订阅的最大主题数""" - -param_ip_address.desc: -"""匹配客户端 IP 地址""" - -mqueue_max.desc: -"""客户端允许的最大消息队列长度""" - -mqueue_dropped.desc: -"""由于消息队列过程,客户端消息队列丢弃消息条数""" - -subid.desc: -"""订阅ID,仅用于 Stomp 网关。用于创建订阅关系时指定订阅 ID。取值范围 1-65535。""" - -clientid.desc: -"""客户端 ID""" - -kick_client.desc: -"""踢出指定客户端""" - -is_bridge.desc: -"""标识客户端是否通过 is_bridge 标志连接""" - -lifetime.desc: -"""LwM2M 客户端心跳周期""" - -send_msg.desc: -"""已发送下行消息数条数""" - -add_subscription.desc: -"""为某客户端新增订阅关系""" - -param_endpoint_name.desc: -"""匹配 LwM2M 客户端 Endpoint Name""" - -param_node.desc: -"""匹配客户端的节点名称""" - -recv_cnt.desc: -"""已接收 Socket 报文次数""" - -recv_pkt.desc: -"""已接收应用层协议控制报文数""" - -endpoint_name.desc: -"""LwM2M 客户端 Endpoint Name""" - -param_proto_ver.desc: -"""匹配客户端协议版本""" - -reductions.desc: -"""进程已消耗 Reduction 数""" - -param_gte_lifetime.desc: -"""匹配心跳时间大于等于指定值的 LwM2M 客户端""" - -mqueue_len.desc: -"""客户端当前消息队列长度""" - -} diff --git a/rel/i18n/zh/emqx_gateway_api_listeners.hocon b/rel/i18n/zh/emqx_gateway_api_listeners.hocon deleted file mode 100644 index 731b14b74..000000000 --- a/rel/i18n/zh/emqx_gateway_api_listeners.hocon +++ /dev/null @@ -1,65 +0,0 @@ -emqx_gateway_api_listeners { - -add_listener.desc: -"""为指定网关添加监听器。
-注:对于某网关不支持的监听器类型,该接口会返回 `400: BAD_REQUEST`。""" - -add_listener_authn.desc: -"""为指定监听器开启认证器以实现客户端认证的能力。
-当某一监听器开启认证后,所有连接到该监听器的客户端会使用该认证器进行认证。""" - -add_user.desc: -"""添加用户(仅支持 built_in_database 类型的认证器)""" - -current_connections.desc: -"""当前连接数""" - -delete_listener.desc: -"""删除指定监听器。被删除的监听器下所有已连接的客户端都会离线。""" - -delete_listener_authn.desc: -"""移除指定监听器的认证器。""" - -delete_user.desc: -"""删除用户(仅支持 built_in_database 类型的认证器)""" - -get_listener.desc: -"""获取指定网关监听器的配置。""" - -get_listener_authn.desc: -"""获取监听器的认证器配置。""" - -get_user.desc: -"""获取用户信息(仅支持 built_in_database 类型的认证器)""" - -import_users.desc: -"""导入用户(仅支持 built_in_database 类型的认证器)""" - -list_listeners.desc: -"""获取网关监听器列表。该接口会返回监听器所有的配置(包括该监听器上的认证器),同时也会返回该监听器在集群中运行的状态。""" - -list_users.desc: -"""获取用户列表(仅支持 built_in_database 类型的认证器)""" - -listener_id.desc: -"""监听器 ID""" - -listener_node_status.desc: -"""监听器在集群中每个节点上的状态""" - -listener_status.desc: -"""监听器状态""" - -node.desc: -"""节点名称""" - -update_listener.desc: -"""更新某网关监听器的配置。被更新的监听器会执行重启,所有已连接到该监听器上的客户端都会被断开。""" - -update_listener_authn.desc: -"""更新指定监听器的认证器配置,或停用/启用该认证器。""" - -update_user.desc: -"""更新用户信息(仅支持 built_in_database 类型的认证器)""" - -} diff --git a/rel/i18n/zh/emqx_gateway_schema.hocon b/rel/i18n/zh/emqx_gateway_schema.hocon deleted file mode 100644 index 40cee4efb..000000000 --- a/rel/i18n/zh/emqx_gateway_schema.hocon +++ /dev/null @@ -1,112 +0,0 @@ -emqx_gateway_schema { - -dtls_listener_acceptors.desc: -"""Acceptor 进程池大小。""" - -dtls_listener_dtls_opts.desc: -"""DTLS Socket 配置""" - -gateway_common_authentication.desc: -"""网关的认证器配置,对该网关下所以的监听器生效。如果每个监听器需要配置不同的认证器,需要配置监听器下的 authentication 字段。""" - -gateway_common_clientinfo_override.desc: -"""ClientInfo 重写。""" - -gateway_common_clientinfo_override_clientid.desc: -"""clientid 重写模板""" - -gateway_common_clientinfo_override_password.desc: -"""password 重写模板""" - -gateway_common_clientinfo_override_username.desc: -"""username 重写模板""" - -gateway_common_enable.desc: -"""是否启用该网关""" - -gateway_common_enable_stats.desc: -"""是否开启客户端统计""" - -gateway_common_idle_timeout.desc: -"""客户端连接过程的空闲时间。该配置用于: - 1. 一个新创建的客户端进程如果在该时间间隔内没有收到任何客户端请求,将被直接关闭。 - 2. 一个正在运行的客户进程如果在这段时间后没有收到任何客户请求,将进入休眠状态以节省资源。""" - -gateway_common_listener_access_rules.desc: -"""配置监听器的访问控制规则。 -见:https://github.com/emqtt/esockd#allowdeny""" - -gateway_common_listener_bind.desc: -"""监听器绑定的 IP 地址或端口。""" - -gateway_common_listener_enable.desc: -"""是否启用该监听器。""" - -gateway_common_listener_enable_authn.desc: -"""配置 true (默认值)启用客户端进行身份认证。 -配置 false 时,将不对客户端做任何认证。""" - -gateway_common_listener_max_conn_rate.desc: -"""监听器支持的最大连接速率。""" - -gateway_common_listener_max_connections.desc: -"""监听器支持的最大连接数。""" - -gateway_mountpoint.desc: -"""发布或订阅时,在所有主题前增加前缀字符串。 -当消息投递给订阅者时,前缀字符串将从主题名称中删除。挂载点是用户可以用来实现不同监听器之间的消息路由隔离的一种方式。 -例如,如果客户端 A 在 `listeners.tcp.\.mountpoint` 设置为 `some_tenant` 的情况下订阅 `t`, -则客户端实际上订阅了 `some_tenant/t` 主题。 -类似地,如果另一个客户端 B(连接到与客户端 A 相同的侦听器)向主题 `t` 发送消息, -则该消息被路由到所有订阅了 `some_tenant/t` 的客户端,因此客户端 A 将收到该消息,带有 主题名称`t`。 设置为 `""` 以禁用该功能。 -挂载点字符串中可用的变量:
- - ${clientid}:clientid
- - ${username}:用户名""" - -listener_name_to_settings_map.desc: -"""从监听器名称到配置参数的映射。""" - -ssl_listener_options.desc: -"""SSL Socket 配置。""" - -tcp_listener_acceptors.desc: -"""Acceptor 进程池大小。""" - -tcp_listener_proxy_protocol.desc: -"""是否开启 Proxy Protocol V1/2。当 EMQX 集群部署在 HAProxy 或 Nginx 后需要获取客户端真实 IP 时常用到该选项。参考:https://www.haproxy.com/blog/haproxy/proxy-protocol/""" - -tcp_listener_proxy_protocol_timeout.desc: -"""接收 Proxy Protocol 报文头的超时时间。如果在超时内没有收到 Proxy Protocol 包,EMQX 将关闭 TCP 连接。""" - -tcp_listener_tcp_opts.desc: -"""TCP Socket 配置。""" - -tcp_listeners.desc: -"""配置 TCP 类型的监听器。""" - -tcp_udp_listeners.desc: -"""监听器配置。""" - -udp_listener_active_n.desc: -"""为 Socket 指定 {active, N} 选项。 -参见:https://erlang.org/doc/man/inet.html#setopts-2""" - -udp_listener_buffer.desc: -"""Socket 在用户空间的缓冲区大小。""" - -udp_listener_recbuf.desc: -"""Socket 在内核空间接收缓冲区的大小。""" - -udp_listener_reuseaddr.desc: -"""允许重用本地处于 TIME_WAIT 的端口号。""" - -udp_listener_sndbuf.desc: -"""Socket 在内核空间发送缓冲区的大小。""" - -udp_listener_udp_opts.desc: -"""UDP Socket 配置。""" - -udp_listeners.desc: -"""配置 UDP 类型的监听器。""" - -} diff --git a/rel/i18n/zh/emqx_license_http_api.hocon b/rel/i18n/zh/emqx_license_http_api.hocon deleted file mode 100644 index 4ad471684..000000000 --- a/rel/i18n/zh/emqx_license_http_api.hocon +++ /dev/null @@ -1,15 +0,0 @@ -emqx_license_http_api { - -desc_license_info_api.desc: -"""获取许可证信息""" - -desc_license_info_api.label: -"""许可证信息""" - -desc_license_key_api.desc: -"""更新一个许可证密钥""" - -desc_license_key_api.label: -"""更新许可证""" - -} diff --git a/rel/i18n/zh/emqx_license_schema.hocon b/rel/i18n/zh/emqx_license_schema.hocon deleted file mode 100644 index 0bf5256e8..000000000 --- a/rel/i18n/zh/emqx_license_schema.hocon +++ /dev/null @@ -1,29 +0,0 @@ -emqx_license_schema { - -connection_high_watermark_field.desc: -"""高水位线,连接数超过这个水位线时,系统会触发许可证连接配额使用告警""" - -connection_high_watermark_field.label: -"""连接高水位""" - -connection_low_watermark_field.desc: -"""低水位限制,低于此水位线时系统会清除连接配额使用告警""" - -connection_low_watermark_field.label: -"""连接低水位线""" - -key_field.desc: -"""许可证字符串""" - -key_field.label: -"""许可证字符串""" - -license_root.desc: -"""EMQX企业许可证。 -EMQX 自带一个默认的试用许可证,默认试用许可允许最多接入 100 个连接,签发时间是 2023年1月9日,有效期是 5 年(1825 天)。若需要在生产环境部署, -请访问 https://www.emqx.com/apply-licenses/emqx 来申请。""" - -license_root.label: -"""许可证""" - -} diff --git a/rel/i18n/zh/emqx_limiter_schema.hocon b/rel/i18n/zh/emqx_limiter_schema.hocon deleted file mode 100644 index 4f5a0ce2f..000000000 --- a/rel/i18n/zh/emqx_limiter_schema.hocon +++ /dev/null @@ -1,89 +0,0 @@ -emqx_limiter_schema { - -bucket_cfg.desc: -"""桶的配置""" - -bucket_cfg.label: -"""桶的配置""" - -burst.desc: -"""突发速率。 -突发速率允许短时间内速率超过设置的速率值,突发速率 + 速率 = 当前桶能达到的最大速率值""" - -burst.label: -"""突发速率""" - -bytes.desc: -"""流入字节率控制器。 -这个是用来控制当前节点上的数据流入的字节率,每条消息将会消耗和其二进制大小等量的令牌,当达到最大速率后,会话将会被限速甚至被强制挂起一小段时间""" - -bytes.label: -"""流入字节率""" - -client.desc: -"""对桶的每个使用者的速率控制设置""" - -client.label: -"""每个使用者的限制""" - -connection.desc: -"""连接速率控制器。 -这个用来控制当前节点上的连接速率,当达到最大速率后,新的连接将会被拒绝""" - -connection.label: -"""连接速率""" - -divisible.desc: -"""申请的令牌数是否可以被分割""" - -divisible.label: -"""是否可分割""" - -failure_strategy.desc: -"""当所有的重试都失败后的处理策略""" - -failure_strategy.label: -"""失败策略""" - -initial.desc: -"""桶中的初始令牌数""" - -initial.label: -"""初始令牌数""" - -internal.desc: -"""EMQX 内部功能所用限制器。""" - -low_watermark.desc: -"""当桶中剩余的令牌数低于这个值,即使令牌申请成功了,也会被强制暂停一会儿""" - -low_watermark.label: -"""低水位线""" - -max_retry_time.desc: -"""申请失败后,尝试重新申请的时长最大值""" - -max_retry_time.label: -"""最大重试时间""" - -message_routing.desc: -"""消息派发速率控制器。 -这个用来控制当前节点内的消息派发速率,当达到最大速率后,新的推送将会被拒绝""" - -message_routing.label: -"""消息派发""" - -messages.desc: -"""流入速率控制器。 -这个用来控制当前节点上的消息流入速率,当达到最大速率后,会话将会被限速甚至被强制挂起一小段时间""" - -messages.label: -"""消息流入速率""" - -rate.desc: -"""桶的令牌生成速率""" - -rate.label: -"""速率""" - -} diff --git a/rel/i18n/zh/emqx_lwm2m_api.hocon b/rel/i18n/zh/emqx_lwm2m_api.hocon deleted file mode 100644 index 7cf53060a..000000000 --- a/rel/i18n/zh/emqx_lwm2m_api.hocon +++ /dev/null @@ -1,27 +0,0 @@ -emqx_lwm2m_api { - -dataType.desc: -"""数据类型""" - -lookup_resource.desc: -"""查看指定资源状态""" - -name.desc: -"""资源名称""" - -observe_resource.desc: -"""Observe/Un-Observe 指定资源""" - -operations.desc: -"""资源可用操作列表""" - -path.desc: -"""资源路径""" - -read_resource.desc: -"""发送读指令到某资源""" - -write_resource.desc: -"""发送写指令到某资源""" - -} diff --git a/rel/i18n/zh/emqx_lwm2m_schema.hocon b/rel/i18n/zh/emqx_lwm2m_schema.hocon deleted file mode 100644 index 3dea6a0c6..000000000 --- a/rel/i18n/zh/emqx_lwm2m_schema.hocon +++ /dev/null @@ -1,56 +0,0 @@ -emqx_lwm2m_schema { - -lwm2m.desc: -"""LwM2M 网关配置。仅支持 v1.0.1 协议。""" - -lwm2m_auto_observe.desc: -"""自动 Observe REGISTER 数据包的 Object 列表。""" - -lwm2m_lifetime_max.desc: -"""允许 LwM2M 客户端允许设置的心跳最大值。""" - -lwm2m_lifetime_min.desc: -"""允许 LwM2M 客户端允许设置的心跳最小值。""" - -lwm2m_qmode_time_window.desc: -"""在QMode模式下,LwM2M网关认为网络链接有效的时间窗口的值。 -例如,在收到客户端的更新信息后,在这个时间窗口内的任何信息都会直接发送到LwM2M客户端,而超过这个时间窗口的所有信息都会暂时储存在内存中。""" - -lwm2m_translators.desc: -"""LwM2M 网关订阅/发布消息的主题映射配置。""" - -lwm2m_translators_command.desc: -"""下行命令主题。 -对于每个成功上线的新 LwM2M 客户端,网关会创建一个订阅关系来接收下行消息并将其发送给客户端。""" - -lwm2m_translators_notify.desc: -"""用于发布来自 LwM2M 客户端的通知事件的主题。 -在成功 Observe 到 LwM2M 客户端的资源后,如果客户端报告任何资源状态的变化,网关将通过该主题发送通知事件。""" - -lwm2m_translators_register.desc: -"""用于发布来自 LwM2M 客户端的注册事件的主题。""" - -lwm2m_translators_response.desc: -"""用于网关发布来自 LwM2M 客户端的确认事件的主题。""" - -lwm2m_translators_update.desc: -"""用于发布来自LwM2M客户端的更新事件的主题。""" - -lwm2m_update_msg_publish_condition.desc: -"""发布UPDATE事件消息的策略。
- - always: 只要收到 UPDATE 请求,就发送更新事件。
- - contains_object_list: 仅当 UPDATE 请求携带 Object 列表时才发送更新事件。""" - -lwm2m_xml_dir.desc: -"""LwM2M Resource 定义的 XML 文件目录路径。""" - -translator.desc: -"""配置某网关客户端对于发布消息或订阅的主题和 QoS 等级。""" - -translator_qos.desc: -"""QoS 等级""" - -translator_topic.desc: -"""主题名称""" - -} diff --git a/rel/i18n/zh/emqx_mgmt_api_alarms.hocon b/rel/i18n/zh/emqx_mgmt_api_alarms.hocon deleted file mode 100644 index d9dafd867..000000000 --- a/rel/i18n/zh/emqx_mgmt_api_alarms.hocon +++ /dev/null @@ -1,37 +0,0 @@ -emqx_mgmt_api_alarms { - -activate_at.desc: -"""告警开始时间,使用 rfc3339 标准时间格式。""" - -deactivate_at.desc: -"""告警结束时间,使用 rfc3339 标准时间格式。""" - -delete_alarms_api.desc: -"""删除所有历史告警。""" - -delete_alarms_api_response204.desc: -"""历史告警已成功清除。""" - -details.desc: -"""告警详情,提供了更多的告警信息,主要提供给程序处理。""" - -duration.desc: -"""表明告警已经持续了多久,单位:毫秒。""" - -get_alarms_qs_activated.desc: -"""用于指定查询的告警类型, -为 true 时返回当前激活的告警,为 false 时返回历史告警,默认为 false。""" - -list_alarms_api.desc: -"""列出当前激活的告警或历史告警,由查询参数决定。""" - -message.desc: -"""告警消息,以人类可读的方式描述告警内容。""" - -name.desc: -"""告警名称,用于区分不同的告警。""" - -node.desc: -"""触发此告警的节点名称。""" - -} diff --git a/rel/i18n/zh/emqx_mgmt_api_banned.hocon b/rel/i18n/zh/emqx_mgmt_api_banned.hocon deleted file mode 100644 index cee3ba288..000000000 --- a/rel/i18n/zh/emqx_mgmt_api_banned.hocon +++ /dev/null @@ -1,54 +0,0 @@ -emqx_mgmt_api_banned { - -as.desc: -"""封禁方式,可以通过客户端 ID、用户名或者 IP 地址等方式进行封禁。""" - -as.label: -"""封禁方式""" - -at.desc: -"""封禁的起始时间,格式为 rfc3339,默认为发起操作的时间。""" - -at.label: -"""封禁时间""" - -by.desc: -"""封禁的发起者。""" - -by.label: -"""封禁发起者""" - -create_banned_api.desc: -"""添加一个客户端 ID、用户名或者 IP 地址到黑名单。""" - -create_banned_api_response400.desc: -"""错误的请求,可能是参数错误或封禁对象已存在等原因。""" - -delete_banned_api.desc: -"""将一个客户端 ID、用户名或者 IP 地址从黑名单中删除。""" - -delete_banned_api_response404.desc: -"""未在黑名单中找到该封禁对象。""" - -list_banned_api.desc: -"""列出目前所有被封禁的客户端 ID、用户名和 IP 地址。""" - -reason.desc: -"""封禁原因,记录当前对象被封禁的原因。""" - -reason.label: -"""封禁原因""" - -until.desc: -"""封禁的结束时间,格式为 rfc3339,默认值为发起操作的时间 + 1 年。""" - -until.label: -"""封禁结束时间""" - -who.desc: -"""封禁对象,具体的客户端 ID、用户名或者 IP 地址。""" - -who.label: -"""封禁对象""" - -} diff --git a/rel/i18n/zh/emqx_mgmt_api_key_schema.hocon b/rel/i18n/zh/emqx_mgmt_api_key_schema.hocon deleted file mode 100644 index 9a0536fe6..000000000 --- a/rel/i18n/zh/emqx_mgmt_api_key_schema.hocon +++ /dev/null @@ -1,19 +0,0 @@ -emqx_mgmt_api_key_schema { - -api_key.desc: -"""API 密钥, 可用于请求除管理 API 密钥及 Dashboard 用户管理 API 的其它接口""" - -api_key.label: -"""API 密钥""" - -bootstrap_file.desc: -"""用于在启动 emqx 时,添加 API 密钥,其格式为: - ``` - 7e729ae70d23144b:2QILI9AcQ9BYlVqLDHQNWN2saIjBV4egr1CZneTNKr9CpK - ec3907f865805db0:Ee3taYltUKtoBVD9C3XjQl9C6NXheip8Z9B69BpUv5JxVHL - ```""" - -bootstrap_file.label: -"""API 密钥初始化文件""" - -} diff --git a/rel/i18n/zh/emqx_mgmt_api_publish.hocon b/rel/i18n/zh/emqx_mgmt_api_publish.hocon deleted file mode 100644 index 2a532fbdd..000000000 --- a/rel/i18n/zh/emqx_mgmt_api_publish.hocon +++ /dev/null @@ -1,81 +0,0 @@ -emqx_mgmt_api_publish { - -error_message.desc: -"""失败的详细原因。""" - -message_id.desc: -"""全局唯一的一个消息 ID,方便用于关联和追踪。""" - -message_properties.desc: -"""PUBLISH 消息里的 Property 字段。""" - -msg_content_type.desc: -"""内容类型标识符,以 UTF-8 格式编码的字符串,用来描述应用消息的内容,服务端必须把收到的应用消息中的内容类型原封不动的发送给所有的订阅者。""" - -msg_correlation_data.desc: -"""对比数据标识符,服务端在收到应用消息时必须原封不动的把对比数据发送给所有的订阅者。对比数据只对请求消息(Request Message)的发送端和响应消息(Response Message)的接收端有意义。""" - -msg_message_expiry_interval.desc: -"""消息过期间隔标识符,以秒为单位。当消失已经过期时,如果服务端还没有开始向匹配的订阅者投递该消息,则服务端会删除该订阅者的消息副本。如果不设置,则消息永远不会过期""" - -msg_payload_format_indicator.desc: -"""载荷格式指示标识符,0 表示载荷是未指定格式的数据,相当于没有发送载荷格式指示;1 表示载荷是 UTF-8 编码的字符数据,载荷中的 UTF-8 数据必须是按照 Unicode 的规范和 RFC 3629 的标准要求进行编码的。""" - -msg_response_topic.desc: -"""响应主题标识符, UTF-8 编码的字符串,用作响应消息的主题名。响应主题不能包含通配符,也不能包含多个主题,否则将造成协议错误。当存在响应主题时,消息将被视作请求报文。服务端在收到应用消息时必须将响应主题原封不动的发送给所有的订阅者。""" - -msg_user_properties.desc: -"""指定 MQTT 消息的 User Property 键值对。注意,如果出现重复的键,只有最后一个会保留。""" - -payload.desc: -"""MQTT 消息体。""" - -payload_encoding.desc: -"""MQTT 消息体的编码方式,可以是 base64plain。当设置为 base64 时,消息在发布前会先被解码。""" - -publish_api.desc: -"""发布一个消息。
-可能的 HTTP 状态码如下:
-200: 消息被成功发送到至少一个订阅。
-202: 没有匹配到任何订阅。
-400: 消息编码错误,如非法主题,或 QoS 超出范围等。
-503: 服务重启等过程中导致转发失败。""" - -publish_api.label: -"""发布一条信息""" - -publish_bulk_api.desc: -"""批量发布一组消息。
-可能的 HTTP 状态码如下:
-200: 所有的消息都被成功发送到至少一个订阅。
-202: 至少有一个消息没有匹配到任何订阅。
-400: 至少有一个消息编码错误,如非法主题,或 QoS 超出范围等。
-503: 至少有一个小因为服务重启的原因导致转发失败。
- -请求的 Body 或者 Body 中包含的某个消息无法通过 API 规范的类型检查时,HTTP 响应的消息与发布单个消息的 API - /publish 是一样的。 -如果所有的消息都是合法的,那么 HTTP 返回的内容是一个 JSON 数组,每个元素代表了该消息转发的状态。""" - -publish_bulk_api.label: -"""发布一批信息""" - -qos.desc: -"""MQTT 消息的 QoS""" - -reason_code.desc: -"""MQTT 消息发布的错误码,这些错误码也是 MQTT 规范中 PUBACK 消息可能携带的错误码。
-当前支持如下错误码:
- -16(0x10):没能匹配到任何订阅;
-131(0x81):消息转发时发生错误,例如 EMQX 服务重启;
-144(0x90):主题名称非法;
-151(0x97):受到了速率限制,或者消息尺寸过大。全局消息大小限制可以通过配置项 mqtt.max_packet_size 来进行修改。
-注意:消息尺寸的是通过主题和消息体的字节数进行估算的。具体发布时所占用的字节数可能会稍大于这个估算的值。""" - -retain.desc: -"""布尔型字段,用于表示该消息是否保留消息。""" - -topic_name.desc: -"""主题名称""" - -} diff --git a/rel/i18n/zh/emqx_mgmt_api_status.hocon b/rel/i18n/zh/emqx_mgmt_api_status.hocon deleted file mode 100644 index 3938f47c1..000000000 --- a/rel/i18n/zh/emqx_mgmt_api_status.hocon +++ /dev/null @@ -1,34 +0,0 @@ -emqx_mgmt_api_status { - -get_status_api.desc: -"""节点的健康检查。 返回节点状态的描述信息。 - -如果 EMQX 应用程序已经启动并运行,返回状态代码 200,否则返回 503。 - -这个API是在v5.0.10中引入的。 -GET `/status`端点(没有`/api/...`前缀)也是这个端点的一个别名,工作方式相同。 这个别名从v5.0.0开始就有了。 -自 v5.0.25 和 e5.0.4 开始,可以通过指定 'format' 参数来得到 JSON 格式的信息。""" - -get_status_api.label: -"""服务健康检查""" - -get_status_response200.desc: -"""如果 'format' 参数为 'json',则返回如下JSON:
-{ - "rel_vsn": "v5.0.23", - "node_name": "emqx@127.0.0.1", - "broker_status": "started", - "app_status": "running" -} -
-否则返回2行自由格式的文本,第一行描述节点的状态,第二行描述 EMQX 应用运行状态。例如:
-Node emqx@127.0.0.1 is started -emqx is running""" - -get_status_response503.desc: -"""如果 EMQX 应用暂时没有启动,或正在重启,则可能返回 'emqx is not_running'""" - -get_status_api_format.desc: -"""指定返回的内容格式。使用 'text'(默认)则返回自由格式的字符串; 'json' 则返回 JSON 格式。""" - -} diff --git a/rel/i18n/zh/emqx_modules_schema.hocon b/rel/i18n/zh/emqx_modules_schema.hocon deleted file mode 100644 index e1c2ca913..000000000 --- a/rel/i18n/zh/emqx_modules_schema.hocon +++ /dev/null @@ -1,45 +0,0 @@ -emqx_modules_schema { - -enable.desc: -"""是否开启该功能""" - -max_delayed_messages.desc: -"""延迟消息的数量上限(0 代表无限)""" - -rewrite.desc: -"""EMQX 的主题重写功能支持根据用户配置的规则在客户端订阅主题、发布消息、取消订阅的时候将 A 主题重写为 B 主题。 -重写规则分为 Pub 规则和 Sub 规则,Pub 规则匹配 PUSHLISH 报文携带的主题,Sub 规则匹配 SUBSCRIBE、UNSUBSCRIBE 报文携带的主题。 -每条重写规则都由主题过滤器、正则表达式、目标表达式三部分组成。 -在主题重写功能开启的前提下,EMQX 在收到诸如 PUBLISH 报文等带有主题的 MQTT 报文时,将使用报文中的主题去依次匹配配置文件中规则的主题过滤器部分,一旦成功匹配,则使用正则表达式提取主题中的信息,然后替换至目标表达式以构成新的主题。 -目标表达式中可以使用 `$N` 这种格式的变量匹配正则表达中提取出来的元素,`$N` 的值为正则表达式中提取出来的第 N 个元素,比如 `$1` 即为正则表达式提取的第一个元素。 -需要注意的是,EMQX 使用倒序读取配置文件中的重写规则,当一条主题可以同时匹配多条主题重写规则的主题过滤器时,EMQX 仅会使用它匹配到的第一条规则进行重写,如果该条规则中的正则表达式与 MQTT 报文主题不匹配,则重写失败,不会再尝试使用其他的规则进行重写。 -因此用户在使用时需要谨慎的设计 MQTT 报文主题以及主题重写规则。""" - -rewrite.label: -"""主题重写""" - -tr_action.desc: -"""主题重写在哪种操作上生效: - - `subscribe`:订阅时重写主题; - - `publish`:发布时重写主题; - -`all`:全部重写主题""" - -tr_action.label: -"""Action""" - -tr_dest_topic.desc: -"""目标主题。""" - -tr_dest_topic.label: -"""目标主题""" - -tr_re.desc: -"""正则表达式""" - -tr_source_topic.desc: -"""源主题,客户端业务指定的主题""" - -tr_source_topic.label: -"""源主题""" - -} diff --git a/rel/i18n/zh/emqx_mqttsn_schema.hocon b/rel/i18n/zh/emqx_mqttsn_schema.hocon deleted file mode 100644 index c6d3a98a6..000000000 --- a/rel/i18n/zh/emqx_mqttsn_schema.hocon +++ /dev/null @@ -1,30 +0,0 @@ -emqx_mqttsn_schema { - -mqttsn.desc: -"""MQTT-SN 网关配置。当前实现仅支持 v1.2 版本""" - -mqttsn_broadcast.desc: -"""是否周期性广播 ADVERTISE 消息""" - -mqttsn_enable_qos3.desc: -"""是否允许无连接的客户端发送 QoS 等于 -1 的消息。 -该功能主要用于支持轻量的 MQTT-SN 客户端实现,它不会向网关建立连接,注册主题,也不会发起订阅;它只使用 QoS 为 -1 来发布消息""" - -mqttsn_gateway_id.desc: -"""MQTT-SN 网关 ID。 -当 broadcast 打开时,MQTT-SN 网关会使用该 ID 来广播 ADVERTISE 消息""" - -mqttsn_predefined.desc: -"""预定义主题列表。 -预定义的主题列表,是一组 主题 ID 和 主题名称 的映射关系。使用预先定义的主题列表,可以减少 MQTT-SN 客户端和网关对于固定主题的注册请求""" - -mqttsn_predefined_id.desc: -"""主题 ID。范围:1-65535""" - -mqttsn_predefined_topic.desc: -"""主题名称。注:不支持通配符""" - -mqttsn_subs_resume.desc: -"""在会话被重用后,网关是否主动向客户端注册对已订阅主题名称""" - -} diff --git a/rel/i18n/zh/emqx_node_rebalance_api.hocon b/rel/i18n/zh/emqx_node_rebalance_api.hocon deleted file mode 100644 index 5f6753aff..000000000 --- a/rel/i18n/zh/emqx_node_rebalance_api.hocon +++ /dev/null @@ -1,266 +0,0 @@ -emqx_node_rebalance_api { - -load_rebalance_status.desc: -"""获取当前节点的重平衡状态""" - -load_rebalance_status.label: -"""获取重平衡状态""" - -load_rebalance_global_status.desc: -"""获取集群中所有重平衡/疏散任务的状态""" - -load_rebalance_global_status.label: -"""获取全局重平衡状态""" - -load_rebalance_availability_check.desc: -"""检查节点是否正在被执行重平衡或疏散""" - -load_rebalance_availability_check.label: -"""可用性检查""" - -load_rebalance_start.desc: -"""启动重平衡任务""" - -load_rebalance_start.label: -"""启动重平衡""" - -load_rebalance_stop.desc: -"""停止重平衡任务""" - -load_rebalance_stop.label: -"""停止重平衡""" - -load_rebalance_evacuation_start.desc: -"""启动疏散任务""" - -load_rebalance_evacuation_start.label: -"""启动疏散""" - -load_rebalance_evacuation_stop.desc: -"""停止疏散任务""" - -load_rebalance_evacuation_stop.label: -"""停止疏散""" - -param_node.desc: -"""节点名称""" - -param_node.label: -"""节点名称""" - -wait_health_check.desc: -"""启动重平衡任务前等待的时间,单位为秒""" - -wait_health_check.label: -"""等待健康检查""" - -conn_evict_rate.desc: -"""每秒迁出连接数""" - -conn_evict_rate.label: -"""迁出速率""" - -sess_evict_rate.desc: -"""每秒迁出会话数""" - -sess_evict_rate.label: -"""会话迁出速率""" - -abs_conn_threshold.desc: -"""当前节点上的连接数与迁入节点上的平均连接数的差值(绝对值)上限,低于该差值时停止迁移连接。""" - -abs_conn_threshold.label: -"""连接数差值""" - -rel_conn_threshold.desc: -"""当前节点上的连接数与迁入节点上的平均连接数的比值上限,低于该比值时停止迁移连接。""" - -rel_conn_threshold.label: -"""连接数比值""" - -abs_sess_threshold.desc: -"""当前节点上的会话数与迁入节点上的平均会话数之间的差值(绝对值)上限,低于该差值时停止迁移会话。""" - -abs_sess_threshold.label: -"""会话数差值""" - -rel_sess_threshold.desc: -"""当前节点上的会话数与迁入节点上的平均会话数的比值上限,低于该比值时停止迁移会话。""" - -rel_sess_threshold.label: -"""会话数比值""" - -wait_takeover.desc: -"""开始会话疏散任务之前的等待时间,以秒为单位""" - -wait_takeover.label: -"""等待接管""" - -redirect_to.desc: -"""将客户端重定向到的服务器参考(MQTTv5 服务器重定向)""" - -redirect_to.label: -"""重定向至""" - -migrate_to.desc: -"""接受会话迁入的节点""" - -migrate_to.label: -"""迁入节点""" - -rebalance_nodes.desc: -"""参与重平衡的节点""" - -rebalance_nodes.label: -"""重新平衡节点""" - -local_status_enabled.desc: -"""节点是否正在执行重平衡疏散任务""" - -local_status_enabled.label: -"""运行状态""" - -local_status_process.desc: -"""正在节点上执行的任务:'evacuation' 或 'rebalance'""" - -local_status_process.label: -"""节点任务""" - -local_status_state.desc: -"""正在节点上执行的任务的状态""" - -local_status_state.label: -"""重新平衡/疏散当前状态""" - -local_status_coordinator_node.desc: -"""协调分配重平衡任务的节点""" - -local_status_coordinator_node.label: -"""协调节点""" - -local_status_connection_eviction_rate.desc: -"""每秒迁出的连接数""" - -local_status_connection_eviction_rate.label: -"""连接迁出速率""" - -local_status_session_eviction_rate.desc: -"""每秒迁出的会话数""" - -local_status_session_eviction_rate.label: -"""会话迁出速率""" - -local_status_connection_goal.desc: -"""节点在重新平衡/疏散任务完成后预期拥有的连接数""" - -local_status_connection_goal.label: -"""连接数目标""" - -local_status_session_goal.desc: -"""疏散任务完成后节点预期的会话数""" - -local_status_session_goal.label: -"""会话数目标""" - -local_status_disconnected_session_goal.desc: -"""重新平衡任务完成后节点预期的无连接的会话数""" - -local_status_disconnected_session_goal.label: -"""预期无连接会话数""" - -local_status_session_recipients.desc: -"""会话被迁入的节点列表""" - -local_status_session_recipients.label: -"""会话迁入节点""" - -local_status_recipients.desc: -"""在重新平衡期间接受连接/会话迁入的节点列表""" - -local_status_recipients.label: -"""接受迁入节点""" - -local_status_stats.desc: -"""疏散/重平衡的统计""" - -local_status_stats.label: -"""统计数据""" - -status_stats_initial_connected.desc: -"""疏散/重新平衡任务开始之前节点上的连接数""" - -status_stats_initial_connected.label: -"""初始连接""" - -status_stats_current_connected.desc: -"""节点上的当前连接数""" - -status_stats_current_connected.label: -"""当前连接""" - -status_stats_initial_sessions.desc: -"""疏散/重新平衡任务开始之前节点上的会话数""" - -status_stats_initial_sessions.label: -"""初始会话""" - -status_stats_current_sessions.desc: -"""节点上的当前会话数""" - -status_stats_current_sessions.label: -"""当前会话""" - -status_stats_current_disconnected_sessions.desc: -"""节点上当前无连接的会话数""" - -status_stats_current_disconnected_sessions.label: -"""当前无连接会话""" - -coordinator_status_donors.desc: -"""正在迁出连接/会话的节点列表""" - -coordinator_status_donors.label: -"""迁出节点""" - -coordinator_status_donor_conn_avg.desc: -"""每个迁出节点的平均连接数""" - -coordinator_status_donor_conn_avg.label: -"""迁出节点连接平均值""" - -coordinator_status_donor_sess_avg.desc: -"""每个迁出节点的平均会话数""" - -coordinator_status_donor_sess_avg.label: -"""迁出节点会话平均数""" - -coordinator_status_node.desc: -"""协调分配疏散/重平衡任务的节点""" - -coordinator_status_node.label: -"""协调节点""" - -evacuation_status_node.desc: -"""正在迁出的节点""" - -evacuation_status_node.label: -"""疏散节点""" - -global_status_evacuations.desc: -"""正在迁出的节点列表""" - -global_status_evacuations.label: -"""疏散""" - -global_status_rebalances.desc: -"""协调重平衡的节点列表""" - -global_status_rebalances.label: -"""重平衡""" - -empty_response.desc: -"""响应为空""" - -empty_response.label: -"""空响应""" -} diff --git a/rel/i18n/zh/emqx_oracle.hocon b/rel/i18n/zh/emqx_oracle.hocon deleted file mode 100644 index 06840f201..000000000 --- a/rel/i18n/zh/emqx_oracle.hocon +++ /dev/null @@ -1,20 +0,0 @@ -emqx_oracle { - - server { - desc = "将要连接的 IPv4 或 IPv6 地址,或者主机名。
" - "主机名具有以下形式:`Host[:Port]`。
" - "如果未指定 `[:Port]`,则使用 Oracle Database 默认端口 1521。" - label = "服务器地址" - } - - sid { - desc = "Oracle Database Sid 名称" - label = "Oracle Database Sid" - } - - service_name { - desc = "Oracle Database 服务名称。" - label = "Oracle Database 服务名称" - } - -} diff --git a/rel/i18n/zh/emqx_plugins_schema.hocon b/rel/i18n/zh/emqx_plugins_schema.hocon deleted file mode 100644 index 4da5c5a7b..000000000 --- a/rel/i18n/zh/emqx_plugins_schema.hocon +++ /dev/null @@ -1,46 +0,0 @@ -emqx_plugins_schema { - -check_interval.desc: -"""检查间隔:检查集群中插件的状态是否一致,
-如果连续3次检查结果不一致,则报警。""" - -enable.desc: -"""设置为“true”以启用此插件""" - -enable.label: -"""启用""" - -install_dir.desc: -"""插件安装包的目录,出于安全考虑,该目录应该值允许 emqx,或用于运行 EMQX 服务的用户拥有写入权限。""" - -install_dir.label: -"""安装目录""" - -name_vsn.desc: -"""插件的名称{name}-{version}。
-它应该与插件的发布包名称一致,如my_plugin-0.1.0。""" - -name_vsn.label: -"""名称-版本""" - -plugins.desc: -"""管理EMQX插件。
-插件可以是EMQX安装包中的一部分,也可以是一个独立的安装包。
-独立安装的插件称为“外部插件”。""" - -plugins.label: -"""插件""" - -state.desc: -"""描述插件的状态""" - -state.label: -"""插件状态""" - -states.desc: -"""一组插件的状态。插件将按照定义的顺序启动""" - -states.label: -"""插件启动顺序及状态""" - -} diff --git a/rel/i18n/zh/emqx_prometheus_schema.hocon b/rel/i18n/zh/emqx_prometheus_schema.hocon deleted file mode 100644 index a1e59e517..000000000 --- a/rel/i18n/zh/emqx_prometheus_schema.hocon +++ /dev/null @@ -1,47 +0,0 @@ -emqx_prometheus_schema { - -enable.desc: -"""开启或关闭 Prometheus 数据推送""" - -headers.desc: -"""推送到 Push Gateway 的 HTTP Headers 列表。
-例如, { Authorization = "some-authz-tokens"}""" - -interval.desc: -"""数据推送间隔""" - -job_name.desc: -"""推送到 Push Gateway 的 Job 名称。可用变量为:
-- ${name}: EMQX 节点的名称。 -- ${host}: EMQX 节点主机名。 -例如,当 EMQX 节点名为 emqx@127.0.0.1 则 name 变量的值为 emqx,host 变量的值为 127.0.0.1
-默认值为: ${name}/instance/${name}~${host}""" - -mnesia_collector.desc: -"""开启或关闭 Mnesia 采集器, 使用 mnesia:system_info/1 收集 Mnesia 相关指标""" - -prometheus.desc: -"""Prometheus 监控数据推送""" - -prometheus.label: -"""Prometheus""" - -push_gateway_server.desc: -"""Prometheus 服务器地址""" - -vm_dist_collector.desc: -"""开启或关闭 VM 分布采集器,收集 Erlang 分布机制中涉及的套接字和进程的信息。""" - -vm_memory_collector.desc: -"""开启或关闭 VM 内存采集器, 使用 erlang:memory/0 收集 Erlang 虚拟机动态分配的内存信息,同时提供基本的 (D)ETS 统计信息""" - -vm_msacc_collector.desc: -"""开启或关闭 VM msacc 采集器, 使用 erlang:statistics(microstate_accounting) 收集微状态计数指标""" - -vm_statistics_collector.desc: -"""开启或关闭 VM 统计采集器, 使用 erlang:statistics/1 收集 Erlang VM 相关指标""" - -vm_system_info_collector.desc: -"""开启或关闭 VM 系统信息采集器, 使用 erlang:system_info/1 收集 Erlang VM 相关指标""" - -} diff --git a/rel/i18n/zh/emqx_psk_schema.hocon b/rel/i18n/zh/emqx_psk_schema.hocon deleted file mode 100644 index 0fc14e730..000000000 --- a/rel/i18n/zh/emqx_psk_schema.hocon +++ /dev/null @@ -1,28 +0,0 @@ -emqx_psk_schema { - -chunk_size.desc: -"""将 PSK 文件导入到内建数据时每个块的大小""" - -enable.desc: -"""是否开启 TLS PSK 支持""" - -init_file.desc: -"""如果设置了初始化文件,EMQX 将在启动时从初始化文件中导入 PSK 信息到内建数据库中。 -这个文件需要按行进行组织,每一行必须遵守如下格式: PSKIdentity:SharedSecret -例如: mydevice1:c2VjcmV0""" - -psk_authentication.desc: -"""此配置用于启用 TLS-PSK 身份验证。 - -PSK 是 “Pre-Shared-Keys” 的缩写。 - -注意: 确保 SSL 监听器仅启用了 'tlsv1.2',并且配置了PSK 密码套件,例如 'RSA-PSK-AES256-GCM-SHA384'。 - -可以通过查看监听器中的 SSL 选项,了解更多详细信息。 - -可以通过配置 'init_file' 来设置初始化的 ID 和 密钥""" - -separator.desc: -"""PSK 文件中 PSKIdentitySharedSecret 之间的分隔符""" - -} diff --git a/rel/i18n/zh/emqx_resource_schema.hocon b/rel/i18n/zh/emqx_resource_schema.hocon deleted file mode 100644 index 9365b1026..000000000 --- a/rel/i18n/zh/emqx_resource_schema.hocon +++ /dev/null @@ -1,112 +0,0 @@ -emqx_resource_schema { - -auto_restart_interval.desc: -"""资源断开以后,自动重连的时间间隔。""" - -auto_restart_interval.label: -"""自动重连间隔""" - -batch_size.desc: -"""最大批量请求大小。如果设为1,则无批处理。""" - -batch_size.label: -"""最大批量请求大小""" - -batch_time.desc: -"""在较低消息率情况下尝试累积批量输出时的最大等待间隔,以提高资源的利用率。""" - -batch_time.label: -"""批量等待最大间隔""" - -buffer_mode.desc: -"""队列操作模式。 -memory_only: 所有的消息都缓存在内存里。volatile_offload: 先将消息缓存在内存中,当内存中的消息堆积超过一定限制(配置项 buffer_seg_bytes 指定该限制)后, 消息会开始缓存到磁盘上。""" - -buffer_mode.label: -"""缓存模式""" - -buffer_seg_bytes.desc: -"""当缓存模式是 volatile_offload 时适用。该配置用于指定缓存到磁盘上的文件的大小。""" - -buffer_seg_bytes.label: -"""缓存文件大小""" - -creation_opts.desc: -"""资源启动相关的选项。""" - -creation_opts.label: -"""资源启动选项""" - -enable_batch.desc: -"""启用批量模式。""" - -enable_batch.label: -"""启用批量模式""" - -enable_queue.desc: -"""启用磁盘缓存队列(仅对 egress 方向桥接有用)。""" - -enable_queue.label: -"""启用磁盘缓存队列""" - -health_check_interval.desc: -"""健康检查间隔。""" - -health_check_interval.label: -"""健康检查间隔""" - -inflight_window.desc: -"""请求飞行队列窗口大小。当请求模式为异步时,如果需要严格保证来自同一 MQTT 客户端的消息有序,则必须将此值设为 1。""" - -inflight_window.label: -"""请求飞行队列窗口""" - -max_buffer_bytes.desc: -"""每个缓存 worker 允许使用的最大字节数。""" - -max_buffer_bytes.label: -"""缓存队列最大长度""" - -query_mode.desc: -"""请求模式。可选 '同步/异步',默认为'异步'模式。""" - -query_mode.label: -"""请求模式""" - -request_timeout.desc: -"""从请求进入缓冲区开始计时,如果请求在规定的时间内仍停留在缓冲区内或者已发送但未能及时收到响应或确认,该请求将被视为过期。""" - -request_timeout.label: -"""请求超期""" - -resource_opts.desc: -"""资源相关的选项。""" - -resource_opts.label: -"""资源选项""" - -resume_interval.desc: -"""在发送失败后尝试重传飞行窗口中的请求的时间间隔。""" - -resume_interval.label: -"""重试时间间隔""" - -start_after_created.desc: -"""是否在创建资源后立即启动资源。""" - -start_after_created.label: -"""创建后立即启动""" - -start_timeout.desc: -"""在回复资源创建请求前等待资源进入健康状态的时间。""" - -start_timeout.label: -"""启动超时时间""" - -worker_pool_size.desc: -"""缓存队列 worker 数量。仅对 egress 类型的桥接有意义。当桥接仅有 ingress 方向时,可设置为 0,否则必须大于 0。""" - -worker_pool_size.label: -"""缓存池大小""" - -} diff --git a/rel/i18n/zh/emqx_retainer_api.hocon b/rel/i18n/zh/emqx_retainer_api.hocon deleted file mode 100644 index f8107f8ce..000000000 --- a/rel/i18n/zh/emqx_retainer_api.hocon +++ /dev/null @@ -1,63 +0,0 @@ -emqx_retainer_api { - -config_content.desc: -"""配置内容""" - -config_not_found.desc: -"""配置不存在""" - -delete_matching_api.desc: -"""删除对应的消息""" - -from_clientid.desc: -"""发布者的 ClientID""" - -from_username.desc: -"""发布者的用户名""" - -get_config_api.desc: -"""查看配置内容""" - -list_retained_api.desc: -"""查看保留消息列表""" - -lookup_api.desc: -"""通过不带通配符的主题查看对应的保留消息""" - -message_detail.desc: -"""消息详情""" - -message_not_exist.desc: -"""消息不存在""" - -msgid.desc: -"""消息 ID""" - -payload.desc: -"""消息内容""" - -publish_at.desc: -"""消息发送时间, RFC 3339 格式""" - -qos.desc: -"""QoS""" - -retained_list.desc: -"""保留消息列表""" - -topic.desc: -"""主题""" - -unsupported_backend.desc: -"""不支持的后端""" - -update_config_failed.desc: -"""配置更新失败""" - -update_config_success.desc: -"""配置更新成功""" - -update_retainer_api.desc: -"""更新配置""" - -} diff --git a/rel/i18n/zh/emqx_retainer_schema.hocon b/rel/i18n/zh/emqx_retainer_schema.hocon deleted file mode 100644 index 1e8630007..000000000 --- a/rel/i18n/zh/emqx_retainer_schema.hocon +++ /dev/null @@ -1,46 +0,0 @@ -emqx_retainer_schema { - -backend.desc: -"""保留消息的存储后端""" - -batch_deliver_limiter.desc: -"""批量发送的限流器的名称。 -限流器可以用来防止短时间内向客户端发送太多的消息,从而避免过多的消息导致客户端队列堵塞甚至崩溃。 -这个名称需要是指向 `limiter.batch` 下的一个真实存在的限流器。 -如果这个字段为空,则不使用限流器。""" - -batch_deliver_number.desc: -"""批量派发时每批的数量。0 代表一次性全部派发""" - -batch_read_number.desc: -"""从存储后端批量加载时的每批数量上限,0 代表一次性读取""" - -enable.desc: -"""是否开启消息保留功能""" - -flow_control.desc: -"""流控设置""" - -max_payload_size.desc: -"""消息大小最大值""" - -max_retained_messages.desc: -"""消息保留的数量上限。0 表示无限""" - -mnesia_config_storage_type.desc: -"""选择消息是存放在磁盘还是内存中""" - -mnesia_config_type.desc: -"""后端类型""" - -msg_clear_interval.desc: -"""消息清理间隔。0 代表不进行清理""" - -msg_expiry_interval.desc: -"""消息保留时间。0 代表永久保留""" - -stop_publish_clear_msg.desc: -"""是否不发送保留消息的清理消息,在 MQTT 5.0 中如果一条保留消息的消息体为空,则会清除掉之前存储 -的对应的保留消息,通过这个值控制是否停止发送清理消息""" - -} diff --git a/rel/i18n/zh/emqx_rewrite_api.hocon b/rel/i18n/zh/emqx_rewrite_api.hocon deleted file mode 100644 index 2be95d38b..000000000 --- a/rel/i18n/zh/emqx_rewrite_api.hocon +++ /dev/null @@ -1,12 +0,0 @@ -emqx_rewrite_api { - -list_topic_rewrite_api.desc: -"""列出全部主题重写规则""" - -update_topic_rewrite_api.desc: -"""更新全部主题重写规则""" - -update_topic_rewrite_api_response413.desc: -"""超出主题重写规则数量上限""" - -} diff --git a/rel/i18n/zh/emqx_rule_api_schema.hocon b/rel/i18n/zh/emqx_rule_api_schema.hocon deleted file mode 100644 index 854f7707f..000000000 --- a/rel/i18n/zh/emqx_rule_api_schema.hocon +++ /dev/null @@ -1,381 +0,0 @@ -emqx_rule_api_schema { - -event_action.desc: -"""订阅或发布""" - -event_action.label: -"""订阅或发布""" - -event_payload.desc: -"""消息负载""" - -event_payload.label: -"""消息负载""" - -metrics_actions_failed_out_of_service.desc: -"""由于输出停止服务而导致规则调用输出失败的次数。 例如,桥接被禁用或停止。""" - -metrics_actions_failed_out_of_service.label: -"""调用输出失败次数""" - -metrics_actions_failed_unknown.desc: -"""由于未知错误,规则调用输出失败的次数。""" - -metrics_actions_failed_unknown.label: -"""调用输出失败次数""" - -event_server.desc: -"""MQTT broker的 IP 地址(或主机名)和端口,采用 IP:Port 格式""" - -event_server.label: -"""服务器 IP 地址和端口""" - -metrics_actions_total.desc: -"""规则调用输出的次数。 该值可能是“sql.matched”的几倍,具体取决于规则输出的数量。""" - -metrics_actions_total.label: -"""调用输出次数""" - -event_ctx_disconnected_da.desc: -"""客户端断开连接的时刻""" - -event_ctx_disconnected_da.label: -"""客户端断开连接时刻""" - -event_topic.desc: -"""消息主题""" - -event_topic.label: -"""消息主题""" - -event_peername.desc: -"""对等客户端的 IP 地址和端口""" - -event_peername.label: -"""IP 地址和端口""" - -metrics_sql_passed.desc: -"""SQL 通过的次数""" - -metrics_sql_passed.label: -"""SQL 通过""" - -test_context.desc: -"""测试事件的上下文""" - -test_context.label: -"""事件上下文""" - -node_node.desc: -"""节点名字""" - -node_node.label: -"""节点名字""" - -event_from_clientid.desc: -"""事件来源客户端的 ID""" - -event_from_clientid.label: -"""客户端 ID""" - -event_keepalive.desc: -"""保持连接""" - -event_keepalive.label: -"""保持连接""" - -event_connected_at.desc: -"""客户端连接完成时的时刻""" - -event_connected_at.label: -"""连接完成时的时刻""" - -metrics_sql_failed_exception.desc: -"""SQL 由于执行异常而失败的次数。 这可能是因为调用 SQL 函数时崩溃,或者试图对未定义的变量进行算术运算""" - -metrics_sql_failed_exception.label: -"""SQL 执行异常""" - -event_from_username.desc: -"""事件来源客户端的用户名""" - -event_from_username.label: -"""用户名""" - -event_ctx_connack_reason_code.desc: -"""错误码""" - -event_ctx_connack_reason_code.label: -"""错误码""" - -rs_description.desc: -"""描述""" - -rs_description.label: -"""描述""" - -rule_id.desc: -"""规则的 ID""" - -rule_id.label: -"""规则 ID""" - -rs_event.desc: -"""事件主题""" - -rs_event.label: -"""事件主题""" - -root_rule_engine.desc: -"""规则引擎配置。该 API 可用于查看和修改规则引擎相关的一些设置。但不可用于规则,如需查看或修改规则,请调用 '/rules' API 进行操作。""" - -root_rule_engine.label: -"""规则引擎配置""" - -event_sockname.desc: -"""本地监听的 IP 地址和端口""" - -event_sockname.label: -"""IP 地址和端口""" - -event_qos.desc: -"""消息的 QoS""" - -event_qos.label: -"""消息 QoS""" - -event_mountpoint.desc: -"""挂载点""" - -event_mountpoint.label: -"""挂载点""" - -rs_title.desc: -"""标题""" - -rs_title.label: -"""标题""" - -ri_metrics.desc: -"""规则的计数器""" - -ri_metrics.label: -"""规则计数器""" - -event_retain.desc: -"""是否是保留消息""" - -event_retain.label: -"""保留消息""" - -event_event_type.desc: -"""事件类型""" - -event_event_type.label: -"""事件类型""" - -event_expiry_interval.desc: -"""到期间隔""" - -event_expiry_interval.label: -"""到期间隔""" - -metrics_sql_matched.desc: -"""SQL 的 FROM 子句匹配的次数。""" - -metrics_sql_matched.label: -"""命中数""" - -event_clientid.desc: -"""客户端 ID""" - -event_clientid.label: -"""客户端 ID""" - -metrics_actions_success.desc: -"""规则成功调用输出的次数。""" - -metrics_actions_success.label: -"""成功调用输出次数""" - -metrics_actions_failed.desc: -"""规则调用输出失败的次数。""" - -metrics_actions_failed.label: -"""调用输出失败次数""" - -metrics_sql_matched_rate.desc: -"""命中速率,次/秒""" - -metrics_sql_matched_rate.label: -"""Matched Rate""" - -event_proto_ver.desc: -"""协议版本""" - -event_proto_ver.label: -"""协议版本""" - -event_publish_received_at.desc: -"""消息被接受的时间""" - -event_publish_received_at.label: -"""消息被接受的时间""" - -metrics_sql_matched_rate_last5m.desc: -"""5分钟平均命中速率,次/秒""" - -metrics_sql_matched_rate_last5m.label: -"""平均命中速率""" - -event_is_bridge.desc: -"""是否桥接""" - -event_is_bridge.label: -"""是否桥接""" - -event_authz_source.desc: -"""缓存,插件或者默认值""" - -event_authz_source.label: -"""认证源""" - -metrics_sql_failed_unknown.desc: -"""由于未知错误导致 SQL 失败的次数。""" - -metrics_sql_failed_unknown.label: -"""SQL 未知错误""" - -metrics_sql_failed.desc: -"""SQL 失败的次数""" - -metrics_sql_failed.label: -"""SQL 失败""" - -event_ctx_dropped.desc: -"""消息被丢弃的原因""" - -event_ctx_dropped.label: -"""丢弃原因""" - -root_rule_test.desc: -"""用于规则测试的 Schema""" - -root_rule_test.label: -"""用于规则测试的 Schema""" - -rs_test_columns.desc: -"""测试列""" - -rs_test_columns.label: -"""测试列""" - -event_peerhost.desc: -"""对等客户端的 IP 地址""" - -event_peerhost.label: -"""对等客户端的 IP""" - -event_proto_name.desc: -"""协议名称""" - -event_proto_name.label: -"""协议名称""" - -root_rule_events.desc: -"""用于事件的 Schema""" - -root_rule_events.label: -"""用于规则事件的 Schema""" - -rs_sql_example.desc: -"""SQL 例子""" - -rs_sql_example.label: -"""SQL 例子""" - -metrics_sql_matched_rate_max.desc: -"""最大命中速率,次/秒""" - -metrics_sql_matched_rate_max.label: -"""最大命中速率""" - -event_clean_start.desc: -"""清除会话""" - -event_clean_start.label: -"""清除会话""" - -ri_created_at.desc: -"""规则创建时间""" - -ri_created_at.label: -"""规则创建时间""" - -event_dup.desc: -"""MQTT 消息的 DUP 标志""" - -event_dup.label: -"""DUP 标志""" - -ri_from.desc: -"""规则指定的主题""" - -ri_from.label: -"""规则指定的主题""" - -ri_node_metrics.desc: -"""每个节点的规则计数器""" - -ri_node_metrics.label: -"""每个节点规则计数器""" - -root_rule_creation.desc: -"""用于创建规则的 Schema""" - -root_rule_creation.label: -"""用于创建规则的 Schema""" - -event_result.desc: -"""允许或禁止""" - -event_result.label: -"""认证结果""" - -event_id.desc: -"""消息 ID""" - -event_id.label: -"""消息 ID""" - -event_username.desc: -"""用户名""" - -event_username.label: -"""用户名""" - -root_rule_info.desc: -"""用于规则信息的 Schema""" - -root_rule_info.label: -"""用于规则信息的 Schema""" - -rs_columns.desc: -"""列""" - -rs_columns.label: -"""列""" - -test_sql.desc: -"""测试的 SQL""" - -test_sql.label: -"""测试 SQL""" - -event_ctx_disconnected_reason.desc: -"""断开连接的原因""" - -event_ctx_disconnected_reason.label: -"""断开连接原因""" - -} diff --git a/rel/i18n/zh/emqx_rule_engine_api.hocon b/rel/i18n/zh/emqx_rule_engine_api.hocon deleted file mode 100644 index eb1be4e73..000000000 --- a/rel/i18n/zh/emqx_rule_engine_api.hocon +++ /dev/null @@ -1,93 +0,0 @@ -emqx_rule_engine_api { - -api1.desc: -"""列出所有规则""" - -api1.label: -"""列出所有规则""" - -api10.desc: -"""更新规则引擎配置。""" - -api10.label: -"""更新配置""" - -api1_enable.desc: -"""根据规则是否开启条件过滤""" - -api1_from.desc: -"""根据规则来源 Topic 过滤, 需要完全匹配""" - -api1_like_description.desc: -"""根据规则描述过滤, 使用子串模糊匹配""" - -api1_like_from.desc: -"""根据规则来源 Topic 过滤, 使用子串模糊匹配""" - -api1_like_id.desc: -"""根据规则 id 过滤, 使用子串模糊匹配""" - -api1_match_from.desc: -"""根据规则来源 Topic 过滤, 使用 MQTT Topic 匹配""" - -api1_resp.desc: -"""规则列表""" - -api1_resp.label: -"""列出所有规则""" - -api2.desc: -"""通过指定 ID 创建规则""" - -api2.label: -"""通过指定 ID 创建规则""" - -api3.desc: -"""列出所有能被规则使用的事件""" - -api3.label: -"""列出所有能被规则使用的事件""" - -api4.desc: -"""通过 ID 查询规则""" - -api4.label: -"""查询规则""" - -api4_1.desc: -"""通过给定的 Id 获得规则的指标数据""" - -api4_1.label: -"""获得指标数据""" - -api5.desc: -"""通过 ID 更新集群里所有节点上的规则""" - -api5.label: -"""更新集群规则""" - -api6.desc: -"""通过 ID 删除集群里所有节点上的规则""" - -api6.label: -"""基于给定 ID 新建一条规则""" - -api7.desc: -"""重置规则计数""" - -api7.label: -"""重置规则计数""" - -api8.desc: -"""测试一个规则""" - -api8.label: -"""测试规则""" - -api9.desc: -"""获取规则引擎配置。""" - -api9.label: -"""获取配置""" - -} diff --git a/rel/i18n/zh/emqx_rule_engine_schema.hocon b/rel/i18n/zh/emqx_rule_engine_schema.hocon deleted file mode 100644 index 26858e10f..000000000 --- a/rel/i18n/zh/emqx_rule_engine_schema.hocon +++ /dev/null @@ -1,184 +0,0 @@ -emqx_rule_engine_schema { - -console_function.desc: -"""将输出打印到控制台""" - -console_function.label: -"""控制台函数""" - -desc_builtin_action_console.desc: -"""配置打印到控制台""" - -desc_builtin_action_console.label: -"""配置打印到控制台""" - -desc_builtin_action_republish.desc: -"""配置重新发布。""" - -desc_builtin_action_republish.label: -"""配置重新发布""" - -desc_republish_args.desc: -"""内置 'republish' 动作的参数。 -可以在参数中使用变量。 -变量是规则中选择的字段。 例如规则 SQL 定义如下: - - SELECT clientid, qos, payload FROM "t/1" - -然后有 3 个变量可用:clientidqospayload。 如果我们将参数设置为: - - { - topic = "t/${clientid}" - qos = "${qos}" - payload = "msg: ${payload}" - } - -当收到一条消息 payload = `hello`, qos = 1, clientid = `Steve` 时,将重新发布一条新的 MQTT 消息到主题 `t/Steve` -消息内容为 payload = `msg: hello`, and `qos = 1""" - -desc_republish_args.label: -"""重新发布参数""" - -desc_rule_engine.desc: -"""配置 EMQX 规则引擎。""" - -desc_rule_engine.label: -"""配置规则引擎""" - -desc_rules.desc: -"""配置规则""" - -desc_rules.label: -"""配置规则""" - -desc_user_provided_function.desc: -"""配置用户函数""" - -desc_user_provided_function.label: -"""配置用户函数""" - -republish_args_payload.desc: -"""要重新发布的消息的有效负载。允许使用带有变量的模板,请参阅“republish_args”的描述。 -默认为 ${payload}。 如果从所选结果中未找到变量 ${payload},则使用字符串 "undefined"。""" - -republish_args_payload.label: -"""消息负载""" - -republish_args_qos.desc: -"""要重新发布的消息的 qos。允许使用带有变量的模板,请参阅“republish_args”的描述。 -默认为 ${qos}。 如果从规则的选择结果中没有找到变量 ${qos},则使用 0。""" - -republish_args_qos.label: -"""消息 QoS 等级""" - -republish_args_retain.desc: -"""要重新发布的消息的“保留”标志。允许使用带有变量的模板,请参阅“republish_args”的描述。 -默认为 ${retain}。 如果从所选结果中未找到变量 ${retain},则使用 false。""" - -republish_args_retain.label: -"""保留消息标志""" - -republish_args_topic.desc: -"""重新发布消息的目标主题。 -允许使用带有变量的模板,请参阅“republish_args”的描述。""" - -republish_args_topic.label: -"""目标主题""" - -republish_args_user_properties.desc: -"""指定使用哪个变量来填充 MQTT 消息的 User-Property 列表。这个变量的值必须是一个 map 类型。 -可以设置成 ${pub_props.'User-Property'} 或者 -使用 SELECT *,pub_props.'User-Property' as user_properties 来把源 MQTT 消息 -的 User-Property 列表用于填充。 -也可以使用 map_put 函数来添加新的 User-Property, -map_put('my-prop-name', 'my-prop-value', user_properties) as user_properties -注意:MQTT 协议允许一个消息中出现多次同一个 property 名,但是 EMQX 的规则引擎不允许。""" - -republish_function.desc: -"""将消息重新发布为新的 MQTT 消息""" - -republish_function.label: -"""重新发布函数""" - -rule_engine_ignore_sys_message.desc: -"""当设置为“true”(默认)时,规则引擎将忽略发布到 $SYS 主题的消息。""" - -rule_engine_ignore_sys_message.label: -"""忽略系统消息""" - -rule_engine_jq_function_default_timeout.desc: -"""规则引擎内建函数 `jq` 默认时间限制""" - -rule_engine_jq_function_default_timeout.label: -"""规则引擎 jq 函数时间限制""" - -rule_engine_jq_implementation_module.desc: -"""jq 规则引擎功能的实现模块。可用的两个选项是 jq_nif 和 jq_port。jq_nif 使用 Erlang NIF 库访问 jq 库,而 jq_port 使用基于 Erlang Port 的实现。jq_nif 方式(默认选项)是这两个选项中最快的实现,但 jq_port 方式更安全,因为这种情况下 jq 程序不会在 Erlang VM 进程中执行。""" - -rule_engine_jq_implementation_module.label: -"""JQ 实现模块""" - -rule_engine_rules.desc: -"""规则""" - -rule_engine_rules.label: -"""规则""" - -rules_actions.desc: -"""规则的动作列表。 -动作可以是指向 EMQX bridge 的引用,也可以是一个指向函数的对象。 -我们支持一些内置函数,如“republish”和“console”,我们还支持用户提供的函数,它的格式为:“{module}:{function}”。 -列表中的动作按顺序执行。这意味着如果其中一个动作执行缓慢,则以下所有动作都不会被执行直到它返回。 -如果其中一个动作崩溃,在它之后的所有动作仍然会被按照原始顺序执行。 -如果运行动作时出现任何错误,则会出现错误消息,并且相应的计数器会增加。""" - -rules_actions.label: -"""动作列表""" - -rules_description.desc: -"""规则的描述""" - -rules_description.label: -"""规则描述""" - -rules_enable.desc: -"""启用或禁用规则引擎""" - -rules_enable.label: -"""启用或禁用规则引擎""" - -rules_metadata.desc: -"""规则的元数据,不要手动修改""" - -rules_metadata.label: -"""规则的元数据""" - -rules_name.desc: -"""规则名字""" - -rules_name.label: -"""规则名字""" - -rules_sql.desc: -"""用于处理消息的 SQL 。 -示例:SELECT * FROM "test/topic" WHERE payload.x = 1""" - -rules_sql.label: -"""规则 SQL""" - -user_provided_function_args.desc: -"""用户提供的参数将作为函数 module:function/3 的第三个参数, -请检查源文件:apps/emqx_rule_engine/src/emqx_rule_actions.erl 中的示例函数 consolerepublish 。""" - -user_provided_function_args.label: -"""用户提供函数的参数""" - -user_provided_function_function.desc: -"""用户提供的函数。 格式应为:'{module}:{function}'。 -其中 {module} 是 Erlang 回调模块, {function} 是 Erlang 函数。 -要编写自己的函数,请检查源文件:apps/emqx_rule_engine/src/emqx_rule_actions.erl 中的示例函数 consolerepublish 。""" - -user_provided_function_function.label: -"""用户提供的函数""" - -} diff --git a/rel/i18n/zh/emqx_schema.hocon b/rel/i18n/zh/emqx_schema.hocon deleted file mode 100644 index 4c8e1f81e..000000000 --- a/rel/i18n/zh/emqx_schema.hocon +++ /dev/null @@ -1,1471 +0,0 @@ -emqx_schema { - -fields_mqtt_quic_listener_peer_unidi_stream_count.desc: -"""允许对端打开的单向流的数量""" - -fields_mqtt_quic_listener_peer_unidi_stream_count.label: -"""对端单向流的数量""" - -fields_authorization_no_match.desc: -"""如果用户或客户端不匹配ACL规则,或者从可配置授权源(比如内置数据库、HTTP API 或 PostgreSQL 等。)内未找 -到此类用户或客户端时,模式的认访问控制操作。 -在“授权”中查找更多详细信息。""" - -fields_authorization_no_match.label: -"""未匹时的默认授权动作""" - -sysmon_top_db_hostname.desc: -"""收集数据点的 PostgreSQL 数据库的主机名。""" - -sysmon_top_db_hostname.label: -"""数据库主机名""" - -zones.desc: -"""zone 是按name 分组的一组配置。 -对于灵活的配置映射,可以将 name 设置为侦听器的 zone 配置。 -注:名为 default 的内置区域是自动创建的,无法删除。""" - -fields_mqtt_quic_listener_certfile.desc: -"""证书文件。在 5.1 中会被废弃,使用 .ssl_options.certfile 代替。""" - -fields_mqtt_quic_listener_certfile.label: -"""证书文件""" - -fields_rate_limit_conn_bytes_in.desc: -"""限制 MQTT 连接接收数据包的速率。 速率以每秒的数据包字节数计算。""" - -fields_rate_limit_conn_bytes_in.label: -"""数据包速率""" - -crl_cache_capacity.desc: -"""缓存中可容纳的 CRL URL 的最大数量。 如果缓存的容量已满,并且必须获取一个新的 URL,那么它将驱逐缓存中插入的最老的 URL。""" - -crl_cache_capacity.label: -"""CRL 缓存容量""" - -alarm_actions.desc: -"""警报激活时触发的动作。
目前,支持以下操作:logpublish. -log 将告警写入日志 (控制台或者文件). -publish 将告警作为 MQTT 消息发布到系统主题: -$SYS/brokers/emqx@xx.xx.xx.x/alarms/activate and -$SYS/brokers/emqx@xx.xx.xx.x/alarms/deactivate""" - -alarm_actions.label: -"""告警动作""" - -base_listener_max_connections.desc: -"""监听器允许的最大并发连接数。""" - -base_listener_max_connections.label: -"""最大并发连接数""" - -mqtt_peer_cert_as_username.desc: -"""使用对端证书中的 CN、DN 字段或整个证书内容来作为用户名;仅适用于 TLS 连接。 -目前支持: -- cn: 取证书的 CN 字段 -- dn: 取证书的 DN 字段 -- crt: 取 DERPEM 的证书内容 -- pem: 将 DER 证书转换为 PEM 格式作为用户名 -- md5: 取 DERPEM 证书内容的 MD5 值""" - -mqtt_peer_cert_as_username.label: -"""对端证书作为用户名""" - -fields_cache_enable.desc: -"""启用或禁用授权缓存。""" - -fields_cache_enable.label: -"""启用或禁用授权缓存""" - -fields_mqtt_quic_listener_disconnect_timeout_ms.desc: -"""在判定路径无效和断开连接之前,要等待多长时间的ACK。默认:16000""" - -fields_mqtt_quic_listener_disconnect_timeout_ms.label: -"""断开连接超时 毫秒""" - -mqtt_max_topic_alias.desc: -"""允许的最大主题别名数,0 表示不支持主题别名。""" - -mqtt_max_topic_alias.label: -"""最大主题别名""" - -common_ssl_opts_schema_user_lookup_fun.desc: -"""用于查找预共享密钥(PSK)标识的 EMQX 内部回调。""" - -common_ssl_opts_schema_user_lookup_fun.label: -"""SSL PSK 用户回调""" - -fields_listeners_wss.desc: -"""HTTPS websocket 监听器。""" - -fields_listeners_wss.label: -"""HTTPS websocket 监听器""" - -sysmon_top_max_procs.desc: -"""当 VM 中的进程数超过此值时,停止收集数据。""" - -sysmon_top_max_procs.label: -"""最大进程数""" - -mqtt_use_username_as_clientid.desc: -"""是否使用用户名作为客户端 ID。 -此设置的作用时间晚于 对端证书作为用户名对端证书作为客户端 ID。""" - -mqtt_use_username_as_clientid.label: -"""用户名作为客户端 ID""" - -mqtt_max_qos_allowed.desc: -"""允许的最大 QoS 等级。""" - -mqtt_max_qos_allowed.label: -"""最大 QoS""" - -fields_mqtt_quic_listener_max_binding_stateless_operations.desc: -"""在任何时候可以在一个绑定上排队的无状态操作的最大数量。默认值:100""" - -fields_mqtt_quic_listener_max_binding_stateless_operations.label: -"""最大绑定无状态操作""" - -fields_mqtt_quic_listener_stream_recv_buffer_default.desc: -"""流的初始缓冲区大小。默认:4096""" - -fields_mqtt_quic_listener_stream_recv_buffer_default.label: -"""流媒体接收缓冲区默认值""" - -fields_mqtt_quic_listener_pacing_enabled.desc: -"""有节奏的发送,以避免路径上的缓冲区过度填充。默认值:1(已启用)""" - -fields_mqtt_quic_listener_pacing_enabled.label: -"""启用节奏发送""" - -mqtt_max_subscriptions.desc: -"""允许每个客户端建立的最大订阅数量。""" - -mqtt_max_subscriptions.label: -"""最大订阅数量""" - -persistent_session_builtin_messages_table.desc: -"""用于内建消息表的性能调优参数。""" - -persistent_session_builtin_messages_table.label: -"""持久化消息""" - -sysmon_os_cpu_low_watermark.desc: -"""在解除相应警报之前可以使用多少系统 CPU 的阈值,以系统CPU负载的百分比表示。""" - -sysmon_os_cpu_low_watermark.label: -"""CPU 低水位线""" - -fields_mqtt_quic_listener_tls_server_max_send_buffer.desc: -"""缓冲多少TLS数据。 默认值:8192""" - -fields_mqtt_quic_listener_tls_server_max_send_buffer.label: -"""TLS 服务器最大发送缓冲区""" - -base_listener_bind.desc: -"""监听套接字的 IP 地址和端口。""" - -base_listener_bind.label: -"""IP 地址和端口""" - -server_ssl_opts_schema_handshake_timeout.desc: -"""握手完成所允许的最长时间""" - -server_ssl_opts_schema_handshake_timeout.label: -"""握手超时时间""" - -fields_deflate_opts_server_context_takeover.desc: -"""接管意味着在服务器消息之间保留压缩状态。""" - -fields_deflate_opts_server_context_takeover.label: -"""服务上下文接管""" - -mqtt_session_expiry_interval.desc: -"""指定会话将在连接断开后多久过期,仅适用于非 MQTT 5.0 的连接。""" - -mqtt_session_expiry_interval.label: -"""会话过期间隔""" - -fields_listener_enabled.desc: -"""启停监听器。""" - -fields_listener_enabled.label: -"""启停监听器""" - -mqtt.desc: -"""全局的 MQTT 配置项。 -mqtt 下所有的配置作为全局的默认值存在,它可以被 zone 中的配置覆盖。""" - -crl_cache_refresh_http_timeout.desc: -"""获取 CRLs 时 HTTP 请求的超时。 该配置对所有启用 CRL 检查的监听器监听器有效。""" - -crl_cache_refresh_http_timeout.label: -"""CRL 缓存刷新 HTTP 超时""" - -fields_tcp_opts_backlog.desc: -"""TCP backlog 定义了挂起连接队列可以增长到的最大长度。""" - -fields_tcp_opts_backlog.label: -"""TCP 连接队列长度""" - -fields_mqtt_quic_listener_initial_window_packets.desc: -"""一个连接的初始拥堵窗口的大小(以包为单位)。默认值:10""" - -fields_mqtt_quic_listener_initial_window_packets.label: -"""初始窗口数据包""" - -flapping_detect_enable.desc: -"""启用抖动检测功能。""" - -flapping_detect_enable.label: -"""启用抖动检测""" - -sysmon_top_db_password.desc: -"""PostgreSQL 数据库的密码""" - -sysmon_top_db_password.label: -"""数据库密码""" - -fields_ws_opts_check_origins.desc: -"""允许的 origins 列表""" - -fields_ws_opts_check_origins.label: -"""允许的 origins""" - -fields_deflate_opts_client_context_takeover.desc: -"""接管意味着在客户端消息之间保留压缩状态。""" - -fields_deflate_opts_client_context_takeover.label: -"""客户端上下文接管""" - -base_listener_acceptors.desc: -"""监听器接收池的大小。""" - -base_listener_acceptors.label: -"""接收器数量""" - -common_ssl_opts_schema_cacertfile.desc: -"""受信任的PEM格式 CA 证书捆绑文件
-此文件中的证书用于验证TLS对等方的证书。 -如果要信任新 CA,请将新证书附加到文件中。 -无需重启EMQX即可加载更新的文件,因为系统会定期检查文件是否已更新(并重新加载)
-注意:从文件中失效(删除)证书不会影响已建立的连接。""" - -common_ssl_opts_schema_cacertfile.label: -"""CA 证书文件""" - -fields_ws_opts_mqtt_path.desc: -"""WebSocket 的 MQTT 协议路径。因此,EMQX Broker的WebSocket地址为: -ws://{ip}:{port}/mqtt""" - -fields_ws_opts_mqtt_path.label: -"""WS MQTT 路径""" - -sysmon_os_procmem_high_watermark.desc: -"""在发出相应警报之前,一个Erlang进程可以分配多少系统内存的阈值,以系统内存的百分比表示。""" - -sysmon_os_procmem_high_watermark.label: -"""进程内存高水位线""" - -fields_listeners_quic.desc: -"""QUIC 监听器。""" - -fields_listeners_quic.label: -"""QUIC 监听器""" - -fields_listeners_ws.desc: -"""HTTP websocket 监听器。""" - -fields_listeners_ws.label: -"""HTTP websocket 监听器""" - -mqtt_retry_interval.desc: -"""QoS 1/2 消息的重新投递间隔。""" - -mqtt_retry_interval.label: -"""重试间隔""" - -stats_enable.desc: -"""启用/禁用统计数据收集功能。""" - -stats_enable.label: -"""启用/禁用统计数据收集功能""" - -fields_authorization_deny_action.desc: -"""授权检查拒绝操作时的操作。""" - -fields_authorization_deny_action.label: -"""授权检查拒绝操作时的操作""" - -fields_deflate_opts_server_max_window_bits.desc: -"""指定服务器压缩上下文的大小。""" - -fields_deflate_opts_server_max_window_bits.label: -"""服务器压缩窗口大小""" - -client_ssl_opts_schema_server_name_indication.desc: -"""指定要在 TLS 服务器名称指示扩展中使用的主机名。
-例如,当连接到 "server.example.net" 时,接受连接并执行 TLS 握手的真正服务器可能与 TLS 客户端最初连接到的主机不同, -例如,当连接到 IP 地址时,或者当主机具有多个可解析的 DNS 记录时
-如果未指定,它将默认为使用的主机名字符串 -建立连接,除非使用 IP 地址
-然后,主机名也用于对等机的主机名验证证书
-特殊值 disable 阻止发送服务器名称指示扩展,并禁用主机名验证检查。""" - -client_ssl_opts_schema_server_name_indication.label: -"""服务器名称指示""" - -fields_mqtt_quic_listener_retry_memory_limit.desc: -"""在使用无状态重试之前,可用于握手连接的可用内存的百分比。计算为`N/65535`。默认值:65""" - -fields_mqtt_quic_listener_retry_memory_limit.label: -"""重试内存限制""" - -force_shutdown_max_mailbox_size.desc: -"""每个在线客户端在 EMQX 服务器中都是独立的一个进程。该配置可以设为单个进程的邮箱消息队列设置最大长度,当超过该上限时,客户端会被强制下线。""" - -force_shutdown_max_mailbox_size.label: -"""进程邮箱消息数上限""" - -sys_heartbeat_interval.desc: -"""发送心跳系统消息的间隔时间,它包括: - - `$SYS/brokers//uptime` - - `$SYS/brokers//datetime`""" - -flapping_detect_ban_time.desc: -"""抖动的客户端将会被禁止登录多长时间。""" - -flapping_detect_ban_time.label: -"""禁止登录时长""" - -sysmon_top_num_items.desc: -"""每个监视组的顶级进程数。""" - -sysmon_top_num_items.label: -"""顶级进程数""" - -persistent_session_builtin_session_table.desc: -"""用于内建会话表的性能调优参数。""" - -persistent_session_builtin_session_table.label: -"""持久会话""" - -mqtt_upgrade_qos.desc: -"""投递消息时,是否根据订阅主题时的 QoS 等级来强制提升派发的消息的 QoS 等级。""" - -mqtt_upgrade_qos.label: -"""升级 QoS""" - -mqtt_shared_subscription.desc: -"""是否启用对 MQTT 共享订阅的支持。""" - -mqtt_shared_subscription.label: -"""共享订阅可用""" - -fields_tcp_opts_sndbuf.desc: -"""连接的 TCP 发送缓冲区(OS 内核)。""" - -fields_tcp_opts_sndbuf.label: -"""TCP 发送缓冲区""" - -sysmon_os_mem_check_interval.desc: -"""定期内存检查的时间间隔。""" - -sysmon_os_mem_check_interval.label: -"""内存检查间隔""" - -server_ssl_opts_schema_gc_after_handshake.desc: -"""内存使用调优。如果启用,将在TLS/SSL握手完成后立即执行垃圾回收。TLS/SSL握手建立后立即进行GC。""" - -server_ssl_opts_schema_gc_after_handshake.label: -"""握手后执行GC""" - -fields_mqtt_quic_listener_ssl_options.desc: -"""QUIC 传输层的 TLS 选项""" - -fields_mqtt_quic_listener_ssl_options.label: -"""TLS 选项""" - -fields_ws_opts_mqtt_piggyback.desc: -"""WebSocket消息是否允许包含多个 MQTT 数据包。""" - -fields_ws_opts_mqtt_piggyback.label: -"""MQTT Piggyback""" - -base_listener_mountpoint.desc: -"""发布或订阅时,请在所有主题前面加上 mountpoint 字符串。 - -将消息传递给订阅者时,将从主题名称中删除带前缀的字符串。挂载点是一种用户可以用来实现不同侦听器之间消息路由隔离的方法。 - -例如,如果客户机 A 使用 listeners.tcp.\.mountpoint 设置为'some_tenant',那么客户端实际上订阅了主题'some_tenant/t'。
-类似地,如果另一个客户端B(与客户端A连接到同一个侦听器)向主题 't' 发送消息,该消息将路由到所有订阅了'some_租户/t'的客户端,因此客户端 A 将接收主题名为't'的消息
- -设置为"" 以禁用该功能
- -mountpoint 字符串中的变量: -- ${clientid}: clientid -- ${username}: username""" - -base_listener_mountpoint.label: -"""mountpoint""" - -mqtt_max_awaiting_rel.desc: -"""每个发布者的会话中,都存在一个队列来处理客户端发送的 QoS 2 消息。该队列会存储 QoS 2 消息的报文 ID 直到收到客户端的 PUBREL 或超时,达到队列长度的限制后,新的 QoS 2 消息发布会被拒绝,并返回 `147(0x93)` 错误。""" - -mqtt_max_awaiting_rel.label: -"""PUBREL 等待队列长度""" - -ciphers_schema_quic.desc: -"""此配置保存由逗号分隔的 TLS 密码套件名称,或作为字符串数组。例如 -"TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256"或 -["TLS_AES_256_GCM_SHA384","TLS_AES_128_GCM_SHA256"]。 -
-密码(及其顺序)定义了客户端和服务器通过网络连接加密信息的方式。 -选择一个好的密码套件对于应用程序的数据安全性、机密性和性能至关重要。 - -名称应为 OpenSSL 字符串格式(而不是 RFC 格式)。 -EMQX 配置文档提供的所有默认值和示例都是 OpenSSL 格式
-注意:某些密码套件仅与特定的 TLS 版本兼容('tlsv1.1'、'tlsv1.2'或'tlsv1.3')。 -不兼容的密码套件将被自动删除。 - -例如,如果只有 versions 仅配置为 tlsv1.3。为其他版本配置密码套件将无效。 - -
-注:PSK 的 Ciphers 不支持 tlsv1.3
-如果打算使用PSK密码套件,tlsv1.3。应在ssl.versions中禁用。 - -
-PSK 密码套件: -"RSA-PSK-AES256-GCM-SHA384,RSA-PSK-AES256-CBC-SHA384, -RSA-PSK-AES128-GCM-SHA256,RSA-PSK-AES128-CBC-SHA256, -RSA-PSK-AES256-CBC-SHA,RSA-PSK-AES128-CBC-SHA, -RSA-PSK-DES-CBC3-SHA,RSA-PSK-RC4-SHA"
- -注:QUIC 监听器不支持 tlsv1.3 的 ciphers""" - -ciphers_schema_quic.label: -"""""" - -fields_mqtt_quic_listener_max_bytes_per_key.desc: -"""在启动密钥更新之前,用单个 1-RTT 加密密钥加密的最大字节数。默认值:274877906944""" - -fields_mqtt_quic_listener_max_bytes_per_key.label: -"""每个密钥的最大字节数""" - -fields_mqtt_quic_listener_mtu_discovery_search_complete_timeout_us.desc: -"""如果没有达到 max ,在重新尝试 MTU 探测之前要等待的时间,单位是微秒。默认值:600000000""" - -fields_mqtt_quic_listener_mtu_discovery_search_complete_timeout_us.label: -"""""" - -fields_ws_opts_check_origin_enable.desc: -"""如果trueoriginHTTP 头将根据check_origins参数中配置的允许来源列表进行验证。""" - -fields_ws_opts_check_origin_enable.label: -"""检查 origin""" - -sysmon_vm_busy_dist_port.desc: -"""启用后,当用于集群接点之间 RPC 的连接过忙时,会触发一条带有 busy_dist_port 关键字的 warning 级别日志。 -同时还会发布一条主题为 $SYS/sysmon/busy_dist_port 的 MQTT 系统消息。""" - -sysmon_vm_busy_dist_port.label: -"""启用分布式端口过忙监控""" - -mqtt_max_mqueue_len.desc: -"""消息队列最大长度。持久客户端断开连接或飞行窗口已满时排队的消息长度。""" - -mqtt_max_mqueue_len.label: -"""最大消息队列长度""" - -mqtt_max_inflight.desc: -"""允许在完成应答前同时投递的 QoS 1 和 QoS 2 消息的最大数量。""" - -mqtt_max_inflight.label: -"""最大飞行窗口""" - -persistent_session_store_enabled.desc: -"""使用数据库存储有关持久会话的信息。 -这使得在节点停止时,可以将客户端连接迁移到另一个群集节点。""" - -persistent_session_store_enabled.label: -"""启用持久会话保存""" - -fields_deflate_opts_level.desc: -"""压缩级别""" - -fields_deflate_opts_level.label: -"""压缩级别""" - -mqtt_server_keepalive.desc: -"""EMQX 要求的保活时间,如设为 disabled,则将使用客户端指定的保持连接时间;仅适用于 MQTT 5.0 客户端。""" - -mqtt_server_keepalive.label: -"""服务端保活时间""" - -global_authentication.desc: -"""全局 MQTT 监听器的默认认证配置。 为每个监听器配置认证参考监听器器配置中的authentication 配置。 - -该配置可以被配置为: -
    -
  • []: 默认值,允许所有的登录请求 -
  • 配置为单认证器,例如 {enable:true,backend:"built_in_database",mechanism="password_based"}
  • -
  • 配置为认证器数组
  • -
- -当配置为认证链后,登录凭证会按照配置的顺序进行检查,直到做出allowdeny的结果。 - -如果在所有的认证器都执行完后,还是没有结果,登录将被拒绝。""" - -fields_mqtt_quic_listener_load_balancing_mode.desc: -"""0: 禁用, 1: SERVER_ID_IP, 2: SERVER_ID_FIXED. 默认: 0""" - -fields_mqtt_quic_listener_load_balancing_mode.label: -"""负载平衡模式""" - -persistent_session_store_session_message_gc_interval.desc: -"""持久会话消息的临时数据垃圾收集的开始间隔。 -这不会影响持久会话消息的生命周期长度。""" - -persistent_session_store_session_message_gc_interval.label: -"""会话消息清理间隔""" - -server_ssl_opts_schema_ocsp_refresh_http_timeout.desc: -"""检查 OCSP 响应时,HTTP 请求的超时。""" - -server_ssl_opts_schema_ocsp_refresh_http_timeout.label: -"""OCSP 刷新 HTTP 超时""" - -fields_tcp_opts_send_timeout.desc: -"""连接的 TCP 发送超时。""" - -fields_tcp_opts_send_timeout.label: -"""TCP 发送超时""" - -sysmon_vm_process_high_watermark.desc: -"""在发出相应警报之前,本地节点上可以同时存在多少进程的阈值(以进程百分比表示)。""" - -sysmon_vm_process_high_watermark.label: -"""进程数高水位线""" - -fields_tcp_opts_buffer.desc: -"""驱动程序使用的用户空间缓冲区的大小。""" - -fields_tcp_opts_buffer.label: -"""TCP 用户态缓冲区""" - -server_ssl_opts_schema_honor_cipher_order.desc: -"""一个重要的安全设置,它强制根据服务器指定的顺序而不是客户机指定的顺序设置密码,从而强制服务器管理员执行(通常配置得更正确)安全顺序。""" - -server_ssl_opts_schema_honor_cipher_order.label: -"""SSL honor cipher order""" - -conn_congestion_min_alarm_sustain_duration.desc: -"""清除警报前的最短时间。
只有当队列中没有挂起的数据,并且连接至少被堵塞了 min_alarm_sustain_duration 毫秒时,
报警才会被清除。这是为了避免太频繁地清除和再次发出警报。""" - -conn_congestion_min_alarm_sustain_duration.label: -"""告警维持时间""" - -fields_mqtt_quic_listener_keep_alive_interval_ms.desc: -"""多长时间发送一次PING帧以保活连接。""" - -fields_mqtt_quic_listener_keep_alive_interval_ms.label: -"""保持活着的时间间隔 毫秒""" - -fields_mqtt_quic_listener_handshake_idle_timeout_ms.desc: -"""一个握手在被丢弃之前可以空闲多长时间""" - -fields_mqtt_quic_listener_handshake_idle_timeout_ms.label: -"""握手空闲超时 毫秒""" - -broker_session_locking_strategy.desc: -"""Session 在集群中的锁策略。 - - `loca`:仅锁本节点的 Session; - - `one`:任选一个其它节点加锁; - - `quorum`:选择集群中半数以上的节点加锁; - - `all`:选择所有节点加锁。""" - -persistent_store_ram_cache.desc: -"""在内存中保持一份数据的副本,以便更快地访问。""" - -persistent_store_ram_cache.label: -"""内存缓存""" - -fields_mqtt_quic_listener_stream_recv_window_default.desc: -"""初始流接收窗口大小。 默认值:32678""" - -fields_mqtt_quic_listener_stream_recv_window_default.label: -"""流接收窗口默认""" - -mqtt_mqueue_priorities.desc: -"""主题优先级。取值范围 [1-255] -默认优先级表为空,即所有的主题优先级相同。 - -注:优先主题名称中不支持使用逗号和等号。 -注:不在此列表中的主题,被视为最高/最低优先级,这取决于mqtt.mqueue_default_priority 的配置 - -示例: -配置 "topic/1" > "topic/2": -mqueue_priorities: {"topic/1": 10, "topic/2": 8}""" - -mqtt_mqueue_priorities.label: -"""主题优先级""" - -fields_rate_limit_conn_messages_in.desc: -"""外部 MQTT 连接的消息限制。""" - -fields_rate_limit_conn_messages_in.label: -"""外部 MQTT 连接的消息限制""" - -fields_rate_limit_max_conn_rate.desc: -"""每秒最大连接数。""" - -fields_rate_limit_max_conn_rate.label: -"""每秒最大连接数""" - -alarm_size_limit.desc: -"""要保留为历史记录的已停用报警的最大总数。当超过此限制时,将删除最旧的停用报警,以限制总数。""" - -alarm_size_limit.label: -"""告警总数限制""" - -fields_cache_max_size.desc: -"""缓存项的最大数量。""" - -fields_cache_max_size.label: -"""缓存项的最大数量""" - -fields_listeners_tcp.desc: -"""TCP 监听器。""" - -fields_listeners_tcp.label: -"""TCP 监听器""" - -conn_congestion_enable_alarm.desc: -"""启用或者禁用连接阻塞告警功能。""" - -conn_congestion_enable_alarm.label: -"""启用/禁用阻塞告警""" - -fields_ws_opts_proxy_port_header.desc: -"""HTTP 头,用于传递有关客户端端口的信息。当 EMQX 集群部署在负载平衡器后面时,这一点非常重要。""" - -fields_ws_opts_proxy_port_header.label: -"""客户端端口头""" - -overload_protection_enable.desc: -"""是否对系统过载做出反应。""" - -overload_protection_enable.label: -"""是否对系统过载做出反应""" - -fields_mqtt_quic_listener_minimum_mtu.desc: -"""一个连接所支持的最小MTU。这将被作为起始MTU使用。默认值:1248""" - -fields_mqtt_quic_listener_minimum_mtu.label: -"""最小 MTU""" - -sys_msg_interval.desc: -"""发送 `$SYS` 主题的间隔时间。""" - -mqtt_await_rel_timeout.desc: -"""客户端发布 QoS 2 消息时,服务器等待 `PUBREL` 的最长时延。超过该时长后服务器会放弃等待,该PACKET ID 会被释放,从而允许后续新的 PUBLISH 消息使用。如果超时后收到 PUBREL,服务器将会产生一条告警日志。注意,向订阅客户端转发消息的动作发生在进入等待之前。""" - -mqtt_await_rel_timeout.label: -"""PUBREL 最大等待时间""" - -common_ssl_opts_schema_verify.desc: -"""启用或禁用对等验证。""" - -common_ssl_opts_schema_verify.label: -"""对等验证""" - -fields_listeners_ssl.desc: -"""SSL 监听器。""" - -fields_listeners_ssl.label: -"""SSL 监听器""" - -fields_deflate_opts_client_max_window_bits.desc: -"""指定客户端压缩上下文的大小。""" - -fields_deflate_opts_client_max_window_bits.label: -"""压缩窗口大小""" - -common_ssl_opts_schema_keyfile.desc: -"""PEM格式的私钥文件。""" - -common_ssl_opts_schema_keyfile.label: -"""私钥文件""" - -sysmon_os_cpu_high_watermark.desc: -"""在发出相应警报之前可以使用多少系统 CPU 的阈值,以系统CPU负载的百分比表示。""" - -sysmon_os_cpu_high_watermark.label: -"""CPU 高水位线""" - -flapping_detect_window_time.desc: -"""抖动检测的时间窗口。""" - -flapping_detect_window_time.label: -"""时间窗口""" - -mqtt_mqueue_default_priority.desc: -"""默认的主题优先级,不在 主题优先级mqueue_priorities) 中的主题将会使用该优先级。""" - -mqtt_mqueue_default_priority.label: -"""默认主题优先级""" - -client_ssl_opts_schema_enable.desc: -"""启用 TLS。""" - -client_ssl_opts_schema_enable.label: -"""启用 TLS""" - -fields_mqtt_quic_listener_mtu_discovery_missing_probe_count.desc: -"""在任何时候都可以在一个绑定上排队的无状态操作的最大数量。默认值:3""" - -fields_mqtt_quic_listener_mtu_discovery_missing_probe_count.label: -"""MTU发现丢失的探针数量""" - -fields_tcp_opts_recbuf.desc: -"""连接的 TCP 接收缓冲区(OS 内核)。""" - -fields_tcp_opts_recbuf.label: -"""TCP 接收缓冲区""" - -sysmon_vm_process_check_interval.desc: -"""定期进程限制检查的时间间隔。""" - -sysmon_vm_process_check_interval.label: -"""进程限制检查时间""" - -fields_mqtt_quic_listener_server_resumption_level.desc: -"""连接恢复 和/或 0-RTT 服务器支持。默认值:0(无恢复功能)""" - -fields_mqtt_quic_listener_server_resumption_level.label: -"""服务端连接恢复支持""" - -fields_ws_opts_proxy_address_header.desc: -"""HTTP 头,用于传递有关客户端 IP 地址的信息。 -当 EMQX 集群部署在负载平衡器后面时,这一点非常重要。""" - -fields_ws_opts_proxy_address_header.label: -"""客户端地址头""" - -sysmon_os_sysmem_high_watermark.desc: -"""在发出相应报警之前可以分配多少系统内存的阈值,以系统内存的百分比表示。""" - -sysmon_os_sysmem_high_watermark.label: -"""系统内存高水位线""" - -fields_tcp_opts_high_watermark.desc: -"""当 VM 套接字实现内部排队的数据量达到此限制时,套接字将设置为忙碌状态。""" - -fields_tcp_opts_high_watermark.label: -"""""" - -fields_mqtt_quic_listener_stateless_operation_expiration_ms.desc: -"""同一个对端的操作之间的时间限制,单位是毫秒。 默认:100""" - -fields_mqtt_quic_listener_stateless_operation_expiration_ms.label: -"""无状态操作过期 毫秒""" - -server_ssl_opts_schema_dhfile.desc: -"""如果协商使用Diffie-Hellman密钥交换的密码套件,则服务器将使用包含PEM编码的Diffie-Hellman参数的文件的路径。如果未指定,则使用默认参数。
-注意:TLS 1.3不支持dhfile选项。""" - -server_ssl_opts_schema_dhfile.label: -"""SSL dhfile""" - -flapping_detect_max_count.desc: -"""MQTT 客户端在“窗口”时间内允许的最大断开次数。""" - -flapping_detect_max_count.label: -"""最大断开次数""" - -mqtt_max_topic_levels.desc: -"""允许的最大主题层级。""" - -mqtt_max_topic_levels.label: -"""最大主题层级""" - -force_shutdown_max_heap_size.desc: -"""Heap 的总大小。""" - -force_shutdown_max_heap_size.label: -"""Heap 的总大小""" - -persistent_store_on_disc.desc: -"""将持久会话数据保存在磁盘上。如果为 false 则存储在内存中。 -如开启, 持久会话数据可在集群重启后恢复。 -如关闭, 数据仅存储在内存中, 则在整个集群停止后丢失。""" - -persistent_store_on_disc.label: -"""持久化在磁盘上""" - -mqtt_ignore_loop_deliver.desc: -"""设置由 MQTT v3.1.1/v3.1.0 客户端发布的消息是否将转发给其本身;类似 MQTT 5.0 协议中的 No Local 选项。""" - -mqtt_ignore_loop_deliver.label: -"""忽略循环投递""" - -common_ssl_opts_schema_certfile.desc: -"""PEM格式证书链文件
-此文件中的证书应与证书颁发链的顺序相反。也就是说,主机的证书应该放在文件的开头, -然后是直接颁发者 CA 证书,依此类推,一直到根 CA 证书。 -根 CA 证书是可选的,如果想要添加,应加到文件到最末端。""" - -common_ssl_opts_schema_certfile.label: -"""证书文件""" - -mqtt_exclusive_subscription.desc: -"""是否启用对 MQTT 排它订阅的支持。""" - -mqtt_exclusive_subscription.label: -"""排它订阅""" - -mqtt_retain_available.desc: -"""是否启用对 MQTT 保留消息的支持。""" - -mqtt_retain_available.label: -"""保留消息可用""" - -fields_tcp_opts_reuseaddr.desc: -"""连接的 SO_REUSEADDR 标识。""" - -fields_tcp_opts_reuseaddr.label: -"""SO_REUSEADDR""" - -sysmon_vm_long_schedule.desc: -"""启用后,如果 Erlang VM 调度器出现某个任务占用时间过长时,会触发一条带有 'long_schedule' 关键字的日志。 -同时还会发布一条主题为 $SYS/sysmon/long_schedule 的 MQTT 系统消息。""" - -sysmon_vm_long_schedule.label: -"""启用长调度监控""" - -mqtt_keepalive_multiplier.desc: -"""EMQX 判定客户端保活超时使用的阈值系数。计算公式为:Keep Alive * Backoff * 2""" - -mqtt_keepalive_multiplier.label: -"""保活超时阈值系数""" - -force_gc_bytes.desc: -"""在进程处理过多少个字节之后,对此进程执行垃圾回收。""" - -force_gc_bytes.label: -"""垃圾回收字节数""" - -server_ssl_opts_schema_fail_if_no_peer_cert.desc: -"""TLS/DTLS 服务器与 {verify,verify_peer} 一起使用。 -如果设置为true,则如果客户端没有要发送的证书,即发送空证书,服务器将失败。 -如果设置为false,则仅当客户端发送无效证书(空证书被视为有效证书)时才会失败。""" - -server_ssl_opts_schema_fail_if_no_peer_cert.label: -"""没有证书则 SSL 失败""" - -fields_ws_opts_compress.desc: -"""如果 true,则使用zlib 压缩 WebSocket 消息
-deflate_opts 下的配置项属于压缩相关参数配置。""" - -fields_ws_opts_compress.label: -"""Ws 压缩""" - -fields_mqtt_quic_listener_keep_alive_interval.desc: -"""发送 PING 帧的频率,以保活连接. 设为 0 表示禁用。""" - -fields_mqtt_quic_listener_keep_alive_interval.label: -"""PING 保活频率""" - -fields_cache_ttl.desc: -"""缓存数据的生存时间。""" - -fields_cache_ttl.label: -"""缓存数据的生存时间。""" - -sys_topics.desc: -"""系统主题配置。""" - -sys_event_client_subscribed.desc: -"""是否开启客户端已成功订阅主题事件消息。""" - -sysmon_top_db_port.desc: -"""收集数据点的 PostgreSQL 数据库的端口。""" - -sysmon_top_db_port.label: -"""数据库端口""" - -fields_mqtt_quic_listener_max_operations_per_drain.desc: -"""每个连接操作的最大耗费操作数。默认:16""" - -fields_mqtt_quic_listener_max_operations_per_drain.label: -"""每次操作最大操作数""" - -fields_mqtt_quic_listener_datagram_receive_enabled.desc: -"""宣传对QUIC Datagram 扩展的支持。为将来保留。默认为0(FALSE)""" - -fields_mqtt_quic_listener_datagram_receive_enabled.label: -"""启用 Datagram 接收""" - -fields_mqtt_quic_listener_initial_rtt_ms.desc: -"""初始RTT估计""" - -fields_mqtt_quic_listener_initial_rtt_ms.label: -"""Initial RTT 毫秒""" - -overload_protection_backoff_gc.desc: -"""高负载时,跳过强制 GC。""" - -overload_protection_backoff_gc.label: -"""跳过GC""" - -broker_perf_route_lock_type.desc: -"""通配主题订阅/取消订阅性能调优。 -建议仅当通配符主题较多时才更改此参数。 - -注:当从/更改为 `global` 锁时,它要求集群中的所有节点在更改之前停止。 - - `key`:为 Mnesia 事务涉及到的每个 key 上锁,建议单节点时使用。 - - `tab`:为 Mnesia 事务涉及到的表上锁,建议在集群中使用。 - - `global`:所以更新操作都被全局的锁保护,仅建议在超大规模集群中使用。""" - -fields_tcp_opts_nodelay.desc: -"""连接的 TCP_NODELAY 标识""" - -fields_tcp_opts_nodelay.label: -"""TCP_NODELAY""" - -sysmon_top_db_username.desc: -"""PostgreSQL 数据库的用户名""" - -sysmon_top_db_username.label: -"""数据库用户名""" - -broker.desc: -"""Broker 相关配置项。""" - -force_gc_count.desc: -"""在进程收到多少消息之后,对此进程执行垃圾回收。""" - -force_gc_count.label: -"""垃圾回收消息数""" - -mqtt_max_clientid_len.desc: -"""允许的最大 MQTT Client ID 长度。""" - -mqtt_max_clientid_len.label: -"""最大 Client ID 长度""" - -fields_ws_opts_supported_subprotocols.desc: -"""逗号分隔的 subprotocols 支持列表。""" - -fields_ws_opts_supported_subprotocols.label: -"""Subprotocols 支持列表""" - -broker_shared_subscription_strategy.desc: -"""共享订阅消息派发策略。 - - `random`:随机挑选一个共享订阅者派发; - - `round_robin`:使用 round-robin 策略派发; - - `round_robin_per_group`:在共享组内循环选择下一个成员; - - `local`:选择随机的本地成员,否则选择随机的集群范围内成员; - - `sticky`:总是使用上次选中的订阅者派发,直到它断开连接; - - `hash_clientid`:通过对发送者的客户端 ID 进行 Hash 处理来选择订阅者; - - `hash_topic`:通过对源主题进行 Hash 处理来选择订阅者。""" - -fields_deflate_opts_mem_level.desc: -"""指定压缩状态的大小
-较低的值会减少每个连接的内存使用。""" - -fields_deflate_opts_mem_level.label: -"""压缩状态大小""" - -fields_mqtt_quic_listener_send_idle_timeout_ms.desc: -"""在闲置一定时间后重置拥堵控制。默认值:1000""" - -fields_mqtt_quic_listener_send_idle_timeout_ms.label: -"""发送空闲超时毫秒""" - -base_listener_limiter.desc: -"""速率限制类型""" - -base_listener_limiter.label: -"""速率限制类型""" - -persistent_session_store_backend.desc: -"""用于存储持久性会话和信息的数据库管理后端 -- `builtin`: 使用内置的数据库(mria)""" - -persistent_session_store_backend.label: -"""后端类型""" - -alarm_validity_period.desc: -"""停用报警的保留时间。报警在停用时不会立即删除,而是在保留时间之后删除。""" - -alarm_validity_period.label: -"""告警保留时间""" - -server_ssl_opts_schema_ocsp_issuer_pem.desc: -"""服务器证书的 OCSP 签发者的 PEM 编码证书。""" - -server_ssl_opts_schema_ocsp_issuer_pem.label: -"""OCSP 签发者证书""" - -fields_tcp_opts_active_n.desc: -"""为此套接字指定{active,N}选项
-See: https://erlang.org/doc/man/inet.html#setopts-2""" - -fields_tcp_opts_active_n.label: -"""active_n""" - -listener_authentication.desc: -"""监听器认证重载。 -认证配置可以是单个认证器实例,也可以是一个认证器数组组成的认证链。 -执行登录验证时(用户名、客户端 ID 等),将按配置的顺序执行。""" - -listener_authentication.label: -"""每个监听器的认证覆盖""" - -fields_trace_payload_encode.desc: -"""确定跟踪文件中有效负载格式的格式。
-`text`:基于文本的协议或纯文本协议。 -建议在有效负载为JSON编码时使用
-`hex`:二进制十六进制编码。当有效负载是自定义二进制协议时,建议使用此选项
-`hidden`:有效负载被模糊化为 `******`""" - -fields_trace_payload_encode.label: -"""有效负载编码""" - -mqtt_response_information.desc: -"""UTF-8 字符串,用于指定返回给客户端的响应主题,如 reqrsp/,此时请求和应答客户端都需要使用 reqrsp/ 前缀的主题来完成通讯。 -如希望禁用此功能,请在下方的文字框中输入"";仅适用于 MQTT 5.0 客户端。""" - -mqtt_response_information.label: -"""响应信息""" - -persistent_session_store_max_retain_undelivered.desc: -"""如果重新启动时处理上一个会话的节点已停止,则未传递到持久会话的消息在垃圾收集之前会被存储。""" - -persistent_session_store_max_retain_undelivered.label: -"""未投递的消息保留条数""" - -fields_mqtt_quic_listener_migration_enabled.desc: -"""开启客户端地址迁移功能。需要一个支持的负载平衡器,或者没有负载平衡器。默认值:1(已启用)""" - -fields_mqtt_quic_listener_migration_enabled.label: -"""启用地址迁移""" - -common_ssl_opts_schema_password.desc: -"""包含用户密码的字符串。仅在私钥文件受密码保护时使用。""" - -common_ssl_opts_schema_password.label: -"""秘钥文件密码""" - -common_ssl_opts_schema_hibernate_after.desc: -"""在闲置一定时间后休眠 SSL 进程,减少其内存占用。""" - -common_ssl_opts_schema_hibernate_after.label: -"""闲置多久后休眠""" - -fields_mqtt_quic_listener_send_buffering_enabled.desc: -"""缓冲发送数据,而不是保留应用缓冲区,直到发送数据被确认。默认值:1(启用)""" - -fields_mqtt_quic_listener_send_buffering_enabled.label: -"""启用发送缓冲功能""" - -sys_event_client_unsubscribed.desc: -"""是否开启客户端已成功取消订阅主题事件消息。""" - -overload_protection_backoff_new_conn.desc: -"""高负载时,拒绝新进来的客户端连接。""" - -overload_protection_backoff_new_conn.label: -"""关闭新连接""" - -server_ssl_opts_schema_ocsp_responder_url.desc: -"""用于检查服务器证书的 OCSP Responder 的 URL。""" - -server_ssl_opts_schema_ocsp_responder_url.label: -"""OCSP Responder 的 URL""" - -mqtt_idle_timeout.desc: -"""设置连接被断开或进入休眠状态前的等待时间,空闲超时后, - - 如暂未收到客户端的 CONNECT 报文,连接将断开; - - 如已收到客户端的 CONNECT 报文,连接将进入休眠模式以节省系统资源。 - -注意:请合理设置该参数值,如等待时间设置过长,可能造成系统资源的浪费。""" - -mqtt_idle_timeout.label: -"""空闲超时""" - -fields_mqtt_quic_listener_conn_flow_control_window.desc: -"""连接的流控窗口。默认:16777216""" - -fields_mqtt_quic_listener_conn_flow_control_window.label: -"""流控窗口""" - -fields_mqtt_quic_listener_maximum_mtu.desc: -"""一个连接所支持的最大MTU。这将是最大的探测值。默认值:1500""" - -fields_mqtt_quic_listener_maximum_mtu.label: -"""最大 MTU""" - -sysmon_top_db_name.desc: -"""PostgreSQL 数据库的数据库名""" - -sysmon_top_db_name.label: -"""数据库名""" - -mqtt_strict_mode.desc: -"""是否以严格模式解析 MQTT 消息。 -严格模式下,如客户端 ID、主题名称等中包含无效 utf8 字符串,连接将被断开。""" - -mqtt_strict_mode.label: -"""严格模式""" - -shared_subscription_group_strategy.desc: -"""设置共享订阅组为单位的分发策略。该配置是一个从组名到 -策略名的一个map,组名不得包含 `[A-Za-z0-9]` 之外的特殊字符。""" - -fields_deflate_opts_strategy.desc: -"""指定压缩策略。""" - -fields_deflate_opts_strategy.label: -"""指定压缩策略""" - -shared_subscription_strategy_enum.desc: -"""共享订阅的分发策略名称。 -- `random`:随机选择一个组内成员; -- `round_robin`:循环选择下一个成员; -- `round_robin_per_group`:在共享组内循环选择下一个成员; -- `sticky`:使用上一次选中的成员; -- `hash`:根据 ClientID 哈希映射到一个成员; -- `local`:随机分发到节点本地成成员,如果本地成员不存在,则随机分发到任意一个成员。""" - -persistent_session_builtin_sess_msg_table.desc: -"""优化内置的会话消息表的配置。""" - -persistent_session_builtin_sess_msg_table.label: -"""用于内建会话管理表的性能调优参数""" - -mqtt_mqueue_store_qos0.desc: -"""指定在连接断开但会话保持期间,是否需要在消息队列中存储 QoS 0 消息。""" - -mqtt_mqueue_store_qos0.label: -"""存储 QoS 0 消息""" - -server_ssl_opts_schema_client_renegotiation.desc: -"""在支持客户机发起的重新协商的协议中,这种操作的资源成本对于服务器来说高于客户机。 -这可能会成为拒绝服务攻击的载体。 -SSL 应用程序已经采取措施来反击此类尝试,但通过将此选项设置为 false,可以严格禁用客户端发起的重新协商。 -默认值为 true。请注意,由于基础密码套件可以加密的消息数量有限,禁用重新协商可能会导致长期连接变得不可用。""" - -server_ssl_opts_schema_client_renegotiation.label: -"""SSL 客户端冲协商""" - -server_ssl_opts_schema_enable_crl_check.desc: -"""是否为该监听器启用 CRL 检查。""" - -server_ssl_opts_schema_enable_crl_check.label: -"""启用 CRL 检查""" - -fields_mqtt_quic_listener_peer_bidi_stream_count.desc: -"""允许对端打开的双向流的数量""" - -fields_mqtt_quic_listener_peer_bidi_stream_count.label: -"""对端双向流的数量""" - -fields_mqtt_quic_listener_max_stateless_operations.desc: -"""无状态操作的最大数量,在任何时候都可以在一个工作者上排队。默认值:16""" - -fields_mqtt_quic_listener_max_stateless_operations.label: -"""最大无状态操作数""" - -fields_ws_opts_idle_timeout.desc: -"""关闭在此间隔内未发送 MQTT CONNECT 消息的客户端的传输层连接。""" - -fields_ws_opts_idle_timeout.label: -"""WS 空闲时间""" - -fields_mqtt_quic_listener_max_ack_delay_ms.desc: -"""在收到数据后要等待多长时间才能发送一个ACK。默认值:25""" - -fields_mqtt_quic_listener_max_ack_delay_ms.label: -"""最大应答延迟 毫秒""" - -base_listener_zone.desc: -"""监听器所属的配置组。""" - -base_listener_zone.label: -"""配置组""" - -fields_mqtt_quic_listener_handshake_idle_timeout.desc: -"""一个握手在被丢弃之前可以空闲多长时间。""" - -fields_mqtt_quic_listener_handshake_idle_timeout.label: -"""握手空闲超时时间""" - -force_gc_enable.desc: -"""启用强制垃圾回收。""" - -force_gc_enable.label: -"""启用强制垃圾回收""" - -fields_ws_opts_allow_origin_absence.desc: -"""If false and check_origin_enable is true, the server will reject requests that don't have origin HTTP header.""" - -fields_ws_opts_allow_origin_absence.label: -"""允许 origin 缺失""" - -common_ssl_opts_schema_versions.desc: -"""支持所有TLS/DTLS版本
-注:PSK 的 Ciphers 无法在 tlsv1.3 中使用,如果打算使用 PSK 密码套件,请确保这里配置为 ["tlsv1.2","tlsv1.1"]。""" - -common_ssl_opts_schema_versions.label: -"""SSL 版本""" - -mqtt_listener_proxy_protocol_timeout.desc: -"""代理协议超时。如果在超时时间内未收到代理协议数据包,EMQX将关闭TCP连接。""" - -mqtt_listener_proxy_protocol_timeout.label: -"""Proxy protocol 超时时间""" - -fields_mqtt_quic_listener_idle_timeout.desc: -"""一个连接在被关闭之前可以空闲多长时间。0表示禁用。""" - -fields_mqtt_quic_listener_idle_timeout.label: -"""空闲超时时间""" - -common_ssl_opts_schema_secure_renegotiate.desc: -"""SSL 参数重新协商是一种允许客户端和服务器动态重新协商 SSL 连接参数的功能。 -RFC 5746 定义了一种更安全的方法。通过启用安全的重新协商,您就失去了对不安全的重新协商的支持,从而容易受到 MitM 攻击。""" - -common_ssl_opts_schema_secure_renegotiate.label: -"""SSL 重新协商""" - -sysmon_vm_busy_port.desc: -"""当一个系统接口(例如 TCP socket)过忙,会触发一条带有 busy_port 关键字的 warning 级别的日志。 -同时还会发布一条主题为 $SYS/sysmon/busy_port 的 MQTT 系统消息。""" - -sysmon_vm_busy_port.label: -"""启用端口过忙监控""" - -sys_event_client_connected.desc: -"""是否开启客户端已连接事件消息。""" - -sysmon_vm_process_low_watermark.desc: -"""在清除相应警报之前,本地节点上可以同时存在多少进程的阈值(以进程百分比表示)。""" - -sysmon_vm_process_low_watermark.label: -"""进程数低水位线""" - -mqtt_max_packet_size.desc: -"""允许的最大 MQTT 报文大小。""" - -mqtt_max_packet_size.label: -"""最大报文大小""" - -common_ssl_opts_schema_reuse_sessions.desc: -"""启用 TLS 会话重用。""" - -common_ssl_opts_schema_reuse_sessions.label: -"""TLS 会话重用""" - -common_ssl_opts_schema_depth.desc: -"""在有效的证书路径中,可以跟随对等证书的非自颁发中间证书的最大数量。 -因此,如果深度为0,则对等方必须由受信任的根 CA 直接签名;
-如果是1,路径可以是 PEER、中间 CA、ROOT-CA;
-如果是2,则路径可以是PEER、中间 CA1、中间 CA2、ROOT-CA。""" - -common_ssl_opts_schema_depth.label: -"""CA 证书深度""" - -sysmon_vm_long_gc.desc: -"""当系统检测到某个 Erlang 进程垃圾回收占用过长时间,会触发一条带有 long_gc 关键字的日志。 -同时还会发布一条主题为 $SYS/sysmon/long_gc 的 MQTT 系统消息。""" - -sysmon_vm_long_gc.label: -"""启用长垃圾回收监控""" - -fields_mqtt_quic_listener_keyfile.desc: -"""私钥文件。在 5.1 中会被废弃,使用 .ssl_options.keyfile 代替。""" - -fields_mqtt_quic_listener_keyfile.label: -"""私钥文件""" - -mqtt_peer_cert_as_clientid.desc: -"""使用对端证书中的 CN、DN 字段或整个证书内容来作为客户端 ID。仅适用于 TLS 连接; -目前支持: -- cn: 取证书的 CN 字段 -- dn: 取证书的 DN 字段 -- crt: 取 DERPEM 证书的内容 -- pem: 将 DER 证书内容转换为 PEM 格式作为客户端 ID -- md5: 取 DERPEM 证书内容的 MD5 值""" - -mqtt_peer_cert_as_clientid.label: -"""对端证书作为客户端 ID""" - -persistent_session_store_message_gc_interval.desc: -"""将未送达的消息垃圾收集到持久会话的开始间隔。 -这会影响检查 "max_retain_undelivered"(最大保留未送达)的删除频率。""" - -persistent_session_store_message_gc_interval.label: -"""消息清理间隔""" - -broker_shared_dispatch_ack_enabled.desc: -"""该配置项已废弃。 -启用/禁用 QoS 1 和 QoS 2 消息的共享派发确认。 -该配置最初设计用于避免将消息派发给客户端离线状态下的会话中去。 -但新版本中,已做增强:在一个会话结束时,会话中的消息会重新派发到组内的其他会话中 -- 使这个老配置失去存在的意义。""" - -base_listener_enable_authn.desc: -"""配置 true (默认值)启用客户端进行身份认证,通过检查认配置的认认证器链来决定是否允许接入。 -配置 false 时,将不对客户端做任何认证,任何客户端,不论是不是携带用户名等认证信息,都可以接入。 -配置 quick_deny_anonymous 时,行为跟 true 类似,但是会对匿名 -客户直接拒绝,不做使用任何认证器对客户端进行身份检查。""" - -base_listener_enable_authn.label: -"""启用身份认证""" - -force_shutdown_enable.desc: -"""启用 `force_shutdown` 功能。""" - -force_shutdown_enable.label: -"""启用 `force_shutdown` 功能""" - -broker_enable_session_registry.desc: -"""是否启用 Session Registry""" - -overload_protection_backoff_delay.desc: -"""高负载时,一些不重要的任务可能会延迟执行,在这里设置允许延迟的时间。""" - -overload_protection_backoff_delay.label: -"""延迟时间""" - -ciphers_schema_common.desc: -"""此配置保存由逗号分隔的 TLS 密码套件名称,或作为字符串数组。例如 -"TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256"或 -["TLS_AES_256_GCM_SHA384","TLS_AES_128_GCM_SHA256"]。 -
-密码(及其顺序)定义了客户端和服务器通过网络连接加密信息的方式。 -选择一个好的密码套件对于应用程序的数据安全性、机密性和性能至关重要。 - -名称应为 OpenSSL 字符串格式(而不是 RFC 格式)。 -EMQX 配置文档提供的所有默认值和示例都是 OpenSSL 格式
-注意:某些密码套件仅与特定的 TLS 版本兼容('tlsv1.1'、'tlsv1.2'或'tlsv1.3')。 -不兼容的密码套件将被自动删除。 - -例如,如果只有 versions 仅配置为 tlsv1.3。为其他版本配置密码套件将无效。 - -
-注:PSK 的 Ciphers 不支持 tlsv1.3
-如果打算使用PSK密码套件 tlsv1.3。应在ssl.versions中禁用。 - -
-PSK 密码套件: -"RSA-PSK-AES256-GCM-SHA384,RSA-PSK-AES256-CBC-SHA384, -RSA-PSK-AES128-GCM-SHA256,RSA-PSK-AES128-CBC-SHA256, -RSA-PSK-AES256-CBC-SHA,RSA-PSK-AES128-CBC-SHA, -RSA-PSK-DES-CBC3-SHA,RSA-PSK-RC4-SHA"""" - -ciphers_schema_common.label: -"""""" - -sys_event_client_disconnected.desc: -"""是否开启客户端已断开连接事件消息。""" - -crl_cache_refresh_interval.desc: -"""从服务器刷新CRL的周期。 该配置对所有 URL 和监听器有效。""" - -crl_cache_refresh_interval.label: -"""CRL 缓存刷新间隔""" - -mqtt_listener_proxy_protocol.desc: -"""如果EMQX集群部署在 HAProxy 或 Nginx 之后,请启用代理协议 V1/2
-详情见: https://www.haproxy.com/blog/haproxy/proxy-protocol/""" - -mqtt_listener_proxy_protocol.label: -"""Proxy protocol""" - -mqtt_listener_access_rules.desc: -"""此监听器的访问控制规则。""" - -mqtt_listener_access_rules.label: -"""访问控制规则""" - -server_ssl_opts_schema_enable_ocsp_stapling.desc: -"""是否为监听器启用 OCSP Stapling 功能。 如果设置为 true,需要定义 OCSP Responder 的 URL 和证书签发者的 PEM 文件路径。""" - -server_ssl_opts_schema_enable_ocsp_stapling.label: -"""启用 OCSP Stapling""" - -fields_tcp_opts_send_timeout_close.desc: -"""如果发送超时,则关闭连接。""" - -fields_tcp_opts_send_timeout_close.label: -"""TCP 发送超时关闭连接""" - -sysmon_os_cpu_check_interval.desc: -"""定期 CPU 检查的时间间隔。""" - -sysmon_os_cpu_check_interval.label: -"""定期 CPU 检查的时间间隔""" - -sysmon_top_sample_interval.desc: -"""指定应收集进程顶部的频率。""" - -sysmon_top_sample_interval.label: -"""取样时间""" - -fields_mqtt_quic_listener_idle_timeout_ms.desc: -"""一个连接在被优雅地关闭之前可以空闲多长时间。0 表示禁用超时""" - -fields_mqtt_quic_listener_idle_timeout_ms.label: -"""空闲超时 毫秒""" - -fields_ws_opts_fail_if_no_subprotocol.desc: -"""如果true,当客户端未携带Sec WebSocket Protocol字段时,服务器将返回一个错误。 -
注意:微信小程序需要禁用此验证。""" - -fields_ws_opts_fail_if_no_subprotocol.label: -"""无 subprotocol 则失败""" - -mqtt_wildcard_subscription.desc: -"""是否启用对 MQTT 通配符订阅的支持。""" - -mqtt_wildcard_subscription.label: -"""通配符订阅可用""" - -server_ssl_opts_schema_ocsp_refresh_interval.desc: -"""为服务器刷新OCSP响应的周期。""" - -server_ssl_opts_schema_ocsp_refresh_interval.label: -"""OCSP 刷新间隔""" - -overload_protection_backoff_hibernation.desc: -"""高负载时,跳过进程休眠。""" - -overload_protection_backoff_hibernation.label: -"""跳过休眠""" - -fields_ws_opts_max_frame_size.desc: -"""单个 MQTT 数据包的最大长度。""" - -fields_ws_opts_max_frame_size.label: -"""最大数据包长度""" - -sys_event_messages.desc: -"""客户端事件消息。""" - -broker_perf_trie_compaction.desc: -"""是否开启主题表压缩存储。 -启用它会显着提高通配符主题订阅率,如果通配符主题具有唯一前缀,例如:'sensor/{{id}}/+/',其中每个订阅者的 ID 是唯一的。 -如果消息主要发布到具有大量级别的主题,则主题匹配性能(发布时)可能会降低。 - -注意:这是一个集群范围的配置。 它要求在更改之前停止所有节点。""" - -sysmon_vm_large_heap.desc: -"""启用后,当一个 Erlang 进程申请了大量内存,系统会触发一条带有 large_heap 关键字的 -warning 级别日志。同时还会发布一条主题为 $SYS/sysmon/busy_dist_port 的 MQTT 系统消息。""" - -sysmon_vm_large_heap.label: -"""启用大 heap 监控""" - -} diff --git a/rel/i18n/zh/emqx_slow_subs_api.hocon b/rel/i18n/zh/emqx_slow_subs_api.hocon deleted file mode 100644 index b0e801ca3..000000000 --- a/rel/i18n/zh/emqx_slow_subs_api.hocon +++ /dev/null @@ -1,30 +0,0 @@ -emqx_slow_subs_api { - -clear_records_api.desc: -"""清除当前记录,然后重新开始统计""" - -clientid.desc: -"""消息的客户端 ID""" - -get_records_api.desc: -"""查看慢订阅的统计数据""" - -get_setting_api.desc: -"""查看配置""" - -last_update_time.desc: -"""记录的更新时间戳""" - -node.desc: -"""消息的节点名称""" - -timespan.desc: -"""消息的传输耗时""" - -topic.desc: -"""消息的主题""" - -update_setting_api.desc: -"""更新配置""" - -} diff --git a/rel/i18n/zh/emqx_slow_subs_schema.hocon b/rel/i18n/zh/emqx_slow_subs_schema.hocon deleted file mode 100644 index beadac1ea..000000000 --- a/rel/i18n/zh/emqx_slow_subs_schema.hocon +++ /dev/null @@ -1,18 +0,0 @@ -emqx_slow_subs_schema { - -enable.desc: -"""开启慢订阅""" - -expire_interval.desc: -"""慢订阅记录的有效时间""" - -stats_type.desc: -"""慢订阅的统计类型""" - -threshold.desc: -"""慢订阅统计的阈值""" - -top_k_num.desc: -"""慢订阅统计表的记录数量上限""" - -} diff --git a/rel/i18n/zh/emqx_stomp_schema.hocon b/rel/i18n/zh/emqx_stomp_schema.hocon deleted file mode 100644 index 13cfc0397..000000000 --- a/rel/i18n/zh/emqx_stomp_schema.hocon +++ /dev/null @@ -1,15 +0,0 @@ -emqx_stomp_schema { - -stom_frame_max_body_length.desc: -"""允许的 Stomp 报文 Body 的最大字节数""" - -stom_frame_max_headers.desc: -"""允许的 Header 最大数量""" - -stomp.desc: -"""Stomp 网关配置。当前实现支持 v1.2/1.1/1.0 协议版本""" - -stomp_frame_max_headers_length.desc: -"""允许的 Header 字符串的最大长度""" - -} diff --git a/rel/i18n/zh/emqx_telemetry_api.hocon b/rel/i18n/zh/emqx_telemetry_api.hocon deleted file mode 100644 index 4e445a56f..000000000 --- a/rel/i18n/zh/emqx_telemetry_api.hocon +++ /dev/null @@ -1,54 +0,0 @@ -emqx_telemetry_api { - -active_modules.desc: -"""获取活跃模块""" - -active_plugins.desc: -"""获取活跃插件""" - -emqx_version.desc: -"""获取 emqx 版本""" - -enable.desc: -"""启用遥测""" - -get_telemetry_data_api.desc: -"""获取遥测数据""" - -get_telemetry_status_api.desc: -"""获取遥测状态""" - -license.desc: -"""获取 license 信息""" - -messages_received.desc: -"""获取接收到的消息数量""" - -messages_sent.desc: -"""获取发送的消息数量""" - -nodes_uuid.desc: -"""获取节点 UUID""" - -num_clients.desc: -"""获取客户端数量""" - -os_name.desc: -"""获取操作系统名称""" - -os_version.desc: -"""获取操作系统版本""" - -otp_version.desc: -"""获取 OTP 版本""" - -up_time.desc: -"""获取运行时间""" - -update_telemetry_status_api.desc: -"""更新遥测状态""" - -uuid.desc: -"""获取 UUID""" - -} diff --git a/rel/i18n/zh/emqx_telemetry_schema.hocon b/rel/i18n/zh/emqx_telemetry_schema.hocon deleted file mode 100644 index 0fa238ee1..000000000 --- a/rel/i18n/zh/emqx_telemetry_schema.hocon +++ /dev/null @@ -1,8 +0,0 @@ -emqx_telemetry_schema { -telemetry_root_doc.desc: -"""配置 EMQX 节点向EMQ 的遥测服务器发送要测数据。 -详情请参考 https://www.emqx.io/docs/zh/v5.0/telemetry/telemetry.html。""" - -enable.desc: -"""设置为 `false` 可以关闭数据发送。""" -} diff --git a/rel/i18n/zh/emqx_topic_metrics_api.hocon b/rel/i18n/zh/emqx_topic_metrics_api.hocon deleted file mode 100644 index 23a5791b3..000000000 --- a/rel/i18n/zh/emqx_topic_metrics_api.hocon +++ /dev/null @@ -1,105 +0,0 @@ -emqx_topic_metrics_api { - -message_qos1_in_rate.desc: -"""QoS1 接收消息速率""" - -message_out_count.desc: -"""发送消息数量""" - -message_qos2_out_rate.desc: -"""QoS2 发送消息速率""" - -message_qos0_in_rate.desc: -"""QoS0 接收消息速率""" - -get_topic_metrics_api.desc: -"""获取主题监控数据""" - -reset_time.desc: -"""重置时间。标准 rfc3339 时间格式,例如:2018-01-01T12:00:00Z。如果从未重置则为空""" - -topic_metrics_api_response400.desc: -"""错误请求。已存在或错误的主题名称""" - -reset_topic_desc.desc: -"""主题名称。如果此参数不存在,则所有创建的主题监控数据都将重置。""" - -topic_metrics_api_response409.desc: -"""冲突。主题监控数据超过最大限制512""" - -post_topic_metrics_api.desc: -"""创建主题监控数据""" - -message_dropped_rate.desc: -"""丢弃消息速率""" - -message_qos2_in_rate.desc: -"""QoS2 接收消息速率""" - -message_in_rate.desc: -"""接收消息速率""" - -message_qos0_out_rate.desc: -"""QoS0 发送消息速率""" - -message_qos2_in_count.desc: -"""QoS2 接收消息数量""" - -message_dropped_count.desc: -"""丢弃消息数量""" - -topic_metrics_api_response404.desc: -"""未找到。主题监控数据未找到""" - -topic_in_path.desc: -"""主题字符串。注意:主题字符串在url路径中必须编码""" - -action.desc: -"""操作,仅支持 reset""" - -message_qos0_in_count.desc: -"""QoS0 接收消息数量""" - -message_qos1_out_rate.desc: -"""QoS1 发送消息速率""" - -topic.desc: -"""主题""" - -reset_topic_metrics_api.desc: -"""重置主题监控状态""" - -create_time.desc: -"""创建时间。标准 rfc3339 时间格式,例如:2018-01-01T12:00:00Z""" - -metrics.desc: -"""监控数据""" - -message_qos1_out_count.desc: -"""QoS1 发送消息数量""" - -gat_topic_metrics_data_api.desc: -"""获取主题监控数据""" - -message_qos1_in_count.desc: -"""QoS1 接收消息数量""" - -delete_topic_metrics_data_api.desc: -"""删除主题监控数据""" - -message_qos0_out_count.desc: -"""QoS0 发送消息数量""" - -topic_in_body.desc: -"""主题字符串""" - -message_in_count.desc: -"""接收消息数量""" - -message_qos2_out_count.desc: -"""QoS2 发送消息数量""" - -message_out_rate.desc: -"""发送消息速率""" - -} From d014b8083469d9f703ae839d534df037e7881e87 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Mon, 29 May 2023 14:06:23 +0200 Subject: [PATCH 21/72] build: download zh translation from emqx-i18n.git --- .../src/emqx_dashboard_desc_cache.erl | 9 ++-- .../emqx_license/src/emqx_license_schema.erl | 2 +- scripts/merge-config.escript | 41 +++++-------------- scripts/pre-compile.sh | 9 ++++ 4 files changed, 23 insertions(+), 38 deletions(-) diff --git a/apps/emqx_dashboard/src/emqx_dashboard_desc_cache.erl b/apps/emqx_dashboard/src/emqx_dashboard_desc_cache.erl index b503fed88..fcabd3688 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard_desc_cache.erl +++ b/apps/emqx_dashboard/src/emqx_dashboard_desc_cache.erl @@ -27,15 +27,12 @@ %% @doc Global ETS table to cache the description of the configuration items. %% The table is owned by the emqx_dashboard_sup the root supervisor of emqx_dashboard. %% The cache is initialized with the default language (English) and -%% all the desc..hocon files in the www/static directory (extracted from dashboard package). +%% all the desc..hocon files in the app's priv directory init() -> ok = ensure_app_loaded(emqx_dashboard), PrivDir = code:priv_dir(emqx_dashboard), - EngDesc = filename:join([PrivDir, "desc.en.hocon"]), - WwwStaticDir = filename:join([PrivDir, "www", "static"]), - OtherLangDesc0 = filelib:wildcard("desc.*.hocon", WwwStaticDir), - OtherLangDesc = lists:map(fun(F) -> filename:join([WwwStaticDir, F]) end, OtherLangDesc0), - Files = [EngDesc | OtherLangDesc], + Files0 = filelib:wildcard("desc.*.hocon", PrivDir), + Files = lists:map(fun(F) -> filename:join([PrivDir, F]) end, Files0), ok = emqx_utils_ets:new(?MODULE, [public, ordered_set, {read_concurrency, true}]), ok = lists:foreach(fun(F) -> load_desc(?MODULE, F) end, Files). diff --git a/lib-ee/emqx_license/src/emqx_license_schema.erl b/lib-ee/emqx_license/src/emqx_license_schema.erl index 7383af92c..bb7868d8d 100644 --- a/lib-ee/emqx_license/src/emqx_license_schema.erl +++ b/lib-ee/emqx_license/src/emqx_license_schema.erl @@ -82,7 +82,7 @@ check_license_watermark(Conf) -> %% @doc The default license key. %% This default license has 1000 connections limit. %% It is issued on 2023-01-09 and valid for 5 years (1825 days) -%% NOTE: when updating a new key, the schema doc in emqx_license_schema_i18n.conf +%% NOTE: when updating a new key, the schema doc in emqx_license_schema.hocon %% should be updated accordingly default_license() -> << diff --git a/scripts/merge-config.escript b/scripts/merge-config.escript index 812bfea2c..014183eda 100755 --- a/scripts/merge-config.escript +++ b/scripts/merge-config.escript @@ -19,9 +19,7 @@ main(_) -> io_lib:nl() ], ok = file:write_file("apps/emqx_conf/etc/emqx.conf.all", Conf), - merge_desc_files_per_lang("en"), - %% TODO: remove this when we have zh translation moved to dashboard package - merge_desc_files_per_lang("zh"). + merge_desc_files(). merge(BaseConf, Cfgs) -> Confs = [BaseConf | lists:map(fun read_conf/1, Cfgs)], @@ -80,29 +78,17 @@ try_enter_child(Dir, Files, Cfgs) -> get_all_cfgs(filename:join([Dir, "src"]), Cfgs) end. -%% Desc files merge is for now done locally in emqx.git repo for all languages. -%% When zh and other languages are moved to a separate repo, -%% we will only merge the en files. -%% The file for other languages will be merged in the other repo, -%% the built as a part of the dashboard package, -%% finally got pulled at build time as a part of the dashboard package. -merge_desc_files_per_lang(Lang) -> +%% Merge English descriptions. +%% other translations are downloaded in pre-compile.sh +merge_desc_files() -> BaseConf = <<"">>, - Cfgs0 = get_all_desc_files(Lang), - Conf = do_merge_desc_files_per_lang(BaseConf, Cfgs0), - OutputFile = case Lang of - "en" -> - %% en desc will always be in the priv dir of emqx_dashboard - "apps/emqx_dashboard/priv/desc.en.hocon"; - "zh" -> - %% so far we inject zh desc as if it's extracted from dashboard package - %% TODO: remove this when we have zh translation moved to dashboard package - "apps/emqx_dashboard/priv/www/static/desc.zh.hocon" - end, + Cfgs0 = get_all_desc_files(), + Conf = do_merge_desc_files(BaseConf, Cfgs0), + OutputFile = "apps/emqx_dashboard/priv/desc.en.hocon", ok = filelib:ensure_dir(OutputFile), ok = file:write_file(OutputFile, Conf). -do_merge_desc_files_per_lang(BaseConf, Cfgs) -> +do_merge_desc_files(BaseConf, Cfgs) -> lists:foldl( fun(CfgFile, Acc) -> case filelib:is_regular(CfgFile) of @@ -113,14 +99,7 @@ do_merge_desc_files_per_lang(BaseConf, Cfgs) -> end end, BaseConf, Cfgs). -get_all_desc_files(Lang) -> - Dir = - case Lang of - "en" -> - filename:join(["rel", "i18n"]); - "zh" -> - %% TODO: remove this when we have zh translation moved to dashboard package - filename:join(["rel", "i18n", "zh"]) - end, +get_all_desc_files() -> + Dir = filename:join(["rel", "i18n"]), Files = filelib:wildcard("*.hocon", Dir), lists:map(fun(Name) -> filename:join([Dir, Name]) end, Files). diff --git a/scripts/pre-compile.sh b/scripts/pre-compile.sh index 71251a03e..1700dd8a4 100755 --- a/scripts/pre-compile.sh +++ b/scripts/pre-compile.sh @@ -19,5 +19,14 @@ esac cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")/.." ./scripts/get-dashboard.sh "$dashboard_version" + +# generate merged config files and English translation of the desc (desc.en.hocon) ./scripts/merge-config.escript + +# download desc (i18n) translations +curl -L --silent --show-error \ + --output "apps/emqx_dashboard/priv/desc.zh.hocon" \ + 'https://raw.githubusercontent.com/emqx/emqx-i18n/main/desc.zh.hocon' + +# generate sbom ./scripts/update-bom.sh "$PROFILE_STR" ./rel From 08c1d5e5aee95a71e4dcca7c5662046563478091 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Mon, 29 May 2023 14:18:03 +0200 Subject: [PATCH 22/72] test: stop testing i18n.conf --- apps/emqx_conf/test/emqx_conf_schema_tests.erl | 12 +----------- .../test/emqx_dashboard_api_test_helpers.erl | 5 ----- .../test/emqx_enterprise_schema_tests.erl | 12 +----------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/apps/emqx_conf/test/emqx_conf_schema_tests.erl b/apps/emqx_conf/test/emqx_conf_schema_tests.erl index 32c66fb90..d274052f9 100644 --- a/apps/emqx_conf/test/emqx_conf_schema_tests.erl +++ b/apps/emqx_conf/test/emqx_conf_schema_tests.erl @@ -414,17 +414,7 @@ doc_gen_test() -> 60, fun() -> Dir = "tmp", - ok = filelib:ensure_dir(filename:join("tmp", foo)), - I18nFile = filename:join([ - "_build", - "test", - "lib", - "emqx_dashboard", - "priv", - "i18n.conf" - ]), - _ = emqx_conf:dump_schema(Dir, emqx_conf_schema, I18nFile), - ok + ok = emqx_conf:dump_schema(Dir, emqx_conf_schema) end }. diff --git a/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl b/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl index 908d7e0ef..166d2927c 100644 --- a/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl +++ b/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl @@ -64,11 +64,6 @@ set_default_config(DefaultUsername, HAProxyEnabled, Opts) -> i18n_lang => en }, emqx_config:put([dashboard], Config), - I18nFile = filename:join([ - code:priv_dir(emqx_dashboard), - "i18n.conf" - ]), - application:set_env(emqx_dashboard, i18n_file, I18nFile), ok. request(Method, Url) -> diff --git a/apps/emqx_enterprise/test/emqx_enterprise_schema_tests.erl b/apps/emqx_enterprise/test/emqx_enterprise_schema_tests.erl index a78bbcb2e..955f358f3 100644 --- a/apps/emqx_enterprise/test/emqx_enterprise_schema_tests.erl +++ b/apps/emqx_enterprise/test/emqx_enterprise_schema_tests.erl @@ -13,16 +13,6 @@ doc_gen_test() -> 60, fun() -> Dir = "tmp", - ok = filelib:ensure_dir(filename:join("tmp", foo)), - I18nFile = filename:join([ - "_build", - "test", - "lib", - "emqx_dashboard", - "priv", - "i18n.conf" - ]), - _ = emqx_conf:dump_schema(Dir, emqx_enterprise_schema, I18nFile), - ok + ok = emqx_conf:dump_schema(Dir, emqx_enterprise_schema) end }. From 995025b5728e6c9a8bf833f5cab554b06ed4d60e Mon Sep 17 00:00:00 2001 From: Kjell Winblad Date: Mon, 29 May 2023 14:22:58 +0200 Subject: [PATCH 23/72] feat: add timezone_to_second/1 function to rule engine This commit adds the functions timezone_to_offset_seconds and its alias timezone_to_second to the rule engine. As the names suggests, these functions convert a timzone offset string such as "+02:00", "Z", "local" to an integer representing how many seconds that the given timezone differs from UTC. timezone_to_offset_seconds is the one of the two functions that should be advertised while timezone_to_second in kept for backwards compatibility with 4.X. Fixes: https://emqx.atlassian.net/browse/EMQX-10058 --- apps/emqx_machine/src/emqx_machine.app.src | 2 +- apps/emqx_rule_engine/src/emqx_rule_funcs.erl | 10 +++++++++- .../test/emqx_rule_funcs_SUITE.erl | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/emqx_machine/src/emqx_machine.app.src b/apps/emqx_machine/src/emqx_machine.app.src index 7cf0e4b53..5cfa80369 100644 --- a/apps/emqx_machine/src/emqx_machine.app.src +++ b/apps/emqx_machine/src/emqx_machine.app.src @@ -3,7 +3,7 @@ {id, "emqx_machine"}, {description, "The EMQX Machine"}, % strict semver, bump manually! - {vsn, "0.2.4"}, + {vsn, "0.2.5"}, {modules, []}, {registered, []}, {applications, [kernel, stdlib, emqx_ctl]}, diff --git a/apps/emqx_rule_engine/src/emqx_rule_funcs.erl b/apps/emqx_rule_engine/src/emqx_rule_funcs.erl index 73e2f78e7..a94aa0c8a 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_funcs.erl +++ b/apps/emqx_rule_engine/src/emqx_rule_funcs.erl @@ -228,7 +228,9 @@ format_date/3, format_date/4, date_to_unix_ts/3, - date_to_unix_ts/4 + date_to_unix_ts/4, + timezone_to_second/1, + timezone_to_offset_seconds/1 ]). %% MongoDB specific date functions. These functions return a date tuple. The @@ -1104,6 +1106,12 @@ date_to_unix_ts(TimeUnit, Offset, FormatString, InputString) -> OffsetDelta = erlang:convert_time_unit(OffsetSecond, second, Unit), date_to_unix_ts(Unit, FormatString, InputString) - OffsetDelta. +timezone_to_second(TimeZone) -> + timezone_to_offset_seconds(TimeZone). + +timezone_to_offset_seconds(TimeZone) -> + emqx_calendar:offset_second(TimeZone). + %% @doc This is for sql funcs that should be handled in the specific modules. %% Here the emqx_rule_funcs module acts as a proxy, forwarding %% the function handling to the worker module. diff --git a/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl b/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl index 9bb6e2e6f..525d60503 100644 --- a/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl +++ b/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl @@ -1012,6 +1012,25 @@ prop_format_date_fun() -> Args3DTUS = [<<"second">>, <<"+04:00">>, <<"--%m--%d--%Y---%H:%M:%S">>, Formatters3], Second == apply_func(date_to_unix_ts, Args3DTUS). +t_timezone_to_offset_seconds(_) -> + t_timezone_to_offset_seconds_helper(timezone_to_offset_seconds), + %% The timezone_to_second function is kept for compatibility with 4.X. + t_timezone_to_offset_seconds_helper(timezone_to_second). + +t_timezone_to_offset_seconds_helper(FunctionName) -> + ?assertEqual(120 * 60, apply_func(FunctionName, [<<"+02:00:00">>])), + ?assertEqual(-120 * 60, apply_func(FunctionName, [<<"-02:00:00">>])), + ?assertEqual(102, apply_func(FunctionName, [<<"+00:01:42">>])), + ?assertEqual(0, apply_func(FunctionName, [<<"z">>])), + ?assertEqual(0, apply_func(FunctionName, [<<"Z">>])), + ?assertEqual(42, apply_func(FunctionName, [42])), + ?assertEqual(0, apply_func(FunctionName, [undefined])), + %% Check that the following does not crash + apply_func(FunctionName, [<<"local">>]), + apply_func(FunctionName, ["local"]), + apply_func(FunctionName, [local]), + ok. + %%------------------------------------------------------------------------------ %% Utility functions %%------------------------------------------------------------------------------ From 1f5714b3f01b169deaf1cfac56ffdbb6985f53ff Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Mon, 29 May 2023 09:31:30 -0300 Subject: [PATCH 24/72] docs: Generate changelog for v5.0.26 --- changes/v5.0.26.en.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/changes/v5.0.26.en.md b/changes/v5.0.26.en.md index c79131bec..54f40eddb 100644 --- a/changes/v5.0.26.en.md +++ b/changes/v5.0.26.en.md @@ -1,4 +1,4 @@ -# +# v5.0.26 ## Enhancements @@ -105,3 +105,14 @@ - [#10817](https://github.com/emqx/emqx/pull/10817) Fix the error of not being able to configure `auto_restart_interval` as infinity - [#10818](https://github.com/emqx/emqx/pull/10818) Fixing `emqx_ctl traces` command. + +- [#10820](https://github.com/emqx/emqx/pull/10820) In case the cluster updated license before the new node join in. The new node will not apply the updated license. + After this change, the new joined node will use the cluster's license key. + + Sometimes the new node must start with a outdated license. + e.g. use emqx-operator deployed and needed to scale up after license expired. + At the time the cluster's license key already updated by API/CLI, but the new node won't use it. + +- [#10833](https://github.com/emqx/emqx/pull/10833) Only include enabled authenticators and authorizers in telemetry report, not all of them. + +- [#10851](https://github.com/emqx/emqx/pull/10851) Obfuscated sensitive data in the bad API logging. From b87e0a2cdd6b5aa6807fd612826e3218c4c0bb85 Mon Sep 17 00:00:00 2001 From: Kjell Winblad Date: Mon, 29 May 2023 14:45:37 +0200 Subject: [PATCH 25/72] docs: add changelog entry --- changes/ce/feat-10858.en.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/ce/feat-10858.en.md diff --git a/changes/ce/feat-10858.en.md b/changes/ce/feat-10858.en.md new file mode 100644 index 000000000..1e2fa9fee --- /dev/null +++ b/changes/ce/feat-10858.en.md @@ -0,0 +1 @@ +A new utility function timezone_to_offset_seconds/1 has been added to the rule engine SQL language. This function converts a timezone string (for example, "+02:00", "Z" and "local") to the corresponding offset in seconds. From 3edbad9f564d5755796a73d39b28c6921d036aaa Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Fri, 26 May 2023 17:50:32 -0300 Subject: [PATCH 26/72] feat(pulsar_producer): add validation for empty message key when strategy = key_dispatch --- .../src/emqx_bridge_pulsar.erl | 10 +++ .../test/emqx_bridge_pulsar_tests.erl | 75 +++++++++++++++++++ changes/ee/feat-10841.en.md | 2 +- lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.erl | 3 +- rebar.config.erl | 1 + 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_tests.erl diff --git a/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar.erl b/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar.erl index 721937cd2..602e9cfdd 100644 --- a/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar.erl +++ b/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar.erl @@ -18,6 +18,8 @@ %% emqx_ee_bridge "unofficial" API -export([conn_bridge_examples/1]). +-export([producer_strategy_key_validator/1]). + %%------------------------------------------------------------------------------------------------- %% `hocon_schema' API %%------------------------------------------------------------------------------------------------- @@ -218,6 +220,14 @@ conn_bridge_examples(_Method) -> } ]. +producer_strategy_key_validator(#{ + <<"strategy">> := key_dispatch, + <<"message">> := #{<<"key">> := ""} +}) -> + {error, "Message key cannot be empty when `key_dispatch` strategy is used"}; +producer_strategy_key_validator(_) -> + ok. + %%------------------------------------------------------------------------------------------------- %% Internal fns %%------------------------------------------------------------------------------------------------- diff --git a/apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_tests.erl b/apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_tests.erl new file mode 100644 index 000000000..d46f2af6f --- /dev/null +++ b/apps/emqx_bridge_pulsar/test/emqx_bridge_pulsar_tests.erl @@ -0,0 +1,75 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%%-------------------------------------------------------------------- + +-module(emqx_bridge_pulsar_tests). + +-include_lib("eunit/include/eunit.hrl"). + +%%=========================================================================== +%% Test cases +%%=========================================================================== + +pulsar_producer_validations_test() -> + Conf0 = pulsar_producer_hocon(), + Conf1 = + Conf0 ++ + "\n" + "bridges.pulsar_producer.my_producer.strategy = key_dispatch" + "\n" + "bridges.pulsar_producer.my_producer.message.key = \"\"", + Conf = parse(Conf1), + ?assertMatch( + #{ + <<"strategy">> := <<"key_dispatch">>, + <<"message">> := #{<<"key">> := <<>>} + }, + emqx_utils_maps:deep_get([<<"bridges">>, <<"pulsar_producer">>, <<"my_producer">>], Conf) + ), + ?assertThrow( + {_, [ + #{ + path := "bridges.pulsar_producer.my_producer", + reason := "Message key cannot be empty when `key_dispatch` strategy is used" + } + ]}, + check(Conf) + ), + + ok. + +%%=========================================================================== +%% Helper functions +%%=========================================================================== + +parse(Hocon) -> + {ok, Conf} = hocon:binary(Hocon), + Conf. + +check(Conf) when is_map(Conf) -> + hocon_tconf:check_plain(emqx_bridge_schema, Conf). + +%%=========================================================================== +%% Data section +%%=========================================================================== + +%% erlfmt-ignore +pulsar_producer_hocon() -> +""" +bridges.pulsar_producer.my_producer { + enable = true + servers = \"localhost:6650\" + pulsar_topic = pulsar_topic + strategy = random + message { + key = \"${.clientid}\" + value = \"${.}\" + } + authentication = none + ssl { + enable = false + verify = verify_none + server_name_indication = \"auto\" + } +} +""". diff --git a/changes/ee/feat-10841.en.md b/changes/ee/feat-10841.en.md index 3bf8daa24..2bcf25c63 100644 --- a/changes/ee/feat-10841.en.md +++ b/changes/ee/feat-10841.en.md @@ -1 +1 @@ -Added a schema validation to ensure message key is not empty when dispatching by key in Kafka Producer bridge. +Added a schema validation to ensure message key is not empty when dispatching by key in Kafka and Pulsar Producer bridges. diff --git a/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.erl b/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.erl index 3636e3eb2..be42a913a 100644 --- a/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.erl +++ b/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.erl @@ -242,7 +242,8 @@ pulsar_structs() -> hoconsc:map(name, ref(emqx_bridge_pulsar, pulsar_producer)), #{ desc => <<"Pulsar Producer Bridge Config">>, - required => false + required => false, + validator => fun emqx_bridge_pulsar:producer_strategy_key_validator/1 } )} ]. diff --git a/rebar.config.erl b/rebar.config.erl index 933e87181..843db9605 100644 --- a/rebar.config.erl +++ b/rebar.config.erl @@ -91,6 +91,7 @@ is_community_umbrella_app("apps/emqx_bridge_matrix") -> false; is_community_umbrella_app("apps/emqx_bridge_mongodb") -> false; is_community_umbrella_app("apps/emqx_bridge_mysql") -> false; is_community_umbrella_app("apps/emqx_bridge_pgsql") -> false; +is_community_umbrella_app("apps/emqx_bridge_pulsar") -> false; is_community_umbrella_app("apps/emqx_bridge_redis") -> false; is_community_umbrella_app("apps/emqx_bridge_rocketmq") -> false; is_community_umbrella_app("apps/emqx_bridge_tdengine") -> false; From 354603da019317b38b1ffc797f019aa6c33a7fd8 Mon Sep 17 00:00:00 2001 From: Kjell Winblad Date: Mon, 29 May 2023 17:20:37 +0200 Subject: [PATCH 27/72] test: helper function was interpreted as a test function --- apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl b/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl index 525d60503..12110b206 100644 --- a/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl +++ b/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl @@ -1013,11 +1013,11 @@ prop_format_date_fun() -> Second == apply_func(date_to_unix_ts, Args3DTUS). t_timezone_to_offset_seconds(_) -> - t_timezone_to_offset_seconds_helper(timezone_to_offset_seconds), + timezone_to_offset_seconds_helper(timezone_to_offset_seconds), %% The timezone_to_second function is kept for compatibility with 4.X. - t_timezone_to_offset_seconds_helper(timezone_to_second). + timezone_to_offset_seconds_helper(timezone_to_second). -t_timezone_to_offset_seconds_helper(FunctionName) -> +timezone_to_offset_seconds_helper(FunctionName) -> ?assertEqual(120 * 60, apply_func(FunctionName, [<<"+02:00:00">>])), ?assertEqual(-120 * 60, apply_func(FunctionName, [<<"-02:00:00">>])), ?assertEqual(102, apply_func(FunctionName, [<<"+00:01:42">>])), From d12c393abcda426d79cb1344294b49ef6cdc5824 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Mon, 29 May 2023 16:46:26 -0300 Subject: [PATCH 28/72] ci(docker): don't fetch deps outside docker build context The dependencies outside EMQX are symlinked with full paths from the GH action environment. When we run `buildx`, the paths are all wrong, and the documentation generation fails when it tries to prepare the library paths. --- .github/workflows/build_and_push_docker_images.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build_and_push_docker_images.yaml b/.github/workflows/build_and_push_docker_images.yaml index 64fc6d5b0..8c426b2dc 100644 --- a/.github/workflows/build_and_push_docker_images.yaml +++ b/.github/workflows/build_and_push_docker_images.yaml @@ -97,7 +97,6 @@ jobs: env: PROFILE: ${{ steps.get_profile.outputs.PROFILE }} run: | - PROFILE=$PROFILE make -C source deps-$PROFILE zip -ryq source.zip source/* source/.[^.]* - uses: actions/upload-artifact@v3 with: From fa508a95c1309c85d6f1057ceceeb5e2d36ac07e Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Mon, 29 May 2023 18:09:35 -0300 Subject: [PATCH 29/72] test(telemetry): fix flaky test ``` %%% emqx_telemetry_SUITE ==> Failure/Error: ?assertEqual(0, get_value ( num_clients , TelemetryData1 )) expected: 0 got: 1 line: 454 ``` --- apps/emqx_telemetry/test/emqx_telemetry_SUITE.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/emqx_telemetry/test/emqx_telemetry_SUITE.erl b/apps/emqx_telemetry/test/emqx_telemetry_SUITE.erl index 65604c4e5..3ef2142e8 100644 --- a/apps/emqx_telemetry/test/emqx_telemetry_SUITE.erl +++ b/apps/emqx_telemetry/test/emqx_telemetry_SUITE.erl @@ -430,25 +430,25 @@ t_num_clients(_Config) -> {port, 1883}, {clean_start, false} ]), - {{ok, _}, _} = ?wait_async_action( + {{ok, _}, {ok, _}} = ?wait_async_action( {ok, _} = emqtt:connect(Client), #{ ?snk_kind := emqx_stats_setstat, count_stat := 'live_connections.count', value := 1 }, - 2000 + 5_000 ), {ok, TelemetryData0} = emqx_telemetry:get_telemetry(), ?assertEqual(1, get_value(num_clients, TelemetryData0)), - {ok, _} = ?wait_async_action( + {ok, {ok, _}} = ?wait_async_action( ok = emqtt:disconnect(Client), #{ ?snk_kind := emqx_stats_setstat, count_stat := 'live_connections.count', value := 0 }, - 2000 + 5_000 ), {ok, TelemetryData1} = emqx_telemetry:get_telemetry(), ?assertEqual(0, get_value(num_clients, TelemetryData1)), From bae00c909ff784eec14930c4917e6fc2dbd1358c Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Mon, 29 May 2023 18:19:26 -0300 Subject: [PATCH 30/72] test(cassandra): attempt to fix flaky test ``` =ERROR REPORT==== 29-May-2023::20:35:20.457796 === id: <<"bridge:cassandra:emqx_bridge_cassandra_SUITE">> msg: unrecoverable_error reason: {8704,<<"Expected 8 or 0 byte long for date (4)">>} =CRITICAL REPORT==== 29-May-2023::20:35:20.458850 === "check stage" failed: error {assertMatch,[{module,emqx_bridge_cassandra_SUITE}, {line,619}, {expression,"Trace"}, {pattern,"[ # { result := { ok , _Pid } } ]"}, {value,[]}]} Stacktrace: [{emqx_bridge_cassandra_SUITE,'-t_missing_data/1-fun-3-',1, [{file, "/emqx/apps/emqx_bridge_cassandra/test/emqx_bridge_cassandra_SUITE.erl"}, {line,619}]}, {emqx_bridge_cassandra_SUITE,t_missing_data,1, [{file, "/emqx/apps/emqx_bridge_cassandra/test/emqx_bridge_cassandra_SUITE.erl"}, {line,607}]}] =CRITICAL REPORT==== 29-May-2023::20:35:20.471872 === Check stage failed. Trace dump: "/emqx/_build/test/logs/ct_run.test@127.0.0.1.2023-05-29_20.34.32/snabbkaffe/1685392520460040358.log" %%% emqx_bridge_cassandra_SUITE ==> tcp.sync.with_batch.t_missing_data: FAILED %%% emqx_bridge_cassandra_SUITE ==> {{panic,#{msg => "Unexpected result",result => {error,check_stage_failed}}}, [{emqx_bridge_cassandra_SUITE,t_missing_data,1, [{file,"/emqx/apps/emqx_bridge_cassandra/test/emqx_bridge_cassandra_SUITE.erl"}, {line,624}]}, {test_server,ts_tc,3,[{file,"test_server.erl"},{line,1782}]}, {test_server,run_test_case_eval1,6,[{file,"test_server.erl"},{line,1291}]}, {test_server,run_test_case_eval,9,[{file,"test_server.erl"},{line,1223}]}]} ``` --- .../test/emqx_bridge_cassandra_SUITE.erl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/emqx_bridge_cassandra/test/emqx_bridge_cassandra_SUITE.erl b/apps/emqx_bridge_cassandra/test/emqx_bridge_cassandra_SUITE.erl index 8f093ef5c..c74d6cdd1 100644 --- a/apps/emqx_bridge_cassandra/test/emqx_bridge_cassandra_SUITE.erl +++ b/apps/emqx_bridge_cassandra/test/emqx_bridge_cassandra_SUITE.erl @@ -606,11 +606,12 @@ t_missing_data(Config) -> %% to ecql driver ?check_trace( begin - ?wait_async_action( - send_message(Config, #{}), - #{?snk_kind := handle_async_reply, result := {error, {8704, _}}}, - 10_000 - ), + {_, {ok, _}} = + ?wait_async_action( + send_message(Config, #{}), + #{?snk_kind := handle_async_reply, result := {error, {8704, _}}}, + 30_000 + ), ok end, fun(Trace0) -> From 1aff46b7c9df63ad49ae38a843f59fb9de688bea Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Mon, 29 May 2023 19:04:48 -0300 Subject: [PATCH 31/72] test(channel): fix flaky test --- apps/emqx/test/emqx_channel_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqx/test/emqx_channel_SUITE.erl b/apps/emqx/test/emqx_channel_SUITE.erl index 6f14293ed..9f73ab163 100644 --- a/apps/emqx/test/emqx_channel_SUITE.erl +++ b/apps/emqx/test/emqx_channel_SUITE.erl @@ -892,7 +892,7 @@ t_handle_kicked_publish_will_msg(_) -> ), receive {pub, Msg} -> ok - after 200 -> exit(will_message_not_published) + after 1_000 -> exit(will_message_not_published) end. t_handle_call_discard(_) -> From bb363e9b9e22b9f7813bb55dab4de648a1700c30 Mon Sep 17 00:00:00 2001 From: JimMoen Date: Fri, 12 May 2023 09:58:35 +0800 Subject: [PATCH 32/72] chore: bump dependency vsn * bump `snappyer` ~> 1.2.9 - as dependency of `kafka_protocol` ~> 4.1.3 - as dependency of `brod` * bump `rocksdb` ~> 1.7.2-emqx-11 --- apps/emqx_bridge_kafka/rebar.config | 3 ++- mix.exs | 6 +++--- rebar.config | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/emqx_bridge_kafka/rebar.config b/apps/emqx_bridge_kafka/rebar.config index 68a8d3e69..cfe84a2a8 100644 --- a/apps/emqx_bridge_kafka/rebar.config +++ b/apps/emqx_bridge_kafka/rebar.config @@ -1,9 +1,10 @@ %% -*- mode: erlang; -*- {erl_opts, [debug_info]}. {deps, [ {wolff, {git, "https://github.com/kafka4beam/wolff.git", {tag, "1.7.5"}}} - , {kafka_protocol, {git, "https://github.com/kafka4beam/kafka_protocol.git", {tag, "4.1.2"}}} + , {kafka_protocol, {git, "https://github.com/kafka4beam/kafka_protocol.git", {tag, "4.1.3"}}} , {brod_gssapi, {git, "https://github.com/kafka4beam/brod_gssapi.git", {tag, "v0.1.0"}}} , {brod, {git, "https://github.com/kafka4beam/brod.git", {tag, "3.16.8"}}} + , {snappyer, "1.2.9"} , {emqx_connector, {path, "../../apps/emqx_connector"}} , {emqx_resource, {path, "../../apps/emqx_resource"}} , {emqx_bridge, {path, "../../apps/emqx_bridge"}} diff --git a/mix.exs b/mix.exs index 3eab0d70d..2e2882e15 100644 --- a/mix.exs +++ b/mix.exs @@ -54,7 +54,7 @@ defmodule EMQXUmbrella.MixProject do {:jiffy, github: "emqx/jiffy", tag: "1.0.5", override: true}, {:cowboy, github: "emqx/cowboy", tag: "2.9.0", override: true}, {:esockd, github: "emqx/esockd", tag: "5.9.6", override: true}, - {:rocksdb, github: "emqx/erlang-rocksdb", tag: "1.7.2-emqx-9", override: true}, + {:rocksdb, github: "emqx/erlang-rocksdb", tag: "1.7.2-emqx-11", override: true}, {:ekka, github: "emqx/ekka", tag: "0.15.2", override: true}, {:gen_rpc, github: "emqx/gen_rpc", tag: "2.8.1", override: true}, {:grpc, github: "emqx/grpc-erl", tag: "0.6.7", override: true}, @@ -195,10 +195,10 @@ defmodule EMQXUmbrella.MixProject do {:hstreamdb_erl, github: "hstreamdb/hstreamdb_erl", tag: "0.2.5"}, {:influxdb, github: "emqx/influxdb-client-erl", tag: "1.1.9", override: true}, {:wolff, github: "kafka4beam/wolff", tag: "1.7.5"}, - {:kafka_protocol, github: "kafka4beam/kafka_protocol", tag: "4.1.2", override: true}, + {:kafka_protocol, github: "kafka4beam/kafka_protocol", tag: "4.1.3", override: true}, {:brod_gssapi, github: "kafka4beam/brod_gssapi", tag: "v0.1.0"}, {:brod, github: "kafka4beam/brod", tag: "3.16.8"}, - {:snappyer, "1.2.8", override: true}, + {:snappyer, "1.2.9", override: true}, {:crc32cer, "0.1.8", override: true}, {:supervisor3, "1.1.12", override: true}, {:opentsdb, github: "emqx/opentsdb-client-erl", tag: "v0.5.1", override: true}, diff --git a/rebar.config b/rebar.config index 0aab04892..81545a7be 100644 --- a/rebar.config +++ b/rebar.config @@ -61,7 +61,7 @@ , {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}} , {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.9.0"}}} , {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.9.6"}}} - , {rocksdb, {git, "https://github.com/emqx/erlang-rocksdb", {tag, "1.7.2-emqx-9"}}} + , {rocksdb, {git, "https://github.com/emqx/erlang-rocksdb", {tag, "1.7.2-emqx-11"}}} , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.15.2"}}} , {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.1"}}} , {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.7"}}} From 909d3485702f90b63144ee166219f9294f691311 Mon Sep 17 00:00:00 2001 From: JimMoen Date: Tue, 30 May 2023 11:23:25 +0800 Subject: [PATCH 33/72] chore: fix spec typo --- apps/emqx/src/persistent_session/emqx_persistent_session.erl | 2 +- apps/emqx_ft/src/emqx_ft_storage_exporter.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/emqx/src/persistent_session/emqx_persistent_session.erl b/apps/emqx/src/persistent_session/emqx_persistent_session.erl index 3e9e00c81..bfda233e1 100644 --- a/apps/emqx/src/persistent_session/emqx_persistent_session.erl +++ b/apps/emqx/src/persistent_session/emqx_persistent_session.erl @@ -229,7 +229,7 @@ discard_if_present(ClientID) -> ok end. --spec discard(binary(), emgx_session:session()) -> emgx_session:session(). +-spec discard(binary(), emqx_session:session()) -> emqx_session:session(). discard(ClientID, Session) -> discard_opt(is_store_enabled(), ClientID, Session). diff --git a/apps/emqx_ft/src/emqx_ft_storage_exporter.erl b/apps/emqx_ft/src/emqx_ft_storage_exporter.erl index 591173615..9e6f83b7f 100644 --- a/apps/emqx_ft/src/emqx_ft_storage_exporter.erl +++ b/apps/emqx_ft/src/emqx_ft_storage_exporter.erl @@ -38,7 +38,7 @@ -export_type([export/0]). --type storage() :: emxt_ft_storage_fs:storage(). +-type storage() :: emqx_ft_storage_fs:storage(). -type transfer() :: emqx_ft:transfer(). -type filemeta() :: emqx_ft:filemeta(). -type checksum() :: emqx_ft:checksum(). From 8569fac6eb98290a206146743394d00cc4dd158b Mon Sep 17 00:00:00 2001 From: JimMoen Date: Tue, 30 May 2023 14:00:20 +0800 Subject: [PATCH 34/72] fix: make static_check happy --- apps/emqx_ft/src/emqx_ft_storage_exporter.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqx_ft/src/emqx_ft_storage_exporter.erl b/apps/emqx_ft/src/emqx_ft_storage_exporter.erl index 9e6f83b7f..601f8b112 100644 --- a/apps/emqx_ft/src/emqx_ft_storage_exporter.erl +++ b/apps/emqx_ft/src/emqx_ft_storage_exporter.erl @@ -38,7 +38,7 @@ -export_type([export/0]). --type storage() :: emqx_ft_storage_fs:storage(). +-type storage() :: emqx_ft_storage_fs:storage() | undefined. -type transfer() :: emqx_ft:transfer(). -type filemeta() :: emqx_ft:filemeta(). -type checksum() :: emqx_ft:checksum(). From 5aaac86e2898205be9e2bf66b7319eabe8b94236 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Tue, 30 May 2023 08:03:42 +0200 Subject: [PATCH 35/72] ci: prepare for 5.1 release --- .github/pull_request_template.md | 2 +- .github/workflows/build_packages_cron.yaml | 2 +- .github/workflows/build_slim_packages.yaml | 2 +- scripts/rel/cut.sh | 20 ++++++++++---------- scripts/rel/sync-remotes.sh | 10 +++++----- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7cb91f0d4..26a3cc5fc 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,6 @@ Fixes - + ## Summary copilot:summary diff --git a/.github/workflows/build_packages_cron.yaml b/.github/workflows/build_packages_cron.yaml index 8498b30ba..a0b2ebd80 100644 --- a/.github/workflows/build_packages_cron.yaml +++ b/.github/workflows/build_packages_cron.yaml @@ -23,7 +23,7 @@ jobs: matrix: profile: - ['emqx', 'master'] - - ['emqx-enterprise', 'release-50'] + - ['emqx-enterprise', 'release-51'] otp: - 24.3.4.2-3 arch: diff --git a/.github/workflows/build_slim_packages.yaml b/.github/workflows/build_slim_packages.yaml index 7e664c1c7..c685078e0 100644 --- a/.github/workflows/build_slim_packages.yaml +++ b/.github/workflows/build_slim_packages.yaml @@ -8,7 +8,7 @@ on: push: branches: - master - - release-50 + - release-51 pull_request: # GitHub pull_request action is by default triggered when # opened reopened or synchronize, diff --git a/scripts/rel/cut.sh b/scripts/rel/cut.sh index 4a0a7ab6c..cf402f7f5 100755 --- a/scripts/rel/cut.sh +++ b/scripts/rel/cut.sh @@ -13,14 +13,14 @@ usage() { cat < one -NOTE: For 5.0 series the current working branch must be 'release-50' +NOTE: For 5.1 series the current working branch must be 'release-51' --.--[ master ]---------------------------.-----------.--- \\ / - \`---[release-50]----(v5.0.20 | e5.0.1) + \`---[release-51]----(v5.1.1 | e5.1.1) EOF } @@ -127,11 +127,11 @@ done rel_branch() { local tag="$1" case "$tag" in - v5.0.*) - echo 'release-50' + v5.1.*) + echo 'release-51' ;; - e5.0.*) - echo 'release-50' + e5.1.*) + echo 'release-51' ;; *) logerr "Unsupported version tag $TAG" @@ -270,7 +270,7 @@ else *beta*) true ;; - e5.0.0*) + e5.1.0*) # the first release has no change log true ;; diff --git a/scripts/rel/sync-remotes.sh b/scripts/rel/sync-remotes.sh index eddce0cd7..f4cbadfa1 100755 --- a/scripts/rel/sync-remotes.sh +++ b/scripts/rel/sync-remotes.sh @@ -5,7 +5,7 @@ set -euo pipefail # ensure dir cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." -BASE_BRANCHES=( 'release-50' 'master' ) +BASE_BRANCHES=( 'release-51' 'master' ) usage() { cat < Date: Tue, 30 May 2023 08:08:29 +0200 Subject: [PATCH 36/72] chore: bump app versions --- apps/emqx_bridge/src/emqx_bridge.app.src | 2 +- apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar.app.src | 2 +- apps/emqx_dashboard/src/emqx_dashboard.app.src | 2 +- apps/emqx_enterprise/src/emqx_enterprise.app.src | 2 +- apps/emqx_resource/src/emqx_resource.app.src | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/emqx_bridge/src/emqx_bridge.app.src b/apps/emqx_bridge/src/emqx_bridge.app.src index 125f53c81..3793dc6d2 100644 --- a/apps/emqx_bridge/src/emqx_bridge.app.src +++ b/apps/emqx_bridge/src/emqx_bridge.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_bridge, [ {description, "EMQX bridges"}, - {vsn, "0.1.20"}, + {vsn, "0.1.21"}, {registered, [emqx_bridge_sup]}, {mod, {emqx_bridge_app, []}}, {applications, [ diff --git a/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar.app.src b/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar.app.src index f50c34391..487e862bc 100644 --- a/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar.app.src +++ b/apps/emqx_bridge_pulsar/src/emqx_bridge_pulsar.app.src @@ -1,6 +1,6 @@ {application, emqx_bridge_pulsar, [ {description, "EMQX Pulsar Bridge"}, - {vsn, "0.1.3"}, + {vsn, "0.1.4"}, {registered, []}, {applications, [ kernel, diff --git a/apps/emqx_dashboard/src/emqx_dashboard.app.src b/apps/emqx_dashboard/src/emqx_dashboard.app.src index 04658ab3c..b8fba8b13 100644 --- a/apps/emqx_dashboard/src/emqx_dashboard.app.src +++ b/apps/emqx_dashboard/src/emqx_dashboard.app.src @@ -2,7 +2,7 @@ {application, emqx_dashboard, [ {description, "EMQX Web Dashboard"}, % strict semver, bump manually! - {vsn, "5.0.22"}, + {vsn, "5.0.23"}, {modules, []}, {registered, [emqx_dashboard_sup]}, {applications, [kernel, stdlib, mnesia, minirest, emqx, emqx_ctl]}, diff --git a/apps/emqx_enterprise/src/emqx_enterprise.app.src b/apps/emqx_enterprise/src/emqx_enterprise.app.src index 84c3ffc02..37c74bdec 100644 --- a/apps/emqx_enterprise/src/emqx_enterprise.app.src +++ b/apps/emqx_enterprise/src/emqx_enterprise.app.src @@ -1,6 +1,6 @@ {application, emqx_enterprise, [ {description, "EMQX Enterprise Edition"}, - {vsn, "0.1.0"}, + {vsn, "0.1.1"}, {registered, []}, {applications, [ kernel, diff --git a/apps/emqx_resource/src/emqx_resource.app.src b/apps/emqx_resource/src/emqx_resource.app.src index 25ce1b79e..28b3bd05c 100644 --- a/apps/emqx_resource/src/emqx_resource.app.src +++ b/apps/emqx_resource/src/emqx_resource.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_resource, [ {description, "Manager for all external resources"}, - {vsn, "0.1.17"}, + {vsn, "0.1.18"}, {registered, []}, {mod, {emqx_resource_app, []}}, {applications, [ From 3b8cba6ba01b85f2e1324035814b83cd8ba1ea0e Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Tue, 30 May 2023 08:21:50 +0200 Subject: [PATCH 37/72] chore: bump rlease versions --- apps/emqx/include/emqx_release.hrl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/emqx/include/emqx_release.hrl b/apps/emqx/include/emqx_release.hrl index e13dd0e52..25b340f50 100644 --- a/apps/emqx/include/emqx_release.hrl +++ b/apps/emqx/include/emqx_release.hrl @@ -32,10 +32,10 @@ %% `apps/emqx/src/bpapi/README.md' %% Community edition --define(EMQX_RELEASE_CE, "5.0.26"). +-define(EMQX_RELEASE_CE, "5.1.0-alpha.1"). %% Enterprise edition --define(EMQX_RELEASE_EE, "5.0.4"). +-define(EMQX_RELEASE_EE, "5.1.0-alpha.1"). %% the HTTP API version -define(EMQX_API_VERSION, "5.0"). From 941939f7d7df6591f41a33e1b94fda9a4cdd6287 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Tue, 30 May 2023 08:12:12 +0200 Subject: [PATCH 38/72] build: finalize 5.0.bpapi --- apps/emqx/test/emqx_static_checks_data/5.0.bpapi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqx/test/emqx_static_checks_data/5.0.bpapi b/apps/emqx/test/emqx_static_checks_data/5.0.bpapi index 5777c1ec4..64dfac537 100644 --- a/apps/emqx/test/emqx_static_checks_data/5.0.bpapi +++ b/apps/emqx/test/emqx_static_checks_data/5.0.bpapi @@ -1 +1 @@ -#{api => #{{emqx,1} => #{calls => [{{emqx_proto_v1,delete_all_deactivated_alarms,['Node']},{emqx_alarm,delete_all_deactivated_alarms,[]}},{{emqx_proto_v1,deactivate_alarm,['Node','Name']},{emqx_alarm,deactivate,['Name']}},{{emqx_proto_v1,clean_pem_cache,['Node']},{ssl_pem_cache,clear,[]}},{{emqx_proto_v1,clean_authz_cache,['Node']},{emqx_authz_cache,drain_cache,[]}},{{emqx_proto_v1,clean_authz_cache,['Node','ClientId']},{emqx_authz_cache,drain_cache,['ClientId']}},{{emqx_proto_v1,get_metrics,['Node']},{emqx_metrics,all,[]}},{{emqx_proto_v1,get_stats,['Node']},{emqx_stats,getstats,[]}},{{emqx_proto_v1,get_alarms,['Node','Type']},{emqx_alarm,get_alarms,['Type']}},{{emqx_proto_v1,is_running,['Node']},{emqx,is_running,[]}}],casts => []},{emqx_authn,1} => #{calls => [{{emqx_authn_proto_v1,lookup_from_all_nodes,['Nodes','ChainName','AuthenticatorID']},{emqx_authn_api,lookup_from_local_node,['ChainName','AuthenticatorID']}}],casts => []},{emqx_authz,1} => #{calls => [{{emqx_authz_proto_v1,lookup_from_all_nodes,['Nodes','Type']},{emqx_authz_api_sources,lookup_from_local_node,['Type']}}],casts => []},{emqx_bridge,1} => #{calls => [{{emqx_bridge_proto_v1,lookup_from_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_api,lookup_from_local_node,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v1,stop_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,stop,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v1,restart_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,restart,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v1,stop_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,stop,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v1,restart_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,restart,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v1,list_bridges,['Node']},{emqx_bridge,list,[]}}],casts => []},{emqx_broker,1} => #{calls => [{{emqx_broker_proto_v1,list_subscriptions_via_topic,['Node','Topic']},{emqx_broker,subscriptions_via_topic,['Topic']}},{{emqx_broker_proto_v1,list_client_subscriptions,['Node','ClientId']},{emqx_broker,subscriptions,['ClientId']}},{{emqx_broker_proto_v1,forward,['Node','Topic','Delivery']},{emqx_broker,dispatch,['Topic','Delivery']}}],casts => [{{emqx_broker_proto_v1,forward_async,['Node','Topic','Delivery']},{emqx_broker,dispatch,['Topic','Delivery']}}]},{emqx_cm,1} => #{calls => [{{emqx_cm_proto_v1,kick_session,['Action','ClientId','ChanPid']},{emqx_cm,do_kick_session,['Action','ClientId','ChanPid']}},{{emqx_cm_proto_v1,takeover_session,['ClientId','ChanPid']},{emqx_cm,takeover_session,['ClientId','ChanPid']}},{{emqx_cm_proto_v1,get_chann_conn_mod,['ClientId','ChanPid']},{emqx_cm,do_get_chann_conn_mod,['ClientId','ChanPid']}},{{emqx_cm_proto_v1,get_chan_info,['ClientId','ChanPid']},{emqx_cm,do_get_chan_info,['ClientId','ChanPid']}},{{emqx_cm_proto_v1,get_chan_stats,['ClientId','ChanPid']},{emqx_cm,do_get_chan_stats,['ClientId','ChanPid']}},{{emqx_cm_proto_v1,lookup_client,['Node','Key']},{emqx_cm,lookup_client,['Key']}},{{emqx_cm_proto_v1,kickout_client,['Node','ClientId']},{emqx_cm,kick_session,['ClientId']}}],casts => []},{emqx_conf,1} => #{calls => [{{emqx_conf_proto_v1,get_override_config_file,['Nodes']},{emqx_conf_app,get_override_config_file,[]}},{{emqx_conf_proto_v1,reset,['Node','KeyPath','Opts']},{emqx,reset_config,['KeyPath','Opts']}},{{emqx_conf_proto_v1,reset,['KeyPath','Opts']},{emqx,reset_config,['KeyPath','Opts']}},{{emqx_conf_proto_v1,remove_config,['Node','KeyPath','Opts']},{emqx,remove_config,['KeyPath','Opts']}},{{emqx_conf_proto_v1,remove_config,['KeyPath','Opts']},{emqx,remove_config,['KeyPath','Opts']}},{{emqx_conf_proto_v1,update,['Node','KeyPath','UpdateReq','Opts']},{emqx,update_config,['KeyPath','UpdateReq','Opts']}},{{emqx_conf_proto_v1,update,['KeyPath','UpdateReq','Opts']},{emqx,update_config,['KeyPath','UpdateReq','Opts']}},{{emqx_conf_proto_v1,get_all,['KeyPath']},{emqx_conf,get_node_and_config,['KeyPath']}},{{emqx_conf_proto_v1,get_config,['Node','KeyPath','Default']},{emqx,get_config,['KeyPath','Default']}},{{emqx_conf_proto_v1,get_config,['Node','KeyPath']},{emqx,get_config,['KeyPath']}}],casts => []},{emqx_dashboard,1} => #{calls => [{{emqx_dashboard_proto_v1,current_rate,['Node']},{emqx_dashboard_monitor,current_rate,['Node']}},{{emqx_dashboard_proto_v1,do_sample,['Node','Latest']},{emqx_dashboard_monitor,do_sample,['Node','Latest']}}],casts => []},{emqx_delayed,1} => #{calls => [{{emqx_delayed_proto_v1,delete_delayed_message,['Node','Id']},{emqx_delayed,delete_delayed_message,['Id']}},{{emqx_delayed_proto_v1,get_delayed_message,['Node','Id']},{emqx_delayed,get_delayed_message,['Id']}}],casts => []},{emqx_exhook,1} => #{calls => [{{emqx_exhook_proto_v1,server_hooks_metrics,['Nodes','Name']},{emqx_exhook_mgr,server_hooks_metrics,['Name']}},{{emqx_exhook_proto_v1,server_info,['Nodes','Name']},{emqx_exhook_mgr,server_info,['Name']}},{{emqx_exhook_proto_v1,all_servers_info,['Nodes']},{emqx_exhook_mgr,all_servers_info,[]}}],casts => []},{emqx_gateway_api_listeners,1} => #{calls => [{{emqx_gateway_api_listeners_proto_v1,listeners_cluster_status,['Nodes','Listeners']},{emqx_gateway_api_listeners,do_listeners_cluster_status,['Listeners']}}],casts => []},{emqx_gateway_cm,1} => #{calls => [{{emqx_gateway_cm_proto_v1,cast,['GwName','ClientId','ChanPid','Req']},{emqx_gateway_cm,do_cast,['GwName','ClientId','ChanPid','Req']}},{{emqx_gateway_cm_proto_v1,call,['GwName','ClientId','ChanPid','Req']},{emqx_gateway_cm,do_call,['GwName','ClientId','ChanPid','Req']}},{{emqx_gateway_cm_proto_v1,call,['GwName','ClientId','ChanPid','Req','Timeout']},{emqx_gateway_cm,do_call,['GwName','ClientId','ChanPid','Req','Timeout']}},{{emqx_gateway_cm_proto_v1,takeover_session,['GwName','ClientId','ChanPid']},{emqx_gateway_cm,do_takeover_session,['GwName','ClientId','ChanPid']}},{{emqx_gateway_cm_proto_v1,get_chann_conn_mod,['GwName','ClientId','ChanPid']},{emqx_gateway_cm,do_get_chann_conn_mod,['GwName','ClientId','ChanPid']}},{{emqx_gateway_cm_proto_v1,kick_session,['GwName','Action','ClientId','ChanPid']},{emqx_gateway_cm,do_kick_session,['GwName','Action','ClientId','ChanPid']}},{{emqx_gateway_cm_proto_v1,set_chan_stats,['GwName','ClientId','ChanPid','Stats']},{emqx_gateway_cm,do_set_chan_stats,['GwName','ClientId','ChanPid','Stats']}},{{emqx_gateway_cm_proto_v1,get_chan_stats,['GwName','ClientId','ChanPid']},{emqx_gateway_cm,do_get_chan_stats,['GwName','ClientId','ChanPid']}},{{emqx_gateway_cm_proto_v1,set_chan_info,['GwName','ClientId','ChanPid','Infos']},{emqx_gateway_cm,do_set_chan_info,['GwName','ClientId','ChanPid','Infos']}},{{emqx_gateway_cm_proto_v1,get_chan_info,['GwName','ClientId','ChanPid']},{emqx_gateway_cm,do_get_chan_info,['GwName','ClientId','ChanPid']}},{{emqx_gateway_cm_proto_v1,lookup_by_clientid,['Nodes','GwName','ClientId']},{emqx_gateway_cm,do_lookup_by_clientid,['GwName','ClientId']}}],casts => []},{emqx_gateway_http,1} => #{calls => [{{emqx_gateway_http_proto_v1,get_cluster_status,['Nodes','GwName']},{emqx_gateway_http,gateway_status,['GwName']}}],casts => []},{emqx_license,1} => #{calls => [{{emqx_license_proto_v1,remote_connection_counts,['Nodes']},{emqx_license_resources,local_connection_count,[]}}],casts => []},{emqx_management,1} => #{calls => [{{emqx_management_proto_v1,get_full_config,['Node']},{emqx_mgmt_api_configs,get_full_config,[]}},{{emqx_management_proto_v1,call_client,['Node','ClientId','Req']},{emqx_mgmt,do_call_client,['ClientId','Req']}},{{emqx_management_proto_v1,unsubscribe,['Node','ClientId','Topic']},{emqx_mgmt,do_unsubscribe,['ClientId','Topic']}},{{emqx_management_proto_v1,subscribe,['Node','ClientId','TopicTables']},{emqx_mgmt,do_subscribe,['ClientId','TopicTables']}},{{emqx_management_proto_v1,list_listeners,['Node']},{emqx_mgmt_api_listeners,do_list_listeners,[]}},{{emqx_management_proto_v1,list_subscriptions,['Node']},{emqx_mgmt,do_list_subscriptions,[]}},{{emqx_management_proto_v1,broker_info,['Node']},{emqx_mgmt,broker_info,[]}},{{emqx_management_proto_v1,node_info,['Node']},{emqx_mgmt,node_info,[]}}],casts => []},{emqx_mgmt_api_plugins,1} => #{calls => [{{emqx_mgmt_api_plugins_proto_v1,ensure_action,['Name','Action']},{emqx_mgmt_api_plugins,ensure_action,['Name','Action']}},{{emqx_mgmt_api_plugins_proto_v1,delete_package,['Name']},{emqx_mgmt_api_plugins,delete_package,['Name']}},{{emqx_mgmt_api_plugins_proto_v1,describe_package,['Name']},{emqx_mgmt_api_plugins,describe_package,['Name']}},{{emqx_mgmt_api_plugins_proto_v1,install_package,['Filename','Bin']},{emqx_mgmt_api_plugins,install_package,['Filename','Bin']}},{{emqx_mgmt_api_plugins_proto_v1,get_plugins,[]},{emqx_mgmt_api_plugins,get_plugins,[]}}],casts => []},{emqx_mgmt_cluster,1} => #{calls => [{{emqx_mgmt_cluster_proto_v1,invite_node,['Node','Self']},{emqx_mgmt_api_cluster,join,['Self']}}],casts => []},{emqx_mgmt_trace,1} => #{calls => [{{emqx_mgmt_trace_proto_v1,read_trace_file,['Node','Name','Position','Limit']},{emqx_mgmt_api_trace,read_trace_file,['Name','Position','Limit']}},{{emqx_mgmt_trace_proto_v1,trace_file,['Nodes','File']},{emqx_trace,trace_file,['File']}},{{emqx_mgmt_trace_proto_v1,get_trace_size,['Nodes']},{emqx_mgmt_api_trace,get_trace_size,[]}}],casts => []},{emqx_persistent_session,1} => #{calls => [{{emqx_persistent_session_proto_v1,resume_end,['Nodes','Pid','SessionID']},{emqx_session_router,resume_end,['Pid','SessionID']}},{{emqx_persistent_session_proto_v1,resume_begin,['Nodes','Pid','SessionID']},{emqx_session_router,resume_begin,['Pid','SessionID']}}],casts => []},{emqx_plugin_libs,1} => #{calls => [{{emqx_plugin_libs_proto_v1,get_metrics,['Node','HandlerName','MetricId']},{emqx_metrics_worker,get_metrics,['HandlerName','MetricId']}}],casts => []},{emqx_prometheus,1} => #{calls => [{{emqx_prometheus_proto_v1,stop,['Nodes']},{emqx_prometheus,do_stop,[]}},{{emqx_prometheus_proto_v1,start,['Nodes']},{emqx_prometheus,do_start,[]}}],casts => []},{emqx_resource,1} => #{calls => [{{emqx_resource_proto_v1,reset_metrics,['ResId']},{emqx_resource,reset_metrics_local,['ResId']}},{{emqx_resource_proto_v1,remove,['ResId']},{emqx_resource,remove_local,['ResId']}},{{emqx_resource_proto_v1,recreate,['ResId','ResourceType','Config','Opts']},{emqx_resource,recreate_local,['ResId','ResourceType','Config','Opts']}},{{emqx_resource_proto_v1,create_dry_run,['ResourceType','Config']},{emqx_resource,create_dry_run_local,['ResourceType','Config']}},{{emqx_resource_proto_v1,create,['ResId','Group','ResourceType','Config','Opts']},{emqx_resource,create_local,['ResId','Group','ResourceType','Config','Opts']}}],casts => []},{emqx_retainer,1} => #{calls => [{{emqx_retainer_proto_v1,wait_dispatch_complete,['Nodes','Timeout']},{emqx_retainer_dispatcher,wait_dispatch_complete,['Timeout']}}],casts => []},{emqx_rule_engine,1} => #{calls => [{{emqx_rule_engine_proto_v1,reset_metrics,['RuleId']},{emqx_rule_engine,reset_metrics_for_rule,['RuleId']}}],casts => []},{emqx_slow_subs,1} => #{calls => [{{emqx_slow_subs_proto_v1,get_history,['Nodes']},{emqx_slow_subs_api,get_history,[]}},{{emqx_slow_subs_proto_v1,clear_history,['Nodes']},{emqx_slow_subs,clear_history,[]}}],casts => []},{emqx_statsd,1} => #{calls => [{{emqx_statsd_proto_v1,restart,['Nodes']},{emqx_statsd,do_restart,[]}},{{emqx_statsd_proto_v1,stop,['Nodes']},{emqx_statsd,do_stop,[]}},{{emqx_statsd_proto_v1,start,['Nodes']},{emqx_statsd,do_start,[]}}],casts => []},{emqx_telemetry,1} => #{calls => [{{emqx_telemetry_proto_v1,get_cluster_uuid,['Node']},{emqx_telemetry,get_cluster_uuid,[]}},{{emqx_telemetry_proto_v1,get_node_uuid,['Node']},{emqx_telemetry,get_node_uuid,[]}}],casts => []},{emqx_topic_metrics,1} => #{calls => [{{emqx_topic_metrics_proto_v1,reset,['Nodes','Topic']},{emqx_topic_metrics,reset,['Topic']}},{{emqx_topic_metrics_proto_v1,reset,['Nodes']},{emqx_topic_metrics,reset,[]}},{{emqx_topic_metrics_proto_v1,metrics,['Nodes','Topic']},{emqx_topic_metrics,metrics,['Topic']}},{{emqx_topic_metrics_proto_v1,metrics,['Nodes']},{emqx_topic_metrics,metrics,[]}}],casts => []}},release => "5.0.0",signatures => #{{emqx_telemetry,get_node_uuid,0} => {any,[]},{emqx,is_running,0} => {{c,atom,[false,true],unknown},[]},{emqx_mgmt_api_plugins,install_package,2} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_bridge_resource,restart,2} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_exhook_mgr,server_hooks_metrics,1} => {any,[any]},{emqx_mgmt,do_unsubscribe,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[channel_not_found],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[unsubscribe],unknown},{c,list,[{c,tuple,[{c,binary,[8,0],unknown},{c,map,{[],any,any},unknown}],{2,any}},{c,nil,[],unknown}],nonempty}],{2,{c,atom,[unsubscribe],unknown}}}]}],unknown},[any,any]},{emqx_gateway_cm,do_cast,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any]},{emqx_prometheus_proto_v1,start,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_statsd_proto_v1,stop,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_proto_v1,clean_authz_cache,1} => {any,[{c,atom,any,unknown}]},{emqx_prometheus,do_start,0} => {{c,atom,[ok],unknown},[]},{emqx_gateway_cm,do_call,5} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any,any]},{emqx_broker,dispatch,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[no_subscribers,not_running],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,number,{int_rng,0,pos_inf},integer}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,binary,[8,0],unknown},{c,tuple,[{c,atom,[delivery],unknown},{c,identifier,[pid],unknown},{c,tuple,[{c,atom,[message],unknown},{c,binary,[8,0],unknown},any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,map,{[],{c,atom,any,unknown},{c,atom,[false,true],unknown}},unknown},{c,map,{[{{c,atom,[allow_publish],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[peerhost],unknown},optional,{c,tuple_set,[{4,[{c,tuple,[any,any,any,any],{4,any}}]},{8,[{c,tuple,[any,any,any,any,any,any,any,any],{8,any}}]}],unknown}},{{c,atom,[properties],unknown},optional,{c,map,{[],any,any},unknown}},{{c,atom,[proto_ver],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}},{{c,atom,[protocol],unknown},optional,{c,atom,any,unknown}},{{c,atom,[username],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}}],{c,atom,any,unknown},any},unknown},{c,binary,[8,0],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,number,any,integer},any],{10,{c,atom,[message],unknown}}}],{3,{c,atom,[delivery],unknown}}}]},{emqx_mgmt_api_plugins_proto_v1,describe_package,1} => {any,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_telemetry_proto_v1,get_cluster_uuid,1} => {any,[{c,atom,any,unknown}]},{emqx_delayed,get_delayed_message,1} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[not_found],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[delayed_interval],unknown},mandatory,any},{{c,atom,[delayed_remaining],unknown},mandatory,{c,number,any,integer}},{{c,atom,[expected_at],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[from_clientid],unknown},mandatory,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[from_username],unknown},mandatory,any},{{c,atom,[msgid],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[payload],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown}},{{c,atom,[publish_at],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[qos],unknown},mandatory,any},{{c,atom,[topic],unknown},mandatory,{c,binary,[8,0],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[any]},{emqx_statsd_proto_v1,start,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_conf_proto_v1,get_override_config_file,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_statsd,do_stop,0} => {{c,atom,[ok],unknown},[]},{emqx_cm_proto_v1,get_chan_stats,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_resource_proto_v1,remove,1} => {any,[{c,binary,[8,0],unknown}]},{emqx_gateway_cm_proto_v1,set_chan_info,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},{c,map,{[],{c,atom,any,unknown},any},unknown}]},{emqx_statsd_proto_v1,restart,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_topic_metrics,reset,0} => {any,[]},{emqx_mgmt_api_listeners,do_list_listeners,0} => {{c,map,{[],{c,binary,"( ",unknown},{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[{c,map,{[],any,any},unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}},unknown},[]},{emqx_mgmt_api_plugins,ensure_action,2} => {{c,atom,[ok],unknown},[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,atom,[restart,start,stop],unknown}]},{emqx_topic_metrics_proto_v1,metrics,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_mgmt,do_subscribe,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[channel_not_found],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[subscribe],unknown},any],{2,{c,atom,[subscribe],unknown}}}]}],unknown},[any,any]},{emqx_cm,do_get_chan_info,2} => {any,[any,any]},{emqx_conf_proto_v1,remove_config,3} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_gateway_api_listeners_proto_v1,listeners_cluster_status,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,list,[any,{c,nil,[],unknown}],unknown}]},{emqx_conf_proto_v1,get_all,1} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown}]},{emqx_cm,do_kick_session,3} => {{c,atom,[ok],unknown},[any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_gateway_cm_proto_v1,get_chann_conn_mod,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_authz_api_sources,lookup_from_local_node,1} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,tuple,[{c,atom,any,unknown},{c,union,[{c,atom,[not_found_resource],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}],{2,any}}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,tuple,[{c,atom,any,unknown},{c,atom,[connected,connecting,disconnected],unknown},{c,map,{[{{c,atom,[counters],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},mandatory,{c,map,{[],any,any},unknown}}],none,none},unknown},{c,map,{[{{c,atom,[counters],unknown},optional,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},optional,{c,map,{[],any,any},unknown}}],none,none},unknown}],{4,any}}],{2,{c,atom,[ok],unknown}}}]}],unknown},[any]},{emqx_gateway_cm_proto_v1,call,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any]},{emqx_resource,reset_metrics_local,1} => {{c,atom,[ok],unknown},[{c,binary,[8,0],unknown}]},{emqx_proto_v1,get_stats,1} => {any,[{c,atom,any,unknown}]},{emqx_gateway_cm_proto_v1,kick_session,4} => {any,[{c,atom,any,unknown},{c,atom,[discard,kick],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_delayed,delete_delayed_message,1} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},{c,atom,[not_found],unknown}],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[any]},{emqx_conf_proto_v1,remove_config,2} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_gateway_http,gateway_status,1} => {{c,map,{[{{c,atom,[current_connections],unknown},optional,any},{{c,atom,[max_connections],unknown},optional,{c,number,{int_set,[0]},integer}},{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[status],unknown},mandatory,{c,atom,[running,stopped,unloaded],unknown}}],none,none},unknown},[{c,atom,any,unknown}]},{emqx_dashboard_proto_v1,do_sample,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,1,pos_inf},integer},none,none,none,none],unknown}]},{emqx_gateway_cm,do_get_chann_conn_mod,3} => {any,[any,any,any]},{emqx_prometheus,do_stop,0} => {{c,atom,[ok],unknown},[]},{emqx_proto_v1,clean_pem_cache,1} => {any,[{c,atom,any,unknown}]},{emqx_delayed_proto_v1,delete_delayed_message,2} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown}]},{emqx_gateway_cm_proto_v1,call,5} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any,{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}]},{emqx_mgmt_trace_proto_v1,read_trace_file,4} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown},{c,number,{int_rng,0,pos_inf},integer},{c,number,{int_rng,0,pos_inf},integer}]},{emqx_session_router,resume_begin,2} => {any,[{c,identifier,[pid],unknown},{c,binary,[8,0],unknown}]},{emqx_bridge_proto_v1,stop_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_mgmt_api_cluster,join,1} => {any,[any]},{emqx_topic_metrics,metrics,0} => {{c,list,[{c,map,{[{{c,atom,[create_time],unknown},mandatory,any},{{c,atom,[metrics],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[reset_time],unknown},optional,any},{{c,atom,[topic],unknown},mandatory,any}],none,none},unknown},{c,nil,[],unknown}],unknown},[]},{emqx,get_config,2} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown},any]},{emqx_mgmt,node_info,0} => {{c,map,{[{{c,atom,[connections],unknown},mandatory,any},{{c,atom,[load1],unknown},optional,{c,binary,[8,0],unknown}},{{c,atom,[load15],unknown},optional,{c,binary,[8,0],unknown}},{{c,atom,[load5],unknown},optional,{c,binary,[8,0],unknown}},{{c,atom,[max_fds],unknown},mandatory,any},{{c,atom,[memory_total],unknown},mandatory,{c,number,any,unknown}},{{c,atom,[memory_used],unknown},mandatory,{c,number,any,integer}},{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[node_status],unknown},mandatory,{c,atom,['Running'],unknown}},{{c,atom,[otp_release],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[process_available],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[process_used],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[role],unknown},mandatory,{c,atom,[core,replicant],unknown}},{{c,atom,[uptime],unknown},mandatory,any},{{c,atom,[version],unknown},mandatory,{c,binary,[8,0],unknown}}],none,none},unknown},[]},{emqx_mgmt_api_plugins,get_plugins,0} => {{c,tuple,[{c,atom,any,unknown},{c,list,[{c,map,{[],any,any},unknown},{c,nil,[],unknown}],unknown}],{2,any}},[]},{emqx_gateway_cm_proto_v1,set_chan_stats,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},{c,list,[{c,tuple,[{c,atom,any,unknown},any],{2,any}},{c,nil,[],unknown}],unknown}]},{emqx_statsd,do_restart,0} => {{c,atom,[ok],unknown},[]},{emqx_license_resources,local_connection_count,0} => {any,[]},{emqx_mgmt_api_trace,read_trace_file,3} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[eof],unknown},{c,union,[{c,atom,[undefined],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}],{2,{c,atom,[eof],unknown}}},{c,tuple,[{c,atom,[error],unknown},{c,union,[{c,atom,any,unknown},none,none,none,none,none,{c,tuple,[{c,atom,[no_translation],unknown},{c,atom,[unicode],unknown},{c,atom,[latin1],unknown}],{3,{c,atom,[no_translation],unknown}}},none,none,none],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,binary,[8,0],unknown},any,any]},{emqx_authz_cache,drain_cache,1} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},{c,atom,[not_found],unknown}],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_cm_proto_v1,kick_session,3} => {any,[{c,atom,[discard,kick],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_bridge,list,0} => {any,[]},{emqx_broker,subscriptions,1} => {{c,list,[{c,tuple,[any,any],{2,any}},{c,nil,[],unknown}],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,{c,identifier,[pid],unknown},none,none,none,none,none,none],unknown}]},{emqx_metrics,all,0} => {{c,list,[{c,tuple,[any,{c,number,any,integer}],{2,any}},{c,nil,[],unknown}],unknown},[]},{emqx_bridge_proto_v1,list_bridges,1} => {any,[{c,atom,any,unknown}]},{emqx_broker_proto_v1,list_subscriptions_via_topic,2} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown}]},{ssl_pem_cache,clear,0} => {any,[]},{emqx_management_proto_v1,get_full_config,1} => {any,[{c,atom,any,unknown}]},{emqx_retainer_proto_v1,wait_dispatch_complete,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}]},{emqx_mgmt,broker_info,0} => {{c,map,{[{{c,atom,[datetime],unknown},optional,{c,binary,[8,0],unknown}},{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[node_status],unknown},mandatory,{c,atom,['Running'],unknown}},{{c,atom,[otp_release],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[sysdescr],unknown},optional,{c,binary,[8,0],unknown}},{{c,atom,[version],unknown},optional,{c,binary,[8,0],unknown}}],none,none},unknown},[]},{emqx_dashboard_monitor,do_sample,2} => {any,[{c,atom,any,unknown},any]},{emqx_gateway_cm_proto_v1,cast,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any]},{emqx_plugin_libs_proto_v1,get_metrics,3} => {any,[{c,atom,any,unknown},{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_conf_proto_v1,update,4} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},any,{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_topic_metrics_proto_v1,reset,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,binary,[8,0],unknown}]},{emqx_alarm,delete_all_deactivated_alarms,0} => {any,[]},{emqx_gateway_api_listeners,do_listeners_cluster_status,1} => {{c,map,{[],any,any},unknown},[{c,list,[any,{c,nil,[],unknown}],unknown}]},{emqx_bridge_proto_v1,stop_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_delayed_proto_v1,get_delayed_message,2} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown}]},{emqx_slow_subs_proto_v1,clear_history,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_topic_metrics_proto_v1,reset,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_gateway_cm_proto_v1,get_chan_stats,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_management_proto_v1,unsubscribe,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,binary,[8,0],unknown}]},{emqx_management_proto_v1,broker_info,1} => {any,[{c,atom,any,unknown}]},{emqx_persistent_session_proto_v1,resume_begin,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,identifier,[pid],unknown},{c,binary,[8,0],unknown}]},{emqx_topic_metrics_proto_v1,metrics,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,binary,[8,0],unknown}]},{emqx_mgmt,do_list_subscriptions,0} => {{c,list,[{c,map,{[{{c,atom,[clientid],unknown},mandatory,any},{{c,atom,[options],unknown},mandatory,any},{{c,atom,[topic],unknown},mandatory,any}],none,none},unknown},{c,nil,[],unknown}],unknown},[]},{emqx_gateway_cm,do_get_chan_info,3} => {{c,union,[{c,atom,[undefined],unknown},none,none,none,none,none,none,none,none,{c,map,{[{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}}],any,any},unknown}],unknown},[any,any,any]},{emqx_resource,create_dry_run_local,2} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,atom,any,unknown},any]},{emqx_gateway_cm_proto_v1,lookup_by_clientid,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_rule_engine_proto_v1,reset_metrics,1} => {any,[{c,binary,[8,0],unknown}]},{emqx_mgmt_api_plugins_proto_v1,get_plugins,0} => {any,[]},{emqx_broker_proto_v1,list_client_subscriptions,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_topic_metrics,metrics,1} => {{c,union,[none,none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},{c,atom,[topic_not_found],unknown}],{2,{c,atom,[error],unknown}}},none,none,{c,map,{[{{c,atom,[create_time],unknown},mandatory,any},{{c,atom,[metrics],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[reset_time],unknown},optional,any},{{c,atom,[topic],unknown},mandatory,any}],none,none},unknown}],unknown},[any]},{emqx_rule_engine,reset_metrics_for_rule,1} => {{c,atom,[ok],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_gateway_cm,do_get_chan_stats,3} => {any,[any,any,any]},{emqx_mgmt_api_plugins_proto_v1,ensure_action,2} => {any,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,atom,[restart,start,stop],unknown}]},{emqx_session_router,resume_end,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,list,[{c,tuple,any,{any,any}},{c,nil,[],unknown}],unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,identifier,[pid],unknown},{c,binary,[8,0],unknown}]},{emqx_management_proto_v1,node_info,1} => {any,[{c,atom,any,unknown}]},{emqx_cm,do_get_chann_conn_mod,2} => {any,[any,any]},{emqx_cm_proto_v1,get_chan_info,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_conf_proto_v1,update,3} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},any,{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_authz_cache,drain_cache,0} => {{c,atom,[ok],unknown},[]},{emqx_mgmt_trace_proto_v1,get_trace_size,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx,remove_config,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,atom,any,unknown},any},unknown}],unknown}},{{c,atom,[post_config_update],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[raw_config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,binary,[8,0],unknown},any},unknown}],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_cm,takeover_session,2} => {{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[{c,tuple,[{c,atom,any,unknown},any],{2,any}},{c,nil,[],unknown}],unknown},none,{c,tuple_set,[{2,[{c,tuple,[{c,atom,[expired],unknown},{c,tuple,[{c,atom,[session],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,binary,[0,128],unknown},{c,atom,[false,true],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,atom,[false,true],unknown},{c,opaque,[{opaque,emqx_inflight,inflight,[],{c,tuple,[any,any,any],{3,any}}}],unknown},{c,tuple,[any,any,any,any,any,any,any,any,any,any,any],{11,any}},{c,number,{int_rng,1,1114111},integer},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,number,{int_rng,1,pos_inf},integer}],{15,{c,atom,[session],unknown}}}],{2,{c,atom,[expired],unknown}}},{c,tuple,[{c,atom,[persistent],unknown},{c,tuple,[{c,atom,[session],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,binary,[0,128],unknown},{c,atom,[false,true],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,atom,[false,true],unknown},{c,opaque,[{opaque,emqx_inflight,inflight,[],{c,tuple,[any,any,any],{3,any}}}],unknown},{c,tuple,[any,any,any,any,any,any,any,any,any,any,any],{11,any}},{c,number,{int_rng,1,1114111},integer},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,number,{int_rng,1,pos_inf},integer}],{15,{c,atom,[session],unknown}}}],{2,{c,atom,[persistent],unknown}}}]},{4,[{c,tuple,[{c,atom,[living],unknown},{c,atom,any,unknown},{c,identifier,[pid],unknown},{c,union,[none,none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,{c,tuple,[{c,atom,[session],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,binary,[0,128],unknown},{c,atom,[false,true],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,atom,[false,true],unknown},{c,opaque,[{opaque,emqx_inflight,inflight,[],{c,tuple,[any,any,any],{3,any}}}],unknown},{c,tuple,[any,any,any,any,any,any,any,any,any,any,any],{11,any}},{c,number,{int_rng,1,1114111},integer},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,number,{int_rng,1,pos_inf},integer}],{15,{c,atom,[session],unknown}}},none,none,none],unknown}],{4,{c,atom,[living],unknown}}}]}],unknown},none,none,{c,map,{[],{c,atom,any,unknown},any},unknown}],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},any]},{emqx_dashboard_monitor,current_rate,1} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[badrpc],unknown},any],{2,{c,atom,[badrpc],unknown}}},{c,tuple,[{c,atom,[ok],unknown},any],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,atom,any,unknown}]},{emqx_gateway_cm,do_set_chan_stats,4} => {{c,atom,[false,true],unknown},[any,any,any,any]},{emqx_cm_proto_v1,get_chann_conn_mod,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_exhook_proto_v1,all_servers_info,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_cm_proto_v1,takeover_session,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_cm_proto_v1,lookup_client,2} => {any,[{c,atom,any,unknown},{c,tuple_set,[{2,[{c,tuple,[{c,atom,[clientid],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}],{2,{c,atom,[clientid],unknown}}},{c,tuple,[{c,atom,[username],unknown},{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}],{2,{c,atom,[username],unknown}}}]}],unknown}]},{emqx_stats,getstats,0} => {{c,list,[{c,tuple,any,{any,any}},{c,nil,[],unknown}],unknown},[]},{emqx_license_proto_v1,remote_connection_counts,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_mgmt_api_plugins_proto_v1,install_package,2} => {any,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,binary,[8,0],unknown}]},{emqx_cm,lookup_client,1} => {{c,list,[any,{c,nil,[],unknown}],unknown},[{c,tuple_set,[{2,[{c,tuple,[{c,atom,[clientid],unknown},any],{2,{c,atom,[clientid],unknown}}},{c,tuple,[{c,atom,[username],unknown},any],{2,{c,atom,[username],unknown}}}]}],unknown}]},{emqx_bridge_proto_v1,lookup_from_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_proto_v1,get_metrics,1} => {any,[{c,atom,any,unknown}]},{emqx_management_proto_v1,subscribe,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,list,[{c,tuple,[{c,binary,[8,0],unknown},{c,map,{[{{c,atom,[nl],unknown},mandatory,{c,number,{int_set,[0,1]},integer}},{{c,atom,[qos],unknown},mandatory,{c,number,{int_set,[0,1,2]},integer}},{{c,atom,[rap],unknown},mandatory,{c,number,{int_set,[0,1]},integer}},{{c,atom,[rh],unknown},mandatory,{c,number,{int_set,[0,1,2]},integer}},{{c,atom,[share],unknown},optional,{c,binary,[8,0],unknown}}],{c,atom,any,unknown},any},unknown}],{2,any}},{c,nil,[],unknown}],unknown}]},{emqx_resource_proto_v1,recreate,4} => {any,[{c,binary,[8,0],unknown},{c,atom,any,unknown},any,{c,map,{[{{c,atom,[auto_retry_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_timeout],unknown},optional,{c,number,any,integer}},{{c,atom,[start_after_created],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[wait_for_resource_ready],unknown},optional,{c,number,any,integer}}],none,none},unknown}]},{emqx_broker,subscriptions_via_topic,1} => {{c,list,[any,{c,nil,[],unknown}],unknown},[any]},{emqx_gateway_cm_proto_v1,get_chan_info,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_topic_metrics,reset,1} => {any,[any]},{emqx_mgmt_api_trace,get_trace_size,0} => {{c,map,{[],any,any},unknown},[]},{emqx_cm,kick_session,1} => {{c,atom,[ok],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_broker_proto_v1,forward_async,3} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown},{c,tuple,[{c,atom,[delivery],unknown},{c,identifier,[pid],unknown},{c,tuple,[{c,atom,[message],unknown},{c,binary,[8,0],unknown},any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,map,{[],{c,atom,any,unknown},{c,atom,[false,true],unknown}},unknown},{c,map,{[{{c,atom,[allow_publish],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[peerhost],unknown},optional,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown}},{{c,atom,[properties],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[proto_ver],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}},{{c,atom,[protocol],unknown},optional,{c,atom,any,unknown}},{{c,atom,[username],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}}],{c,atom,any,unknown},any},unknown},{c,binary,[8,0],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,number,any,integer},any],{10,{c,atom,[message],unknown}}}],{3,{c,atom,[delivery],unknown}}}]},{emqx_trace,trace_file,1} => {{c,tuple_set,[{3,[{c,tuple,[{c,atom,[error],unknown},{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},{c,atom,any,unknown}],{3,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},{c,binary,[8,0],unknown}],{3,{c,atom,[ok],unknown}}}]}],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_bridge_api,lookup_from_local_node,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[not_found],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[metrics],unknown},mandatory,{c,map,{[{{c,atom,[failed],unknown},mandatory,{c,number,any,unknown}},{{c,atom,[matched],unknown},mandatory,any},{{c,atom,[rate],unknown},mandatory,any},{{c,atom,[rate_last5m],unknown},mandatory,any},{{c,atom,[rate_max],unknown},mandatory,any},{{c,atom,[success],unknown},mandatory,any}],none,none},unknown}},{{c,atom,[name],unknown},mandatory,any},{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[status],unknown},mandatory,any},{{c,atom,[type],unknown},mandatory,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown}}],any,any},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_resource,create_local,5} => {{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[config],unknown},mandatory,any},{{c,atom,[id],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[metrics],unknown},mandatory,{c,map,{[{{c,atom,[counters],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},mandatory,{c,map,{[],any,any},unknown}}],none,none},unknown}},{{c,atom,[mod],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[state],unknown},mandatory,any},{{c,atom,[status],unknown},mandatory,{c,atom,[connected,connecting,disconnected],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}},[{c,binary,[8,0],unknown},{c,binary,[8,0],unknown},{c,atom,any,unknown},any,{c,map,{[{{c,atom,[auto_retry_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_timeout],unknown},optional,{c,number,any,integer}},{{c,atom,[start_after_created],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[wait_for_resource_ready],unknown},optional,{c,number,any,integer}}],none,none},unknown}]},{emqx_cm,do_get_chan_stats,2} => {any,[any,any]},{emqx_gateway_http_proto_v1,get_cluster_status,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,atom,any,unknown}]},{emqx_management_proto_v1,list_listeners,1} => {any,[{c,atom,any,unknown}]},{emqx_slow_subs,clear_history,0} => {any,[]},{emqx_gateway_cm,do_call,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any]},{emqx_mgmt_api_plugins,describe_package,1} => {{c,tuple,[{c,atom,any,unknown},{c,list,[{c,map,{[],any,any},unknown},{c,nil,[],unknown}],unknown}],{2,any}},[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_gateway_cm,do_lookup_by_clientid,2} => {{c,list,[any,{c,nil,[],unknown}],unknown},[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},{c,number,any,unknown},none,none,none,none],unknown},any]},{emqx_prometheus_proto_v1,stop,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_slow_subs_api,get_history,0} => {{c,list,[{c,map,{[{{c,atom,[clientid],unknown},mandatory,any},{{c,atom,[last_update_time],unknown},mandatory,any},{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[timespan],unknown},mandatory,any},{{c,atom,[topic],unknown},mandatory,any}],none,none},unknown},{c,nil,[],unknown}],unknown},[]},{emqx_resource_proto_v1,create_dry_run,2} => {any,[{c,atom,any,unknown},any]},{emqx_conf_proto_v1,get_config,3} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown},any]},{emqx_cm_proto_v1,kickout_client,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_mgmt_api_plugins,delete_package,1} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_retainer_dispatcher,wait_dispatch_complete,1} => {{c,atom,[ok],unknown},[any]},{emqx_resource,recreate_local,4} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[not_found,updating_to_incorrect_resource_type],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[config],unknown},mandatory,any},{{c,atom,[id],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[metrics],unknown},mandatory,{c,map,{[{{c,atom,[counters],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},mandatory,{c,map,{[],any,any},unknown}}],none,none},unknown}},{{c,atom,[mod],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[state],unknown},mandatory,any},{{c,atom,[status],unknown},mandatory,{c,atom,[connected,connecting,disconnected],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,binary,[8,0],unknown},{c,atom,any,unknown},any,{c,map,{[{{c,atom,[auto_retry_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_timeout],unknown},optional,{c,number,any,integer}},{{c,atom,[start_after_created],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[wait_for_resource_ready],unknown},optional,{c,number,any,integer}}],none,none},unknown}]},{emqx_proto_v1,deactivate_alarm,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_dashboard_proto_v1,current_rate,1} => {any,[{c,atom,any,unknown}]},{emqx_mgmt_api_configs,get_full_config,0} => {{c,map,{[],any,any},unknown},[]},{emqx_exhook_proto_v1,server_info,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},any]},{emqx_exhook_mgr,server_info,1} => {any,[any]},{emqx_mgmt,do_call_client,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},any]},{emqx_proto_v1,is_running,1} => {any,[{c,atom,any,unknown}]},{emqx_resource_proto_v1,create,5} => {any,[{c,binary,[8,0],unknown},{c,binary,[8,0],unknown},{c,atom,any,unknown},any,{c,map,{[{{c,atom,[auto_retry_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_timeout],unknown},optional,{c,number,any,integer}},{{c,atom,[start_after_created],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[wait_for_resource_ready],unknown},optional,{c,number,any,integer}}],none,none},unknown}]},{emqx_conf_app,get_override_config_file,0} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,map,{[{{c,atom,[msg],unknown},mandatory,any},{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}}],none,none},unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},any],{2,{c,atom,[ok],unknown}}}]}],unknown},[]},{emqx_telemetry_proto_v1,get_node_uuid,1} => {any,[{c,atom,any,unknown}]},{emqx_statsd,do_start,0} => {{c,atom,[ok],unknown},[]},{emqx_gateway_cm,do_takeover_session,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_exhook_mgr,all_servers_info,0} => {any,[]},{emqx_conf_proto_v1,get_config,2} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown}]},{emqx_gateway_cm,do_kick_session,4} => {{c,atom,[ok],unknown},[{c,atom,any,unknown},any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_mgmt_api_plugins_proto_v1,delete_package,1} => {any,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx,get_config,1} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown}]},{emqx_resource,remove_local,1} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,binary,[8,0],unknown}]},{emqx_bridge_resource,stop,2} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_authz_proto_v1,lookup_from_all_nodes,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,atom,any,unknown}]},{emqx,update_config,3} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,atom,any,unknown},any},unknown}],unknown}},{{c,atom,[post_config_update],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[raw_config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,binary,[8,0],unknown},any},unknown}],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},any,{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_management_proto_v1,call_client,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},any]},{emqx_conf,get_node_and_config,1} => {{c,tuple,[{c,atom,any,unknown},any],{2,any}},[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown}]},{emqx_authn_api,lookup_from_local_node,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,tuple,[{c,atom,any,unknown},{c,union,[{c,atom,[not_found_resource],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}],{2,any}}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,tuple,[{c,atom,any,unknown},{c,atom,[connected,connecting,disconnected],unknown},{c,map,{[{{c,atom,[counters],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},mandatory,{c,map,{[],any,any},unknown}}],none,none},unknown},{c,map,{[{{c,atom,[counters],unknown},optional,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},optional,{c,map,{[],any,any},unknown}}],none,none},unknown}],{4,any}}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,atom,any,unknown},{c,binary,[8,0],unknown}]},{emqx_management_proto_v1,list_subscriptions,1} => {any,[{c,atom,any,unknown}]},{emqx_bridge_proto_v1,restart_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_alarm,deactivate,1} => {any,[any]},{emqx_mgmt_cluster_proto_v1,invite_node,2} => {any,[{c,atom,any,unknown},{c,atom,any,unknown}]},{emqx_proto_v1,delete_all_deactivated_alarms,1} => {any,[{c,atom,any,unknown}]},{emqx_conf_proto_v1,reset,3} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_conf_proto_v1,reset,2} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx,reset_config,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,atom,any,unknown},any},unknown}],unknown}},{{c,atom,[post_config_update],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[raw_config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,binary,[8,0],unknown},any},unknown}],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},any]},{emqx_gateway_cm_proto_v1,takeover_session,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_bridge_proto_v1,restart_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_telemetry,get_cluster_uuid,0} => {any,[]},{emqx_proto_v1,get_alarms,2} => {any,[{c,atom,any,unknown},{c,atom,[activated,all,deactivated],unknown}]},{emqx_gateway_cm,do_set_chan_info,4} => {{c,atom,[false,true],unknown},[any,any,any,any]},{emqx_broker_proto_v1,forward,3} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown},{c,tuple,[{c,atom,[delivery],unknown},{c,identifier,[pid],unknown},{c,tuple,[{c,atom,[message],unknown},{c,binary,[8,0],unknown},any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,map,{[],{c,atom,any,unknown},{c,atom,[false,true],unknown}},unknown},{c,map,{[{{c,atom,[allow_publish],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[peerhost],unknown},optional,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown}},{{c,atom,[properties],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[proto_ver],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}},{{c,atom,[protocol],unknown},optional,{c,atom,any,unknown}},{{c,atom,[username],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}}],{c,atom,any,unknown},any},unknown},{c,binary,[8,0],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,number,any,integer},any],{10,{c,atom,[message],unknown}}}],{3,{c,atom,[delivery],unknown}}}]},{emqx_alarm,get_alarms,1} => {any,[{c,atom,[activated,all,deactivated],unknown}]},{emqx_resource_proto_v1,reset_metrics,1} => {any,[{c,binary,[8,0],unknown}]},{emqx_mgmt_trace_proto_v1,trace_file,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_persistent_session_proto_v1,resume_end,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,identifier,[pid],unknown},{c,binary,[8,0],unknown}]},{emqx_metrics_worker,get_metrics,2} => {{c,map,{[{{c,atom,[counters],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},mandatory,{c,map,{[],any,any},unknown}}],none,none},unknown},[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_slow_subs_proto_v1,get_history,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_proto_v1,clean_authz_cache,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_exhook_proto_v1,server_hooks_metrics,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},any]},{emqx_authn_proto_v1,lookup_from_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,atom,any,unknown},{c,binary,[8,0],unknown}]}}}. +#{api => #{{emqx_gateway_cm,1} => #{calls => [{{emqx_gateway_cm_proto_v1,cast,['GwName','ClientId','ChanPid','Req']},{emqx_gateway_cm,do_cast,['GwName','ClientId','ChanPid','Req']}},{{emqx_gateway_cm_proto_v1,call,['GwName','ClientId','ChanPid','Req']},{emqx_gateway_cm,do_call,['GwName','ClientId','ChanPid','Req']}},{{emqx_gateway_cm_proto_v1,call,['GwName','ClientId','ChanPid','Req','Timeout']},{emqx_gateway_cm,do_call,['GwName','ClientId','ChanPid','Req','Timeout']}},{{emqx_gateway_cm_proto_v1,takeover_session,['GwName','ClientId','ChanPid']},{emqx_gateway_cm,do_takeover_session,['GwName','ClientId','ChanPid']}},{{emqx_gateway_cm_proto_v1,get_chann_conn_mod,['GwName','ClientId','ChanPid']},{emqx_gateway_cm,do_get_chann_conn_mod,['GwName','ClientId','ChanPid']}},{{emqx_gateway_cm_proto_v1,kick_session,['GwName','Action','ClientId','ChanPid']},{emqx_gateway_cm,do_kick_session,['GwName','Action','ClientId','ChanPid']}},{{emqx_gateway_cm_proto_v1,set_chan_stats,['GwName','ClientId','ChanPid','Stats']},{emqx_gateway_cm,do_set_chan_stats,['GwName','ClientId','ChanPid','Stats']}},{{emqx_gateway_cm_proto_v1,get_chan_stats,['GwName','ClientId','ChanPid']},{emqx_gateway_cm,do_get_chan_stats,['GwName','ClientId','ChanPid']}},{{emqx_gateway_cm_proto_v1,set_chan_info,['GwName','ClientId','ChanPid','Infos']},{emqx_gateway_cm,do_set_chan_info,['GwName','ClientId','ChanPid','Infos']}},{{emqx_gateway_cm_proto_v1,get_chan_info,['GwName','ClientId','ChanPid']},{emqx_gateway_cm,do_get_chan_info,['GwName','ClientId','ChanPid']}},{{emqx_gateway_cm_proto_v1,lookup_by_clientid,['Nodes','GwName','ClientId']},{emqx_gateway_cm,do_lookup_by_clientid,['GwName','ClientId']}}],casts => []},{emqx_authn,1} => #{calls => [{{emqx_authn_proto_v1,lookup_from_all_nodes,['Nodes','ChainName','AuthenticatorID']},{emqx_authn_api,lookup_from_local_node,['ChainName','AuthenticatorID']}}],casts => []},{emqx_bridge,4} => #{calls => [{{emqx_bridge_proto_v4,get_metrics_from_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_api,get_metrics_from_local_node,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v4,lookup_from_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_api,lookup_from_local_node,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v4,stop_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,stop,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v4,start_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,start,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v4,restart_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,restart,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v4,stop_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,stop,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v4,start_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,start,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v4,restart_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,restart,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v4,list_bridges_on_nodes,['Nodes']},{emqx_bridge,list,[]}}],casts => []},{emqx_gateway_api_listeners,1} => #{calls => [{{emqx_gateway_api_listeners_proto_v1,listeners_cluster_status,['Nodes','Listeners']},{emqx_gateway_api_listeners,do_listeners_cluster_status,['Listeners']}}],casts => []},{emqx_slow_subs,1} => #{calls => [{{emqx_slow_subs_proto_v1,get_history,['Nodes']},{emqx_slow_subs_api,get_history,[]}},{{emqx_slow_subs_proto_v1,clear_history,['Nodes']},{emqx_slow_subs,clear_history,[]}}],casts => []},{emqx_license,1} => #{calls => [{{emqx_license_proto_v1,remote_connection_counts,['Nodes']},{emqx_license_resources,local_connection_count,[]}}],casts => []},{emqx_exhook,1} => #{calls => [{{emqx_exhook_proto_v1,server_hooks_metrics,['Nodes','Name']},{emqx_exhook_mgr,server_hooks_metrics,['Name']}},{{emqx_exhook_proto_v1,server_info,['Nodes','Name']},{emqx_exhook_mgr,server_info,['Name']}},{{emqx_exhook_proto_v1,all_servers_info,['Nodes']},{emqx_exhook_mgr,all_servers_info,[]}}],casts => []},{emqx_cm,1} => #{calls => [{{emqx_cm_proto_v1,kick_session,['Action','ClientId','ChanPid']},{emqx_cm,do_kick_session,['Action','ClientId','ChanPid']}},{{emqx_cm_proto_v1,takeover_session,['ClientId','ChanPid']},{emqx_cm,takeover_session,['ClientId','ChanPid']}},{{emqx_cm_proto_v1,get_chann_conn_mod,['ClientId','ChanPid']},{emqx_cm,do_get_chann_conn_mod,['ClientId','ChanPid']}},{{emqx_cm_proto_v1,get_chan_info,['ClientId','ChanPid']},{emqx_cm,do_get_chan_info,['ClientId','ChanPid']}},{{emqx_cm_proto_v1,get_chan_stats,['ClientId','ChanPid']},{emqx_cm,do_get_chan_stats,['ClientId','ChanPid']}},{{emqx_cm_proto_v1,lookup_client,['Node','Key']},{emqx_cm,lookup_client,['Key']}},{{emqx_cm_proto_v1,kickout_client,['Node','ClientId']},{emqx_cm,kick_session,['ClientId']}}],casts => []},{emqx_license,2} => #{calls => [{{emqx_license_proto_v2,remote_connection_counts,['Nodes']},{emqx_license_resources,local_connection_count,[]}}],casts => []},{emqx_retainer,1} => #{calls => [{{emqx_retainer_proto_v1,wait_dispatch_complete,['Nodes','Timeout']},{emqx_retainer_dispatcher,wait_dispatch_complete,['Timeout']}}],casts => []},{emqx_node_rebalance,1} => #{calls => [{{emqx_node_rebalance_proto_v1,disconnected_session_counts,['Nodes']},{emqx_node_rebalance,disconnected_session_count,[]}},{{emqx_node_rebalance_proto_v1,disable_rebalance_agent,['Nodes','OwnerPid']},{emqx_node_rebalance_agent,disable,['OwnerPid']}},{{emqx_node_rebalance_proto_v1,enable_rebalance_agent,['Nodes','OwnerPid']},{emqx_node_rebalance_agent,enable,['OwnerPid']}},{{emqx_node_rebalance_proto_v1,session_counts,['Nodes']},{emqx_node_rebalance,session_count,[]}},{{emqx_node_rebalance_proto_v1,connection_counts,['Nodes']},{emqx_node_rebalance,connection_count,[]}},{{emqx_node_rebalance_proto_v1,evict_sessions,['Nodes','Count','RecipientNodes','ConnState']},{emqx_eviction_agent,evict_sessions,['Count','RecipientNodes','ConnState']}},{{emqx_node_rebalance_proto_v1,evict_connections,['Nodes','Count']},{emqx_eviction_agent,evict_connections,['Count']}},{{emqx_node_rebalance_proto_v1,available_nodes,['Nodes']},{emqx_node_rebalance,is_node_available,[]}}],casts => []},{emqx_broker,1} => #{calls => [{{emqx_broker_proto_v1,list_subscriptions_via_topic,['Node','Topic']},{emqx_broker,subscriptions_via_topic,['Topic']}},{{emqx_broker_proto_v1,list_client_subscriptions,['Node','ClientId']},{emqx_broker,subscriptions,['ClientId']}},{{emqx_broker_proto_v1,forward,['Node','Topic','Delivery']},{emqx_broker,dispatch,['Topic','Delivery']}}],casts => [{{emqx_broker_proto_v1,forward_async,['Node','Topic','Delivery']},{emqx_broker,dispatch,['Topic','Delivery']}}]},{emqx_topic_metrics,1} => #{calls => [{{emqx_topic_metrics_proto_v1,reset,['Nodes','Topic']},{emqx_topic_metrics,reset,['Topic']}},{{emqx_topic_metrics_proto_v1,reset,['Nodes']},{emqx_topic_metrics,reset,[]}},{{emqx_topic_metrics_proto_v1,metrics,['Nodes','Topic']},{emqx_topic_metrics,metrics,['Topic']}},{{emqx_topic_metrics_proto_v1,metrics,['Nodes']},{emqx_topic_metrics,metrics,[]}}],casts => []},{emqx_resource,1} => #{calls => [{{emqx_resource_proto_v1,reset_metrics,['ResId']},{emqx_resource,reset_metrics_local,['ResId']}},{{emqx_resource_proto_v1,remove,['ResId']},{emqx_resource,remove_local,['ResId']}},{{emqx_resource_proto_v1,recreate,['ResId','ResourceType','Config','Opts']},{emqx_resource,recreate_local,['ResId','ResourceType','Config','Opts']}},{{emqx_resource_proto_v1,create_dry_run,['ResourceType','Config']},{emqx_resource,create_dry_run_local,['ResourceType','Config']}},{{emqx_resource_proto_v1,create,['ResId','Group','ResourceType','Config','Opts']},{emqx_resource,create_local,['ResId','Group','ResourceType','Config','Opts']}}],casts => []},{emqx_bridge,1} => #{calls => [{{emqx_bridge_proto_v1,lookup_from_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_api,lookup_from_local_node,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v1,stop_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,stop,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v1,restart_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,restart,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v1,stop_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,stop,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v1,restart_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,restart,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v1,list_bridges,['Node']},{emqx_bridge,list,[]}}],casts => []},{emqx_mgmt_trace,1} => #{calls => [{{emqx_mgmt_trace_proto_v1,read_trace_file,['Node','Name','Position','Limit']},{emqx_mgmt_api_trace,read_trace_file,['Name','Position','Limit']}},{{emqx_mgmt_trace_proto_v1,trace_file,['Nodes','File']},{emqx_trace,trace_file,['File']}},{{emqx_mgmt_trace_proto_v1,get_trace_size,['Nodes']},{emqx_mgmt_api_trace,get_trace_size,[]}}],casts => []},{emqx_ft_storage_fs_reader,1} => #{calls => [{{emqx_ft_storage_fs_reader_proto_v1,read,['Node','Pid','Bytes']},{emqx_ft_storage_fs_reader,read,['Pid','Bytes']}}],casts => []},{emqx_bridge,2} => #{calls => [{{emqx_bridge_proto_v2,lookup_from_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_api,lookup_from_local_node,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v2,stop_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,stop,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v2,start_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,start,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v2,restart_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,restart,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v2,stop_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,stop,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v2,start_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,start,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v2,restart_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,restart,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v2,list_bridges,['Node']},{emqx_bridge,list,[]}}],casts => []},{emqx_eviction_agent,1} => #{calls => [{{emqx_eviction_agent_proto_v1,evict_session_channel,['Node','ClientId','ConnInfo','ClientInfo']},{emqx_eviction_agent,evict_session_channel,['ClientId','ConnInfo','ClientInfo']}}],casts => []},{emqx_node_rebalance_api,1} => #{calls => [{{emqx_node_rebalance_api_proto_v1,node_rebalance_stop,['Node']},{emqx_node_rebalance,stop,[]}},{{emqx_node_rebalance_api_proto_v1,node_rebalance_start,['Node','Opts']},{emqx_node_rebalance,start,['Opts']}},{{emqx_node_rebalance_api_proto_v1,node_rebalance_evacuation_stop,['Node']},{emqx_node_rebalance_evacuation,stop,[]}},{{emqx_node_rebalance_api_proto_v1,node_rebalance_evacuation_start,['Node','Opts']},{emqx_node_rebalance_evacuation,start,['Opts']}}],casts => []},{emqx,1} => #{calls => [{{emqx_proto_v1,delete_all_deactivated_alarms,['Node']},{emqx_alarm,delete_all_deactivated_alarms,[]}},{{emqx_proto_v1,deactivate_alarm,['Node','Name']},{emqx_alarm,deactivate,['Name']}},{{emqx_proto_v1,clean_pem_cache,['Node']},{ssl_pem_cache,clear,[]}},{{emqx_proto_v1,clean_authz_cache,['Node']},{emqx_authz_cache,drain_cache,[]}},{{emqx_proto_v1,clean_authz_cache,['Node','ClientId']},{emqx_authz_cache,drain_cache,['ClientId']}},{{emqx_proto_v1,get_metrics,['Node']},{emqx_metrics,all,[]}},{{emqx_proto_v1,get_stats,['Node']},{emqx_stats,getstats,[]}},{{emqx_proto_v1,get_alarms,['Node','Type']},{emqx_alarm,get_alarms,['Type']}},{{emqx_proto_v1,is_running,['Node']},{emqx,is_running,[]}}],casts => []},{emqx_shared_sub,1} => #{calls => [{{emqx_shared_sub_proto_v1,dispatch_with_ack,['Pid','Group','Topic','Msg','Timeout']},{emqx_shared_sub,do_dispatch_with_ack,['Pid','Group','Topic','Msg']}}],casts => [{{emqx_shared_sub_proto_v1,send,['Node','Pid','Topic','Msg']},{erlang,send,['Pid','Msg']}}]},{emqx_management,1} => #{calls => [{{emqx_management_proto_v1,get_full_config,['Node']},{emqx_mgmt_api_configs,get_full_config,[]}},{{emqx_management_proto_v1,call_client,['Node','ClientId','Req']},{emqx_mgmt,do_call_client,['ClientId','Req']}},{{emqx_management_proto_v1,unsubscribe,['Node','ClientId','Topic']},{emqx_mgmt,do_unsubscribe,['ClientId','Topic']}},{{emqx_management_proto_v1,subscribe,['Node','ClientId','TopicTables']},{emqx_mgmt,do_subscribe,['ClientId','TopicTables']}},{{emqx_management_proto_v1,list_listeners,['Node']},{emqx_mgmt_api_listeners,do_list_listeners,[]}},{{emqx_management_proto_v1,list_subscriptions,['Node']},{emqx_mgmt,do_list_subscriptions,[]}},{{emqx_management_proto_v1,broker_info,['Node']},{emqx_mgmt,broker_info,[]}},{{emqx_management_proto_v1,node_info,['Node']},{emqx_mgmt,node_info,[]}}],casts => []},{emqx_persistent_session,1} => #{calls => [{{emqx_persistent_session_proto_v1,resume_end,['Nodes','Pid','SessionID']},{emqx_session_router,resume_end,['Pid','SessionID']}},{{emqx_persistent_session_proto_v1,resume_begin,['Nodes','Pid','SessionID']},{emqx_session_router,resume_begin,['Pid','SessionID']}}],casts => []},{emqx_bridge,3} => #{calls => [{{emqx_bridge_proto_v3,lookup_from_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_api,lookup_from_local_node,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v3,stop_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,stop,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v3,start_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,start,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v3,restart_bridges_to_all_nodes,['Nodes','BridgeType','BridgeName']},{emqx_bridge_resource,restart,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v3,stop_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,stop,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v3,start_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,start,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v3,restart_bridge_to_node,['Node','BridgeType','BridgeName']},{emqx_bridge_resource,restart,['BridgeType','BridgeName']}},{{emqx_bridge_proto_v3,list_bridges_on_nodes,['Nodes']},{emqx_bridge,list,[]}},{{emqx_bridge_proto_v3,list_bridges,['Node']},{emqx_bridge,list,[]}}],casts => []},{emqx_mgmt_api_plugins,1} => #{calls => [{{emqx_mgmt_api_plugins_proto_v1,ensure_action,['Name','Action']},{emqx_mgmt_api_plugins,ensure_action,['Name','Action']}},{{emqx_mgmt_api_plugins_proto_v1,delete_package,['Name']},{emqx_mgmt_api_plugins,delete_package,['Name']}},{{emqx_mgmt_api_plugins_proto_v1,describe_package,['Name']},{emqx_mgmt_api_plugins,describe_package,['Name']}},{{emqx_mgmt_api_plugins_proto_v1,install_package,['Filename','Bin']},{emqx_mgmt_api_plugins,install_package,['Filename','Bin']}},{{emqx_mgmt_api_plugins_proto_v1,get_plugins,[]},{emqx_mgmt_api_plugins,get_plugins,[]}}],casts => []},{emqx_mgmt_trace,2} => #{calls => [{{emqx_mgmt_trace_proto_v2,read_trace_file,['Node','Name','Position','Limit']},{emqx_mgmt_api_trace,read_trace_file,['Name','Position','Limit']}},{{emqx_mgmt_trace_proto_v2,trace_file_detail,['Nodes','File']},{emqx_trace,trace_file_detail,['File']}},{{emqx_mgmt_trace_proto_v2,trace_file,['Nodes','File']},{emqx_trace,trace_file,['File']}},{{emqx_mgmt_trace_proto_v2,get_trace_size,['Nodes']},{emqx_mgmt_api_trace,get_trace_size,[]}}],casts => []},{emqx_gateway_http,1} => #{calls => [{{emqx_gateway_http_proto_v1,get_cluster_status,['Nodes','GwName']},{emqx_gateway_http,gateway_status,['GwName']}}],casts => []},{emqx_node_rebalance_status,1} => #{calls => [{{emqx_node_rebalance_status_proto_v1,evacuation_status,['Nodes']},{emqx_node_rebalance_status,evacuation_status,[]}},{{emqx_node_rebalance_status_proto_v1,rebalance_status,['Nodes']},{emqx_node_rebalance_status,rebalance_status,[]}},{{emqx_node_rebalance_status_proto_v1,local_status,['Node']},{emqx_node_rebalance_status,local_status,[]}}],casts => []},{emqx_authz,1} => #{calls => [{{emqx_authz_proto_v1,lookup_from_all_nodes,['Nodes','Type']},{emqx_authz_api_sources,lookup_from_local_node,['Type']}}],casts => []},{emqx_plugins,1} => #{calls => [{{emqx_plugins_proto_v1,get_tar,['Node','NameVsn','Timeout']},{emqx_plugins,get_tar,['NameVsn']}}],casts => []},{emqx_ft_storage_exporter_fs,1} => #{calls => [{{emqx_ft_storage_exporter_fs_proto_v1,read_export_file,['Node','Filepath','CallerPid']},{emqx_ft_storage_exporter_fs_proxy,read_export_file_local,['Filepath','CallerPid']}},{{emqx_ft_storage_exporter_fs_proto_v1,list_exports,['Nodes','Query']},{emqx_ft_storage_exporter_fs_proxy,list_exports_local,['Query']}}],casts => []},{emqx_prometheus,1} => #{calls => [{{emqx_prometheus_proto_v1,stop,['Nodes']},{emqx_prometheus,do_stop,[]}},{{emqx_prometheus_proto_v1,start,['Nodes']},{emqx_prometheus,do_start,[]}}],casts => []},{emqx,2} => #{calls => [{{emqx_proto_v2,delete_all_deactivated_alarms,['Node']},{emqx_alarm,delete_all_deactivated_alarms,[]}},{{emqx_proto_v2,deactivate_alarm,['Node','Name']},{emqx_alarm,deactivate,['Name']}},{{emqx_proto_v2,clean_pem_cache,['Node']},{ssl_pem_cache,clear,[]}},{{emqx_proto_v2,clean_authz_cache,['Node']},{emqx_authz_cache,drain_cache,[]}},{{emqx_proto_v2,clean_authz_cache,['Node','ClientId']},{emqx_authz_cache,drain_cache,['ClientId']}},{{emqx_proto_v2,get_metrics,['Node']},{emqx_metrics,all,[]}},{{emqx_proto_v2,get_stats,['Node']},{emqx_stats,getstats,[]}},{{emqx_proto_v2,get_alarms,['Node','Type']},{emqx_alarm,get_alarms,['Type']}},{{emqx_proto_v2,are_running,['Nodes']},{emqx,is_running,[]}},{{emqx_proto_v2,is_running,['Node']},{emqx,is_running,[]}}],casts => []},{emqx_delayed,1} => #{calls => [{{emqx_delayed_proto_v1,delete_delayed_message,['Node','Id']},{emqx_delayed,delete_delayed_message,['Id']}},{{emqx_delayed_proto_v1,get_delayed_message,['Node','Id']},{emqx_delayed,get_delayed_message,['Id']}}],casts => []},{emqx_conf,2} => #{calls => [{{emqx_conf_proto_v2,get_override_config_file,['Nodes']},{emqx_conf_app,get_override_config_file,[]}},{{emqx_conf_proto_v2,reset,['Node','KeyPath','Opts']},{emqx,reset_config,['KeyPath','Opts']}},{{emqx_conf_proto_v2,reset,['KeyPath','Opts']},{emqx,reset_config,['KeyPath','Opts']}},{{emqx_conf_proto_v2,remove_config,['Node','KeyPath','Opts']},{emqx,remove_config,['KeyPath','Opts']}},{{emqx_conf_proto_v2,remove_config,['KeyPath','Opts']},{emqx,remove_config,['KeyPath','Opts']}},{{emqx_conf_proto_v2,update,['Node','KeyPath','UpdateReq','Opts']},{emqx,update_config,['KeyPath','UpdateReq','Opts']}},{{emqx_conf_proto_v2,update,['KeyPath','UpdateReq','Opts']},{emqx,update_config,['KeyPath','UpdateReq','Opts']}},{{emqx_conf_proto_v2,get_all,['KeyPath']},{emqx_conf,get_node_and_config,['KeyPath']}},{{emqx_conf_proto_v2,get_config,['Node','KeyPath','Default']},{emqx,get_config,['KeyPath','Default']}},{{emqx_conf_proto_v2,get_config,['Node','KeyPath']},{emqx,get_config,['KeyPath']}},{{emqx_conf_proto_v2,sync_data_from_node,['Node']},{emqx_conf_app,sync_data_from_node,[]}}],casts => []},{emqx_plugin_libs,1} => #{calls => [{{emqx_plugin_libs_proto_v1,get_metrics,['Node','HandlerName','MetricId']},{emqx_metrics_worker,get_metrics,['HandlerName','MetricId']}}],casts => []},{emqx_management,2} => #{calls => [{{emqx_management_proto_v2,get_full_config,['Node']},{emqx_mgmt_api_configs,get_full_config,[]}},{{emqx_management_proto_v2,call_client,['Node','ClientId','Req']},{emqx_mgmt,do_call_client,['ClientId','Req']}},{{emqx_management_proto_v2,unsubscribe,['Node','ClientId','Topic']},{emqx_mgmt,do_unsubscribe,['ClientId','Topic']}},{{emqx_management_proto_v2,subscribe,['Node','ClientId','TopicTables']},{emqx_mgmt,do_subscribe,['ClientId','TopicTables']}},{{emqx_management_proto_v2,list_listeners,['Node']},{emqx_mgmt_api_listeners,do_list_listeners,[]}},{{emqx_management_proto_v2,list_subscriptions,['Node']},{emqx_mgmt,do_list_subscriptions,[]}},{{emqx_management_proto_v2,broker_info,['Node']},{emqx_mgmt,broker_info,[]}},{{emqx_management_proto_v2,node_info,['Node']},{emqx_mgmt,node_info,[]}},{{emqx_management_proto_v2,unsubscribe_batch,['Node','ClientId','Topics']},{emqx_mgmt,do_unsubscribe_batch,['ClientId','Topics']}}],casts => []},{emqx_cm,2} => #{calls => [{{emqx_cm_proto_v2,kick_session,['Action','ClientId','ChanPid']},{emqx_cm,do_kick_session,['Action','ClientId','ChanPid']}},{{emqx_cm_proto_v2,takeover_finish,['ConnMod','ChanPid']},{emqx_cm,takeover_finish,['ConnMod','ChanPid']}},{{emqx_cm_proto_v2,takeover_session,['ClientId','ChanPid']},{emqx_cm,takeover_session,['ClientId','ChanPid']}},{{emqx_cm_proto_v2,get_chann_conn_mod,['ClientId','ChanPid']},{emqx_cm,do_get_chann_conn_mod,['ClientId','ChanPid']}},{{emqx_cm_proto_v2,get_chan_info,['ClientId','ChanPid']},{emqx_cm,do_get_chan_info,['ClientId','ChanPid']}},{{emqx_cm_proto_v2,get_chan_stats,['ClientId','ChanPid']},{emqx_cm,do_get_chan_stats,['ClientId','ChanPid']}},{{emqx_cm_proto_v2,lookup_client,['Node','Key']},{emqx_cm,lookup_client,['Key']}},{{emqx_cm_proto_v2,kickout_client,['Node','ClientId']},{emqx_cm,kick_session,['ClientId']}}],casts => []},{emqx_conf,1} => #{calls => [{{emqx_conf_proto_v1,get_override_config_file,['Nodes']},{emqx_conf_app,get_override_config_file,[]}},{{emqx_conf_proto_v1,reset,['Node','KeyPath','Opts']},{emqx,reset_config,['KeyPath','Opts']}},{{emqx_conf_proto_v1,reset,['KeyPath','Opts']},{emqx,reset_config,['KeyPath','Opts']}},{{emqx_conf_proto_v1,remove_config,['Node','KeyPath','Opts']},{emqx,remove_config,['KeyPath','Opts']}},{{emqx_conf_proto_v1,remove_config,['KeyPath','Opts']},{emqx,remove_config,['KeyPath','Opts']}},{{emqx_conf_proto_v1,update,['Node','KeyPath','UpdateReq','Opts']},{emqx,update_config,['KeyPath','UpdateReq','Opts']}},{{emqx_conf_proto_v1,update,['KeyPath','UpdateReq','Opts']},{emqx,update_config,['KeyPath','UpdateReq','Opts']}},{{emqx_conf_proto_v1,get_all,['KeyPath']},{emqx_conf,get_node_and_config,['KeyPath']}},{{emqx_conf_proto_v1,get_config,['Node','KeyPath','Default']},{emqx,get_config,['KeyPath','Default']}},{{emqx_conf_proto_v1,get_config,['Node','KeyPath']},{emqx,get_config,['KeyPath']}}],casts => []},{emqx_ft_storage_fs,1} => #{calls => [{{emqx_ft_storage_fs_proto_v1,list_assemblers,['Nodes','Transfer']},{emqx_ft_storage_fs_proxy,lookup_local_assembler,['Transfer']}},{{emqx_ft_storage_fs_proto_v1,pread,['Node','Transfer','Frag','Offset','Size']},{emqx_ft_storage_fs_proxy,pread_local,['Transfer','Frag','Offset','Size']}},{{emqx_ft_storage_fs_proto_v1,multilist,['Nodes','Transfer','What']},{emqx_ft_storage_fs_proxy,list_local,['Transfer','What']}}],casts => []},{emqx_mgmt_cluster,1} => #{calls => [{{emqx_mgmt_cluster_proto_v1,invite_node,['Node','Self']},{emqx_mgmt_api_cluster,join,['Self']}}],casts => []},{emqx_management,3} => #{calls => [{{emqx_management_proto_v3,get_full_config,['Node']},{emqx_mgmt_api_configs,get_full_config,[]}},{{emqx_management_proto_v3,call_client,['Node','ClientId','Req']},{emqx_mgmt,do_call_client,['ClientId','Req']}},{{emqx_management_proto_v3,unsubscribe,['Node','ClientId','Topic']},{emqx_mgmt,do_unsubscribe,['ClientId','Topic']}},{{emqx_management_proto_v3,subscribe,['Node','ClientId','TopicTables']},{emqx_mgmt,do_subscribe,['ClientId','TopicTables']}},{{emqx_management_proto_v3,list_listeners,['Node']},{emqx_mgmt_api_listeners,do_list_listeners,[]}},{{emqx_management_proto_v3,list_subscriptions,['Node']},{emqx_mgmt,do_list_subscriptions,[]}},{{emqx_management_proto_v3,broker_info,['Nodes']},{emqx_mgmt,broker_info,[]}},{{emqx_management_proto_v3,node_info,['Nodes']},{emqx_mgmt,node_info,[]}},{{emqx_management_proto_v3,unsubscribe_batch,['Node','ClientId','Topics']},{emqx_mgmt,do_unsubscribe_batch,['ClientId','Topics']}}],casts => []},{emqx_telemetry,1} => #{calls => [{{emqx_telemetry_proto_v1,get_cluster_uuid,['Node']},{emqx_telemetry,get_cluster_uuid,[]}},{{emqx_telemetry_proto_v1,get_node_uuid,['Node']},{emqx_telemetry,get_node_uuid,[]}}],casts => []},{emqx_node_rebalance_evacuation,1} => #{calls => [{{emqx_node_rebalance_evacuation_proto_v1,available_nodes,['Nodes']},{emqx_node_rebalance_evacuation,is_node_available,[]}}],casts => []},{emqx_retainer,2} => #{calls => [{{emqx_retainer_proto_v2,active_mnesia_indices,['Nodes']},{emqx_retainer_mnesia,active_indices,[]}},{{emqx_retainer_proto_v2,wait_dispatch_complete,['Nodes','Timeout']},{emqx_retainer_dispatcher,wait_dispatch_complete,['Timeout']}}],casts => []},{emqx_rule_engine,1} => #{calls => [{{emqx_rule_engine_proto_v1,reset_metrics,['RuleId']},{emqx_rule_engine,reset_metrics_for_rule,['RuleId']}}],casts => []},{emqx_dashboard,1} => #{calls => [{{emqx_dashboard_proto_v1,current_rate,['Node']},{emqx_dashboard_monitor,current_rate,['Node']}},{{emqx_dashboard_proto_v1,do_sample,['Node','Latest']},{emqx_dashboard_monitor,do_sample,['Node','Latest']}}],casts => []}},release => "5.0",signatures => #{{emqx_bridge_proto_v2,stop_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_conf_proto_v1,remove_config,2} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_alarm,deactivate,1} => {any,[any]},{emqx_cm,do_get_chan_info,2} => {any,[any,any]},{emqx_ft_storage_fs_proxy,pread_local,4} => {any,[any,any,any,any]},{emqx_node_rebalance_status,evacuation_status,0} => {{c,tuple,[{c,atom,any,unknown},{c,union,[{c,atom,[disabled],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[enabled],unknown},{c,map,{[{{c,atom,[conn_evict_rate],unknown},mandatory,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[current_conns],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[current_sessions],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[initial_conns],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[initial_sessions],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[migrate_to],unknown},mandatory,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}},{{c,atom,[server_reference],unknown},mandatory,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[sess_evict_rate],unknown},mandatory,{c,number,{int_rng,1,pos_inf},integer}}],none,none},unknown}],{2,{c,atom,[enabled],unknown}}},none,none,none],unknown}],{2,any}},[]},{emqx_bridge_proto_v4,start_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_cm_proto_v1,lookup_client,2} => {any,[{c,atom,any,unknown},{c,tuple_set,[{2,[{c,tuple,[{c,atom,[clientid],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}],{2,{c,atom,[clientid],unknown}}},{c,tuple,[{c,atom,[username],unknown},{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}],{2,{c,atom,[username],unknown}}}]}],unknown}]},{emqx_bridge_proto_v3,stop_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_conf_proto_v1,update,3} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},any,{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_plugins,get_tar,1} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,binary,[8,0],unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown}]},{emqx_broker,subscriptions_via_topic,1} => {{c,list,[any,{c,nil,[],unknown}],unknown},[any]},{emqx_cm,takeover_session,2} => {{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[{c,tuple,[{c,atom,any,unknown},any],{2,any}},{c,nil,[],unknown}],unknown},none,{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[expired],unknown},{c,tuple,[{c,atom,[session],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,binary,[0,128],unknown},{c,atom,[false,true],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,atom,[false,true],unknown},{c,opaque,[{opaque,emqx_inflight,inflight,[],{c,tuple,[any,any,any],{3,any}}}],unknown},{c,tuple,[any,any,any,any,any,any,any,any,any,any,any],{11,any}},{c,number,{int_rng,1,1114111},integer},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,number,{int_rng,1,pos_inf},integer}],{15,{c,atom,[session],unknown}}}],{2,{c,atom,[expired],unknown}}},{c,tuple,[{c,atom,[ok],unknown},any],{2,{c,atom,[ok],unknown}}},{c,tuple,[{c,atom,[persistent],unknown},{c,tuple,[{c,atom,[session],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,binary,[0,128],unknown},{c,atom,[false,true],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,atom,[false,true],unknown},{c,opaque,[{opaque,emqx_inflight,inflight,[],{c,tuple,[any,any,any],{3,any}}}],unknown},{c,tuple,[any,any,any,any,any,any,any,any,any,any,any],{11,any}},{c,number,{int_rng,1,1114111},integer},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,number,{int_rng,1,pos_inf},integer}],{15,{c,atom,[session],unknown}}}],{2,{c,atom,[persistent],unknown}}}]},{3,[{c,tuple,[{c,atom,[ok],unknown},{c,list,[any,{c,nil,[],unknown}],unknown},any],{3,{c,atom,[ok],unknown}}}]},{4,[{c,tuple,[{c,atom,[living],unknown},{c,atom,any,unknown},{c,identifier,[pid],unknown},{c,union,[none,none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,{c,tuple,[{c,atom,[session],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,binary,[0,128],unknown},{c,atom,[false,true],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,atom,[false,true],unknown},{c,opaque,[{opaque,emqx_inflight,inflight,[],{c,tuple,[any,any,any],{3,any}}}],unknown},{c,tuple,[any,any,any,any,any,any,any,any,any,any,any],{11,any}},{c,number,{int_rng,1,1114111},integer},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,number,{int_rng,1,pos_inf},integer}],{15,{c,atom,[session],unknown}}},none,none,none],unknown}],{4,{c,atom,[living],unknown}}}]}],unknown},none,none,{c,map,{[],{c,atom,any,unknown},any},unknown}],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},any]},{emqx_bridge_proto_v4,restart_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_resource_proto_v1,create,5} => {any,[{c,binary,[8,0],unknown},{c,binary,[8,0],unknown},{c,atom,any,unknown},any,{c,map,{[{{c,atom,[auto_restart_interval],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[auto_retry_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[batch_size],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[batch_time],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[health_check_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_timeout],unknown},optional,{c,number,any,integer}},{{c,atom,[inflight_window],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[max_buffer_bytes],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[query_mode],unknown},optional,{c,atom,[async,dynamic,sync],unknown}},{{c,atom,[resume_interval],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[start_after_created],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[start_timeout],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[wait_for_resource_ready],unknown},optional,{c,number,any,integer}},{{c,atom,[worker_pool_size],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}}],none,none},unknown}]},{emqx_bridge_proto_v2,restart_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_management_proto_v3,broker_info,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_metrics,all,0} => {{c,list,[{c,tuple,[any,{c,number,any,integer}],{2,any}},{c,nil,[],unknown}],unknown},[]},{emqx_node_rebalance_status_proto_v1,evacuation_status,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_exhook_mgr,all_servers_info,0} => {any,[]},{emqx_alarm,get_alarms,1} => {any,[{c,atom,[activated,all,deactivated],unknown}]},{emqx_plugin_libs_proto_v1,get_metrics,3} => {any,[{c,atom,any,unknown},{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_exhook_proto_v1,all_servers_info,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{ssl_pem_cache,clear,0} => {any,[]},{emqx_resource,remove_local,1} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,binary,[8,0],unknown}]},{emqx_bridge_proto_v4,start_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_bridge,list,0} => {any,[]},{emqx_retainer_proto_v1,wait_dispatch_complete,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}]},{emqx_node_rebalance,stop,0} => {any,[]},{emqx_mgmt_api_cluster,join,1} => {any,[any]},{emqx_node_rebalance_evacuation_proto_v1,available_nodes,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_prometheus,do_stop,0} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},{c,atom,[not_found,restarting,running,simple_one_for_one],unknown}],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[]},{emqx_node_rebalance_api_proto_v1,node_rebalance_evacuation_start,2} => {any,[{c,atom,any,unknown},{c,map,{[{{c,atom,[conn_evict_rate],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[migrate_to],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}},{{c,atom,[server_reference],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[sess_evict_rate],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[wait_takeover],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}}],none,none},unknown}]},{emqx_telemetry_proto_v1,get_node_uuid,1} => {any,[{c,atom,any,unknown}]},{emqx_cm_proto_v2,kickout_client,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_management_proto_v3,node_info,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_conf,get_node_and_config,1} => {{c,tuple,[{c,atom,any,unknown},any],{2,any}},[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown}]},{emqx_gateway_cm_proto_v1,cast,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any]},{emqx_node_rebalance,connection_count,0} => {{c,tuple,[{c,atom,[ok],unknown},any],{2,{c,atom,[ok],unknown}}},[]},{emqx_node_rebalance_status,local_status,0} => {{c,union,[{c,atom,[disabled],unknown},none,none,none,none,none,{c,tuple_set,[{2,[{c,tuple,[{c,atom,[evacuation],unknown},{c,map,{[{{c,atom,[connection_eviction_rate],unknown},mandatory,any},{{c,atom,[connection_goal],unknown},mandatory,{c,number,{int_set,[0]},integer}},{{c,atom,[session_eviction_rate],unknown},mandatory,any},{{c,atom,[session_goal],unknown},mandatory,{c,number,{int_set,[0]},integer}},{{c,atom,[session_recipients],unknown},mandatory,any},{{c,atom,[state],unknown},mandatory,any},{{c,atom,[stats],unknown},mandatory,{c,map,{[{{c,atom,[current_connected],unknown},mandatory,any},{{c,atom,[current_sessions],unknown},mandatory,any},{{c,atom,[initial_connected],unknown},mandatory,any},{{c,atom,[initial_sessions],unknown},mandatory,any}],none,none},unknown}}],none,none},unknown}],{2,{c,atom,[evacuation],unknown}}},{c,tuple,[{c,atom,[rebalance],unknown},{c,map,{[],{c,atom,[connection_eviction_rate,connection_goal,coordinator_node,disconnected_session_goal,recipients,session_eviction_rate,state,stats],unknown},any},unknown}],{2,{c,atom,[rebalance],unknown}}}]}],unknown},none,none,none],unknown},[]},{emqx,is_running,0} => {{c,atom,[false,true],unknown},[]},{emqx_conf_proto_v1,reset,2} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_resource,create_dry_run_local,2} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,atom,any,unknown},any]},{emqx_management_proto_v3,call_client,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},any]},{emqx_cm_proto_v2,takeover_session,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_conf_app,sync_data_from_node,0} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,binary,[8,0],unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[]},{emqx_conf_proto_v1,get_config,3} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown},any]},{emqx_proto_v1,is_running,1} => {any,[{c,atom,any,unknown}]},{emqx_exhook_mgr,server_info,1} => {any,[any]},{emqx_cm_proto_v2,get_chann_conn_mod,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_conf_proto_v2,get_override_config_file,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_management_proto_v1,broker_info,1} => {any,[{c,atom,any,unknown}]},{emqx_bridge_proto_v1,list_bridges,1} => {any,[{c,atom,any,unknown}]},{emqx_mgmt_api_plugins,describe_package,1} => {{c,tuple,[{c,atom,any,unknown},{c,list,[{c,map,{[],any,any},unknown},{c,nil,[],unknown}],unknown}],{2,any}},[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_mgmt_api_plugins_proto_v1,ensure_action,2} => {any,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,atom,[restart,start,stop],unknown}]},{emqx_gateway_cm,do_call,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any]},{emqx_slow_subs_proto_v1,clear_history,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_bridge_proto_v3,list_bridges_on_nodes,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_gateway_cm_proto_v1,get_chan_info,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_cm_proto_v2,get_chan_stats,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_rule_engine_proto_v1,reset_metrics,1} => {any,[{c,binary,[8,0],unknown}]},{emqx_resource_proto_v1,reset_metrics,1} => {any,[{c,binary,[8,0],unknown}]},{emqx_gateway_cm,do_takeover_session,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx,remove_config,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,atom,any,unknown},any},unknown}],unknown}},{{c,atom,[post_config_update],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[raw_config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,binary,[8,0],unknown},any},unknown}],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_management_proto_v3,unsubscribe,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,binary,[8,0],unknown}]},{emqx_cm_proto_v1,get_chann_conn_mod,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_metrics_worker,get_metrics,2} => {{c,map,{[{{c,atom,[counters],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[gauges],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[slides],unknown},mandatory,{c,map,{[],any,any},unknown}}],none,none},unknown},[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_mgmt_api_plugins,install_package,2} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_gateway_api_listeners_proto_v1,listeners_cluster_status,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,list,[any,{c,nil,[],unknown}],unknown}]},{emqx_topic_metrics_proto_v1,reset,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_management_proto_v3,unsubscribe_batch,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,list,[{c,binary,[8,0],unknown},{c,nil,[],unknown}],unknown}]},{emqx_mgmt_api_configs,get_full_config,0} => {{c,map,{[],any,any},unknown},[]},{emqx_management_proto_v2,list_listeners,1} => {any,[{c,atom,any,unknown}]},{emqx_gateway_cm,do_get_chan_stats,3} => {any,[any,any,any]},{emqx_node_rebalance_evacuation,is_node_available,0} => {{c,atom,any,unknown},[]},{emqx_ft_storage_exporter_fs_proto_v1,read_export_file,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_mgmt_trace_proto_v1,read_trace_file,4} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown},{c,number,{int_rng,0,pos_inf},integer},{c,number,{int_rng,0,pos_inf},integer}]},{emqx_exhook_proto_v1,server_hooks_metrics,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},any]},{emqx_proto_v2,get_metrics,1} => {any,[{c,atom,any,unknown}]},{emqx_conf_proto_v2,update,3} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},any,{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_topic_metrics,metrics,0} => {{c,list,[{c,map,{[{{c,atom,[create_time],unknown},mandatory,any},{{c,atom,[metrics],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[reset_time],unknown},optional,any},{{c,atom,[topic],unknown},mandatory,any}],none,none},unknown},{c,nil,[],unknown}],unknown},[]},{emqx_bridge_proto_v3,start_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_delayed,get_delayed_message,1} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[not_found],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[delayed_interval],unknown},mandatory,any},{{c,atom,[delayed_remaining],unknown},mandatory,{c,number,any,integer}},{{c,atom,[expected_at],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[from_clientid],unknown},mandatory,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[from_username],unknown},mandatory,any},{{c,atom,[msgid],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[node],unknown},mandatory,any},{{c,atom,[payload],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown}},{{c,atom,[publish_at],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[qos],unknown},mandatory,any},{{c,atom,[topic],unknown},mandatory,{c,binary,[8,0],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[any]},{emqx_proto_v1,clean_authz_cache,1} => {any,[{c,atom,any,unknown}]},{emqx_proto_v2,clean_authz_cache,1} => {any,[{c,atom,any,unknown}]},{emqx_mgmt,do_unsubscribe_batch,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[channel_not_found],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[unsubscribe],unknown},{c,list,[{c,tuple,[any,any],{2,any}},{c,nil,[],unknown}],unknown}],{2,{c,atom,[unsubscribe],unknown}}}]}],unknown},[any,any]},{emqx_mgmt_api_plugins_proto_v1,describe_package,1} => {any,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_conf_proto_v1,remove_config,3} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_cm_proto_v2,kick_session,3} => {any,[{c,atom,[discard,kick],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_mgmt_trace_proto_v2,read_trace_file,4} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown},{c,number,{int_rng,0,pos_inf},integer},{c,number,{int_rng,0,pos_inf},integer}]},{emqx_gateway_cm,do_lookup_by_clientid,2} => {{c,list,[any,{c,nil,[],unknown}],unknown},[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},{c,number,any,unknown},none,none,none,none],unknown},any]},{emqx_management_proto_v2,list_subscriptions,1} => {any,[{c,atom,any,unknown}]},{emqx_management_proto_v3,get_full_config,1} => {any,[{c,atom,any,unknown}]},{emqx_management_proto_v1,call_client,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},any]},{emqx,update_config,3} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,atom,any,unknown},any},unknown}],unknown}},{{c,atom,[post_config_update],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[raw_config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,binary,[8,0],unknown},any},unknown}],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},any,{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_mgmt_trace_proto_v2,trace_file,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_gateway_http,gateway_status,1} => {{c,map,{[{{c,atom,[current_connections],unknown},optional,any},{{c,atom,[max_connections],unknown},optional,{c,number,{int_set,[0]},integer}},{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[status],unknown},mandatory,{c,atom,[running,stopped,unloaded],unknown}}],none,none},unknown},[{c,atom,any,unknown}]},{emqx_ft_storage_fs_proto_v1,list_assemblers,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,tuple,[{c,binary,[8,0],unknown},{c,binary,[8,0],unknown}],{2,any}}]},{emqx_dashboard_monitor,current_rate,1} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[badrpc],unknown},any],{2,{c,atom,[badrpc],unknown}}},{c,tuple,[{c,atom,[ok],unknown},any],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,atom,any,unknown}]},{emqx_mgmt_api_listeners,do_list_listeners,0} => {{c,map,{[],{c,binary,"( ",unknown},{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[{c,map,{[],any,any},unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}},unknown},[]},{emqx_resource,create_local,5} => {{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[callback_mode],unknown},mandatory,{c,atom,[always_sync,async_if_possible],unknown}},{{c,atom,[config],unknown},mandatory,any},{{c,atom,[error],unknown},mandatory,any},{{c,atom,[id],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[mod],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[query_mode],unknown},mandatory,{c,atom,[async,dynamic,sync],unknown}},{{c,atom,[state],unknown},mandatory,any},{{c,atom,[status],unknown},mandatory,{c,atom,[connected,connecting,disconnected,stopped],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}},[{c,binary,[8,0],unknown},{c,binary,[8,0],unknown},{c,atom,any,unknown},any,{c,map,{[{{c,atom,[auto_restart_interval],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[auto_retry_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[batch_size],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[batch_time],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[health_check_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_timeout],unknown},optional,{c,number,any,integer}},{{c,atom,[inflight_window],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[max_buffer_bytes],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[query_mode],unknown},optional,{c,atom,[async,dynamic,sync],unknown}},{{c,atom,[resume_interval],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[start_after_created],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[start_timeout],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[wait_for_resource_ready],unknown},optional,{c,number,any,integer}},{{c,atom,[worker_pool_size],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}}],none,none},unknown}]},{emqx_management_proto_v2,node_info,1} => {any,[{c,atom,any,unknown}]},{emqx_gateway_cm_proto_v1,get_chann_conn_mod,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_management_proto_v2,broker_info,1} => {any,[{c,atom,any,unknown}]},{emqx_broker,dispatch,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[no_subscribers,not_running],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,number,{int_rng,0,pos_inf},integer}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,binary,[8,0],unknown},{c,tuple,[{c,atom,[delivery],unknown},{c,identifier,[pid],unknown},{c,tuple,[{c,atom,[message],unknown},{c,binary,[8,0],unknown},any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,map,{[],{c,atom,any,unknown},{c,atom,[false,true],unknown}},unknown},{c,map,{[{{c,atom,[allow_publish],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[peerhost],unknown},optional,{c,tuple_set,[{4,[{c,tuple,[any,any,any,any],{4,any}}]},{8,[{c,tuple,[any,any,any,any,any,any,any,any],{8,any}}]}],unknown}},{{c,atom,[properties],unknown},optional,{c,map,{[],any,any},unknown}},{{c,atom,[proto_ver],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}},{{c,atom,[protocol],unknown},optional,{c,atom,any,unknown}},{{c,atom,[username],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}}],{c,atom,any,unknown},any},unknown},{c,binary,[8,0],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,number,any,integer},any],{10,{c,atom,[message],unknown}}}],{3,{c,atom,[delivery],unknown}}}]},{emqx_conf_proto_v1,get_override_config_file,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_session_router,resume_begin,2} => {any,[{c,identifier,[pid],unknown},{c,binary,[8,0],unknown}]},{emqx_proto_v2,get_stats,1} => {any,[{c,atom,any,unknown}]},{emqx_slow_subs_proto_v1,get_history,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_mgmt_trace_proto_v2,get_trace_size,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_ft_storage_fs_proxy,lookup_local_assembler,1} => {any,[any]},{emqx_telemetry,get_node_uuid,0} => {any,[]},{emqx_license_proto_v2,remote_connection_counts,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_proto_v1,get_metrics,1} => {any,[{c,atom,any,unknown}]},{emqx_cm_proto_v2,lookup_client,2} => {any,[{c,atom,any,unknown},{c,tuple_set,[{2,[{c,tuple,[{c,atom,[clientid],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}],{2,{c,atom,[clientid],unknown}}},{c,tuple,[{c,atom,[username],unknown},{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}],{2,{c,atom,[username],unknown}}}]}],unknown}]},{emqx_delayed_proto_v1,delete_delayed_message,2} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown}]},{emqx_node_rebalance_agent,disable,1} => {any,[any]},{emqx_bridge_resource,start,2} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_conf_proto_v2,reset,3} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_ft_storage_fs_reader,read,2} => {any,[{c,identifier,[pid],unknown},{c,number,{int_rng,1,pos_inf},integer}]},{emqx_cm_proto_v2,takeover_finish,2} => {any,[{c,atom,any,unknown},{c,identifier,[pid],unknown}]},{emqx_trace,trace_file,1} => {{c,tuple_set,[{3,[{c,tuple,[{c,atom,[error],unknown},{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},{c,atom,any,unknown}],{3,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},{c,binary,[8,0],unknown}],{3,{c,atom,[ok],unknown}}}]}],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_management_proto_v1,list_subscriptions,1} => {any,[{c,atom,any,unknown}]},{emqx_node_rebalance_proto_v1,session_counts,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_bridge_proto_v1,lookup_from_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_conf_proto_v2,get_config,2} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown}]},{emqx_proto_v1,get_stats,1} => {any,[{c,atom,any,unknown}]},{emqx_ft_storage_fs_proto_v1,pread,5} => {any,[{c,atom,any,unknown},{c,tuple,[{c,binary,[8,0],unknown},{c,binary,[8,0],unknown}],{2,any}},{c,map,{[{{c,atom,[fragment],unknown},mandatory,{c,tuple_set,[{2,[{c,tuple,[{c,atom,[filemeta],unknown},{c,map,{[{{c,atom,[checksum],unknown},optional,{c,tuple,[{c,atom,any,unknown},{c,binary,[8,0],unknown}],{2,any}}},{{c,atom,[expire_at],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[name],unknown},mandatory,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown}},{{c,atom,[segments_ttl],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[size],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[user_data],unknown},optional,{c,union,[{c,atom,[false,null,true],unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[{c,atom,[false,null,true],unknown},{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,any,unknown},none,none,none,{c,map,{[],{c,binary,[8,0],unknown},any},unknown}],unknown},{c,nil,[],unknown}],unknown},{c,number,any,unknown},none,none,none,{c,map,{[],{c,binary,[8,0],unknown},{c,union,[{c,atom,[false,null,true],unknown},{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,any,unknown},none,none,none,{c,map,{[],{c,binary,[8,0],unknown},any},unknown}],unknown}},unknown}],unknown}}],none,none},unknown}],{2,{c,atom,[filemeta],unknown}}},{c,tuple,[{c,atom,[segment],unknown},{c,map,{[{{c,atom,[offset],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[size],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}}],none,none},unknown}],{2,{c,atom,[segment],unknown}}}]}],unknown}},{{c,atom,[path],unknown},mandatory,{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}},{{c,atom,[size],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[timestamp],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}}],none,none},unknown},{c,number,{int_rng,0,pos_inf},integer},{c,number,{int_rng,0,pos_inf},integer}]},{emqx_conf_proto_v2,get_all,1} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown}]},{emqx_authz_cache,drain_cache,1} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},{c,atom,[not_found],unknown}],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_conf_proto_v2,sync_data_from_node,1} => {any,[{c,atom,any,unknown}]},{emqx_node_rebalance_evacuation,start,1} => {any,[{c,map,{[],any,any},unknown}]},{emqx_prometheus_proto_v1,start,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_mgmt,do_unsubscribe,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[channel_not_found],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[unsubscribe],unknown},{c,list,[{c,tuple,[{c,binary,[8,0],unknown},{c,map,{[],any,any},unknown}],{2,any}},{c,nil,[],unknown}],nonempty}],{2,{c,atom,[unsubscribe],unknown}}}]}],unknown},[any,any]},{emqx_ft_storage_exporter_fs_proto_v1,list_exports,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,map,{[{{c,atom,[following],unknown},optional,{c,var,'_LocalCursor',unknown}},{{c,atom,[limit],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[transfer],unknown},optional,{c,tuple,[{c,binary,[8,0],unknown},{c,binary,[8,0],unknown}],{2,any}}}],none,none},unknown}]},{emqx_cm,do_get_chan_stats,2} => {any,[any,any]},{emqx_ft_storage_exporter_fs_proxy,list_exports_local,1} => {any,[any]},{emqx_node_rebalance,start,1} => {any,[{c,map,{[],any,any},unknown}]},{emqx_bridge_proto_v4,list_bridges_on_nodes,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_license_proto_v1,remote_connection_counts,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_cm_proto_v1,takeover_session,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_bridge_proto_v4,restart_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_slow_subs,clear_history,0} => {any,[]},{emqx_mgmt_api_plugins_proto_v1,get_plugins,0} => {any,[]},{emqx_management_proto_v2,get_full_config,1} => {any,[{c,atom,any,unknown}]},{emqx_proto_v2,is_running,1} => {any,[{c,atom,any,unknown}]},{emqx_gateway_cm_proto_v1,set_chan_stats,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},{c,list,[{c,tuple,[{c,atom,any,unknown},any],{2,any}},{c,nil,[],unknown}],unknown}]},{emqx_bridge_api,lookup_from_local_node,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[not_found],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},any],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_management_proto_v3,list_listeners,1} => {any,[{c,atom,any,unknown}]},{emqx_management_proto_v2,unsubscribe_batch,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,list,[{c,binary,[8,0],unknown},{c,nil,[],unknown}],unknown}]},{emqx_bridge_proto_v4,stop_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_mgmt_trace_proto_v1,trace_file,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_bridge_resource,restart,2} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_delayed,delete_delayed_message,1} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},{c,atom,[not_found],unknown}],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[any]},{emqx_mgmt_api_trace,get_trace_size,0} => {{c,map,{[],any,any},unknown},[]},{emqx_shared_sub,do_dispatch_with_ack,4} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,identifier,[pid,port],unknown},any,any,{c,tuple,[{c,atom,[message],unknown},{c,binary,[8,0],unknown},any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,map,{[],{c,atom,any,unknown},{c,atom,[false,true],unknown}},unknown},{c,map,{[{{c,atom,[allow_publish],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[peerhost],unknown},optional,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown}},{{c,atom,[properties],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[proto_ver],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}},{{c,atom,[protocol],unknown},optional,{c,atom,any,unknown}},{{c,atom,[username],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}}],{c,atom,any,unknown},any},unknown},{c,binary,[8,0],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,number,any,integer},any],{10,{c,atom,[message],unknown}}}]},{emqx,reset_config,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,atom,any,unknown},any},unknown}],unknown}},{{c,atom,[post_config_update],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[raw_config],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,{c,map,{[],{c,binary,[8,0],unknown},any},unknown}],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},any]},{emqx_authn_api,lookup_from_local_node,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,tuple,[{c,atom,any,unknown},{c,union,[{c,atom,[not_found_resource],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}],{2,any}}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,tuple,[{c,atom,any,unknown},{c,atom,[connected,connecting,disconnected,stopped],unknown},{c,map,{[{{c,atom,[counters],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[gauges],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[slides],unknown},mandatory,{c,map,{[],any,any},unknown}}],none,none},unknown},{c,map,{[{{c,atom,[counters],unknown},optional,{c,map,{[],any,any},unknown}},{{c,atom,[gauges],unknown},optional,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},optional,{c,map,{[],any,any},unknown}},{{c,atom,[slides],unknown},optional,{c,map,{[],any,any},unknown}}],none,none},unknown}],{4,any}}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,atom,any,unknown},{c,binary,[8,0],unknown}]},{emqx_management_proto_v1,unsubscribe,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,binary,[8,0],unknown}]},{emqx_management_proto_v2,call_client,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},any]},{emqx_persistent_session_proto_v1,resume_end,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,identifier,[pid],unknown},{c,binary,[8,0],unknown}]},{emqx_conf_proto_v2,remove_config,3} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_proto_v2,deactivate_alarm,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_node_rebalance,session_count,0} => {{c,tuple,[{c,atom,[ok],unknown},any],{2,{c,atom,[ok],unknown}}},[]},{emqx_topic_metrics_proto_v1,metrics,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_mgmt_api_plugins_proto_v1,delete_package,1} => {any,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_persistent_session_proto_v1,resume_begin,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,identifier,[pid],unknown},{c,binary,[8,0],unknown}]},{emqx_proto_v1,clean_authz_cache,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_bridge_api,get_metrics_from_local_node,2} => {{c,map,{[{{c,atom,[dropped],unknown},mandatory,{c,number,any,integer}},{{c,atom,['dropped.expired'],unknown},mandatory,{c,number,any,integer}},{{c,atom,['dropped.other'],unknown},mandatory,{c,number,any,integer}},{{c,atom,['dropped.queue_full'],unknown},mandatory,{c,number,any,integer}},{{c,atom,['dropped.resource_not_found'],unknown},mandatory,{c,number,any,integer}},{{c,atom,['dropped.resource_stopped'],unknown},mandatory,{c,number,any,integer}},{{c,atom,[failed],unknown},mandatory,{c,number,any,integer}},{{c,atom,[inflight],unknown},mandatory,any},{{c,atom,[late_reply],unknown},mandatory,{c,number,any,integer}},{{c,atom,[matched],unknown},mandatory,{c,number,any,integer}},{{c,atom,[queuing],unknown},mandatory,any},{{c,atom,[rate],unknown},mandatory,any},{{c,atom,[rate_last5m],unknown},mandatory,any},{{c,atom,[rate_max],unknown},mandatory,any},{{c,atom,[received],unknown},mandatory,{c,number,any,integer}},{{c,atom,[retried],unknown},mandatory,{c,number,any,integer}},{{c,atom,[success],unknown},mandatory,{c,number,any,integer}}],none,none},unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_delayed_proto_v1,get_delayed_message,2} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown}]},{emqx_shared_sub_proto_v1,send,4} => {any,[{c,atom,any,unknown},{c,identifier,[pid],unknown},{c,binary,[8,0],unknown},any]},{emqx_eviction_agent,evict_sessions,3} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},{c,atom,[disabled],unknown}],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[any,{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},any]},{emqx_management_proto_v1,node_info,1} => {any,[{c,atom,any,unknown}]},{emqx_management_proto_v3,subscribe,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,list,[{c,tuple,[{c,binary,[8,0],unknown},{c,map,{[{{c,atom,[nl],unknown},mandatory,{c,number,{int_set,[0,1]},integer}},{{c,atom,[qos],unknown},mandatory,{c,number,{int_set,[0,1,2]},integer}},{{c,atom,[rap],unknown},mandatory,{c,number,{int_set,[0,1]},integer}},{{c,atom,[rh],unknown},mandatory,{c,number,{int_set,[0,1,2]},integer}},{{c,atom,[share],unknown},optional,{c,binary,[8,0],unknown}}],{c,atom,any,unknown},any},unknown}],{2,any}},{c,nil,[],unknown}],unknown}]},{emqx_topic_metrics_proto_v1,reset,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,binary,[8,0],unknown}]},{emqx_node_rebalance_agent,enable,1} => {any,[any]},{emqx_mgmt_trace_proto_v1,get_trace_size,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_node_rebalance_status,rebalance_status,0} => {{c,tuple,[{c,atom,any,unknown},{c,union,[{c,atom,[disabled],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[enabled],unknown},{c,map,{[],any,any},unknown}],{2,{c,atom,[enabled],unknown}}},none,none,none],unknown}],{2,any}},[]},{emqx_license_resources,local_connection_count,0} => {any,[]},{emqx_cm,lookup_client,1} => {{c,list,[any,{c,nil,[],unknown}],unknown},[{c,tuple_set,[{2,[{c,tuple,[{c,atom,[clientid],unknown},any],{2,{c,atom,[clientid],unknown}}},{c,tuple,[{c,atom,[username],unknown},any],{2,{c,atom,[username],unknown}}}]}],unknown}]},{emqx_management_proto_v2,subscribe,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,list,[{c,tuple,[{c,binary,[8,0],unknown},{c,map,{[{{c,atom,[nl],unknown},mandatory,{c,number,{int_set,[0,1]},integer}},{{c,atom,[qos],unknown},mandatory,{c,number,{int_set,[0,1,2]},integer}},{{c,atom,[rap],unknown},mandatory,{c,number,{int_set,[0,1]},integer}},{{c,atom,[rh],unknown},mandatory,{c,number,{int_set,[0,1,2]},integer}},{{c,atom,[share],unknown},optional,{c,binary,[8,0],unknown}}],{c,atom,any,unknown},any},unknown}],{2,any}},{c,nil,[],unknown}],unknown}]},{emqx_cm,kick_session,1} => {{c,atom,[ok],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_cm_proto_v1,kick_session,3} => {any,[{c,atom,[discard,kick],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_dashboard_proto_v1,current_rate,1} => {any,[{c,atom,any,unknown}]},{emqx_bridge_proto_v3,start_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_ft_storage_fs_reader_proto_v1,read,3} => {any,[{c,atom,any,unknown},{c,identifier,[pid],unknown},{c,number,{int_rng,1,pos_inf},integer}]},{emqx_plugins_proto_v1,get_tar,3} => {any,[{c,atom,any,unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}]},{emqx_bridge_proto_v2,start_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_resource,recreate_local,4} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[not_found,updating_to_incorrect_resource_type],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[callback_mode],unknown},mandatory,{c,atom,[always_sync,async_if_possible],unknown}},{{c,atom,[config],unknown},mandatory,any},{{c,atom,[error],unknown},mandatory,any},{{c,atom,[id],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[mod],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[query_mode],unknown},mandatory,{c,atom,[async,dynamic,sync],unknown}},{{c,atom,[state],unknown},mandatory,any},{{c,atom,[status],unknown},mandatory,{c,atom,[connected,connecting,disconnected,stopped],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,binary,[8,0],unknown},{c,atom,any,unknown},any,{c,map,{[{{c,atom,[auto_restart_interval],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[auto_retry_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[batch_size],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[batch_time],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[health_check_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_timeout],unknown},optional,{c,number,any,integer}},{{c,atom,[inflight_window],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[max_buffer_bytes],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[query_mode],unknown},optional,{c,atom,[async,dynamic,sync],unknown}},{{c,atom,[resume_interval],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[start_after_created],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[start_timeout],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[wait_for_resource_ready],unknown},optional,{c,number,any,integer}},{{c,atom,[worker_pool_size],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}}],none,none},unknown}]},{emqx_topic_metrics_proto_v1,metrics,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,binary,[8,0],unknown}]},{emqx_bridge_resource,stop,2} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_mgmt,do_call_client,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},any]},{emqx_exhook_proto_v1,server_info,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},any]},{emqx_retainer_mnesia,active_indices,0} => {{c,tuple,[any,any],{2,any}},[]},{emqx_prometheus_proto_v1,stop,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_proto_v1,get_alarms,2} => {any,[{c,atom,any,unknown},{c,atom,[activated,all,deactivated],unknown}]},{emqx_dashboard_proto_v1,do_sample,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,1,pos_inf},integer},none,none,none,none],unknown}]},{emqx_telemetry,get_cluster_uuid,0} => {any,[]},{emqx_bridge_proto_v2,list_bridges,1} => {any,[{c,atom,any,unknown}]},{emqx_node_rebalance_proto_v1,evict_connections,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,pos_inf},integer}]},{emqx_conf_proto_v1,update,4} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},any,{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_node_rebalance_api_proto_v1,node_rebalance_evacuation_stop,1} => {any,[{c,atom,any,unknown}]},{emqx_broker_proto_v1,list_client_subscriptions,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_broker_proto_v1,forward_async,3} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown},{c,tuple,[{c,atom,[delivery],unknown},{c,identifier,[pid],unknown},{c,tuple,[{c,atom,[message],unknown},{c,binary,[8,0],unknown},any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,map,{[],{c,atom,any,unknown},{c,atom,[false,true],unknown}},unknown},{c,map,{[{{c,atom,[allow_publish],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[peerhost],unknown},optional,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown}},{{c,atom,[properties],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[proto_ver],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}},{{c,atom,[protocol],unknown},optional,{c,atom,any,unknown}},{{c,atom,[username],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}}],{c,atom,any,unknown},any},unknown},{c,binary,[8,0],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,number,any,integer},any],{10,{c,atom,[message],unknown}}}],{3,{c,atom,[delivery],unknown}}}]},{emqx_cm_proto_v1,kickout_client,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_mgmt,do_subscribe,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[channel_not_found],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[subscribe],unknown},any],{2,{c,atom,[subscribe],unknown}}}]}],unknown},[any,any]},{emqx_conf_app,get_override_config_file,0} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,map,{[{{c,atom,[msg],unknown},mandatory,any},{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}}],none,none},unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},any],{2,{c,atom,[ok],unknown}}}]}],unknown},[]},{emqx_gateway_cm,do_cast,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any]},{emqx_management_proto_v1,subscribe,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,list,[{c,tuple,[{c,binary,[8,0],unknown},{c,map,{[{{c,atom,[nl],unknown},mandatory,{c,number,{int_set,[0,1]},integer}},{{c,atom,[qos],unknown},mandatory,{c,number,{int_set,[0,1,2]},integer}},{{c,atom,[rap],unknown},mandatory,{c,number,{int_set,[0,1]},integer}},{{c,atom,[rh],unknown},mandatory,{c,number,{int_set,[0,1,2]},integer}},{{c,atom,[share],unknown},optional,{c,binary,[8,0],unknown}}],{c,atom,any,unknown},any},unknown}],{2,any}},{c,nil,[],unknown}],unknown}]},{emqx_mgmt_api_plugins,delete_package,1} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_mgmt_api_plugins_proto_v1,install_package,2} => {any,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,binary,[8,0],unknown}]},{emqx_resource_proto_v1,recreate,4} => {any,[{c,binary,[8,0],unknown},{c,atom,any,unknown},any,{c,map,{[{{c,atom,[auto_restart_interval],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[auto_retry_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[batch_size],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[batch_time],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[health_check_interval],unknown},optional,{c,number,any,integer}},{{c,atom,[health_check_timeout],unknown},optional,{c,number,any,integer}},{{c,atom,[inflight_window],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[max_buffer_bytes],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[query_mode],unknown},optional,{c,atom,[async,dynamic,sync],unknown}},{{c,atom,[resume_interval],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[start_after_created],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[start_timeout],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[wait_for_resource_ready],unknown},optional,{c,number,any,integer}},{{c,atom,[worker_pool_size],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}}],none,none},unknown}]},{emqx_authz_cache,drain_cache,0} => {{c,atom,[ok],unknown},[]},{emqx_mgmt_api_plugins,ensure_action,2} => {{c,atom,[ok],unknown},[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,atom,[restart,start,stop],unknown}]},{emqx_broker_proto_v1,list_subscriptions_via_topic,2} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown}]},{emqx_trace,trace_file_detail,1} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,map,{[{{c,atom,[file],unknown},mandatory,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}},{{c,atom,[node],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[reason],unknown},mandatory,{c,atom,any,unknown}}],none,none},unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,map,{[{{c,atom,[mtime],unknown},mandatory,{c,union,[{c,atom,[undefined],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},{c,tuple,[{c,tuple,[any,any,any],{3,any}},{c,tuple,[any,any,any],{3,any}}],{2,any}},none,none,none],unknown}},{{c,atom,[node],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[size],unknown},mandatory,{c,union,[{c,atom,[undefined],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}}],none,none},unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_proto_v1,delete_all_deactivated_alarms,1} => {any,[{c,atom,any,unknown}]},{emqx_prometheus,do_start,0} => {{c,atom,[ok],unknown},[]},{emqx_gateway_cm,do_kick_session,4} => {{c,atom,[ok],unknown},[{c,atom,any,unknown},any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_ft_storage_fs_proto_v1,multilist,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,tuple,[{c,binary,[8,0],unknown},{c,binary,[8,0],unknown}],{2,any}},{c,atom,[fragment,result],unknown}]},{emqx_telemetry_proto_v1,get_cluster_uuid,1} => {any,[{c,atom,any,unknown}]},{emqx_node_rebalance,is_node_available,0} => {{c,atom,any,unknown},[]},{emqx_retainer_proto_v2,active_mnesia_indices,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_bridge_proto_v4,lookup_from_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_bridge_proto_v3,list_bridges,1} => {any,[{c,atom,any,unknown}]},{emqx_gateway_cm,do_get_chan_info,3} => {{c,union,[{c,atom,[undefined],unknown},none,none,none,none,none,none,none,none,{c,map,{[{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}}],any,any},unknown}],unknown},[any,any,any]},{emqx_node_rebalance_status_proto_v1,rebalance_status,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_node_rebalance_proto_v1,disable_rebalance_agent,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,identifier,[pid],unknown}]},{emqx_rule_engine,reset_metrics_for_rule,1} => {{c,atom,[ok],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_authz_proto_v1,lookup_from_all_nodes,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,atom,any,unknown}]},{emqx_proto_v1,clean_pem_cache,1} => {any,[{c,atom,any,unknown}]},{emqx_conf_proto_v1,get_config,2} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown}]},{emqx_management_proto_v1,get_full_config,1} => {any,[{c,atom,any,unknown}]},{emqx_topic_metrics,reset,1} => {any,[any]},{emqx_node_rebalance_proto_v1,available_nodes,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_proto_v2,clean_pem_cache,1} => {any,[{c,atom,any,unknown}]},{emqx_bridge_proto_v4,stop_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_conf_proto_v1,reset,3} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_broker,subscriptions,1} => {{c,list,[{c,tuple,[any,any],{2,any}},{c,nil,[],unknown}],unknown},[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,{c,identifier,[pid],unknown},none,none,none,none,none,none],unknown}]},{emqx_mgmt,do_list_subscriptions,0} => {none,[]},{emqx_eviction_agent_proto_v1,evict_session_channel,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,map,{[{{c,atom,[clean_start],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[clientid],unknown},optional,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[conn_mod],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[conn_props],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[connected],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[connected_at],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[disconnected_at],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[expiry_interval],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[keepalive],unknown},optional,{c,number,{int_rng,0,1114111},integer}},{{c,atom,[peercert],unknown},mandatory,{c,union,[{c,atom,[nossl,undefined],unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,none,none,{c,tuple,any,{any,any}},none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}},{{c,atom,[peername],unknown},mandatory,{c,tuple,[{c,union,[{c,atom,[local,undefined,unspec],unknown},none,none,none,none,none,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown},none,none,none],unknown},any],{2,any}}},{{c,atom,[proto_name],unknown},optional,{c,binary,[8,0],unknown}},{{c,atom,[proto_ver],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}},{{c,atom,[receive_maximum],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[sockname],unknown},mandatory,{c,tuple,[{c,union,[{c,atom,[local,undefined,unspec],unknown},none,none,none,none,none,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown},none,none,none],unknown},any],{2,any}}},{{c,atom,[socktype],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[username],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}}],{c,atom,any,unknown},any},unknown},{c,map,{[{{c,atom,[anonymous],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[auth_result],unknown},optional,{c,atom,[bad_authentication_method,bad_clientid_or_password,bad_username_or_password,banned,client_identifier_not_valid,not_authorized,server_busy,server_unavailable,success],unknown}},{{c,atom,[clientid],unknown},mandatory,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[cn],unknown},optional,{c,binary,[8,0],unknown}},{{c,atom,[dn],unknown},optional,{c,binary,[8,0],unknown}},{{c,atom,[is_bridge],unknown},mandatory,{c,atom,[false,true],unknown}},{{c,atom,[is_superuser],unknown},mandatory,{c,atom,[false,true],unknown}},{{c,atom,[mountpoint],unknown},mandatory,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[password],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[peerhost],unknown},mandatory,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown}},{{c,atom,[protocol],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[sockport],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[username],unknown},mandatory,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[ws_cookie],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}},{{c,atom,[zone],unknown},mandatory,{c,atom,any,unknown}}],{c,atom,any,unknown},any},unknown}]},{emqx_gateway_cm,do_set_chan_stats,4} => {{c,atom,[false,true],unknown},[any,any,any,any]},{emqx_retainer_dispatcher,wait_dispatch_complete,1} => {{c,atom,[ok],unknown},[any]},{emqx_conf_proto_v2,update,4} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},any,{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_bridge_proto_v3,stop_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_gateway_cm_proto_v1,get_chan_stats,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_ft_storage_exporter_fs_proxy,read_export_file_local,2} => {any,[any,any]},{emqx_conf_proto_v2,remove_config,2} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_stats,getstats,0} => {{c,list,[{c,tuple,any,{any,any}},{c,nil,[],unknown}],unknown},[]},{emqx_mgmt,node_info,0} => {{c,map,{[{{c,atom,[connections],unknown},mandatory,any},{{c,atom,[edition],unknown},mandatory,{c,binary,[0,80],unknown}},{{c,atom,[load1],unknown},optional,{c,number,any,float}},{{c,atom,[load15],unknown},optional,{c,number,any,float}},{{c,atom,[load5],unknown},optional,{c,number,any,float}},{{c,atom,[log_path],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[max_fds],unknown},mandatory,any},{{c,atom,[memory_total],unknown},mandatory,{c,number,any,unknown}},{{c,atom,[memory_used],unknown},mandatory,{c,number,any,integer}},{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[node_status],unknown},mandatory,{c,atom,[running],unknown}},{{c,atom,[otp_release],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[process_available],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[process_used],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[role],unknown},mandatory,{c,atom,[core,replicant],unknown}},{{c,atom,[sys_path],unknown},mandatory,{c,binary,[8,0],unknown}},{{c,atom,[uptime],unknown},mandatory,any},{{c,atom,[version],unknown},mandatory,{c,binary,[8,0],unknown}}],none,none},unknown},[]},{emqx_gateway_cm_proto_v1,kick_session,4} => {any,[{c,atom,any,unknown},{c,atom,[discard,kick],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_node_rebalance_api_proto_v1,node_rebalance_stop,1} => {any,[{c,atom,any,unknown}]},{emqx_cm_proto_v2,get_chan_info,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_authn_proto_v1,lookup_from_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,atom,any,unknown},{c,binary,[8,0],unknown}]},{emqx_cm,takeover_finish,2} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,atom,[noproc,timeout,unexpected_exception],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,union,[none,none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,{c,tuple,[{c,atom,[session],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,binary,[0,128],unknown},{c,atom,[false,true],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,atom,[false,true],unknown},{c,opaque,[{opaque,emqx_inflight,inflight,[],{c,tuple,[{c,atom,[inflight],unknown},{c,number,{int_rng,1,pos_inf},integer},{c,opaque,[{opaque,gb_trees,tree,[any,any],{c,tuple,[any,any],{2,any}}}],unknown}],{3,{c,atom,[inflight],unknown}}}}],unknown},{c,tuple,[{c,atom,[mqueue],unknown},{c,atom,[false,true],unknown},{c,number,{int_rng,0,pos_inf},integer},{c,number,{int_rng,0,pos_inf},integer},{c,number,{int_rng,0,pos_inf},integer},{c,union,[{c,atom,[disabled],unknown},none,none,none,none,none,none,none,none,{c,map,{[],any,any},unknown}],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,any,integer},none,none,none,none],unknown},{c,tuple_set,[{2,[{c,tuple,[any,any],{2,any}}]},{4,[{c,tuple,[any,any,any,any],{4,any}}]}],unknown},{c,tuple,[any,any,any],{3,any}},{c,union,[{c,atom,[undefined],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,union,[{c,atom,[undefined],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}],{11,{c,atom,[mqueue],unknown}}},{c,number,{int_rng,1,1114111},integer},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,map,{[],any,any},unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown},{c,number,{int_rng,1,pos_inf},integer}],{15,{c,atom,[session],unknown}}},none,none,none],unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},none,none,none],unknown},[{c,atom,any,unknown},{c,identifier,[pid],unknown}]},{emqx_gateway_cm_proto_v1,call,5} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any,{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}]},{emqx_mgmt,broker_info,0} => {{c,map,{[{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[node_status],unknown},mandatory,{c,atom,[running],unknown}},{{c,atom,[otp_release],unknown},mandatory,{c,binary,[8,0],unknown}}],any,any},unknown},[]},{emqx_resource,reset_metrics_local,1} => {{c,atom,[ok],unknown},[{c,binary,[8,0],unknown}]},{emqx_cm,do_kick_session,3} => {{c,atom,[ok],unknown},[any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_gateway_cm,do_get_chann_conn_mod,3} => {any,[any,any,any]},{emqx_conf_proto_v2,get_config,3} => {any,[{c,atom,any,unknown},{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown},any]},{emqx_resource_proto_v1,remove,1} => {any,[{c,binary,[8,0],unknown}]},{emqx_topic_metrics,reset,0} => {any,[]},{emqx_bridge_proto_v3,restart_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_slow_subs_api,get_history,0} => {{c,list,[{c,map,{[{{c,atom,[clientid],unknown},mandatory,any},{{c,atom,[last_update_time],unknown},mandatory,any},{{c,atom,[node],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[timespan],unknown},mandatory,any},{{c,atom,[topic],unknown},mandatory,any}],none,none},unknown},{c,nil,[],unknown}],unknown},[]},{emqx_node_rebalance_proto_v1,evict_sessions,4} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,pos_inf},integer},{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,atom,[connected,connecting,disconnected,idle,reauthenticating],unknown}]},{emqx,get_config,1} => {any,[{c,list,[any,{c,nil,[],unknown}],unknown}]},{emqx_bridge_proto_v1,stop_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_bridge_proto_v2,start_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_authz_api_sources,lookup_from_local_node,1} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},{c,tuple,[{c,atom,any,unknown},{c,union,[{c,atom,[not_found_resource],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}],{2,any}}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,tuple,[{c,atom,any,unknown},{c,atom,[connected,connecting,disconnected,stopped],unknown},{c,map,{[{{c,atom,[counters],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[gauges],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[slides],unknown},mandatory,{c,map,{[],any,any},unknown}}],none,none},unknown},{c,map,{[{{c,atom,[counters],unknown},optional,{c,map,{[],any,any},unknown}},{{c,atom,[gauges],unknown},optional,{c,map,{[],any,any},unknown}},{{c,atom,[rate],unknown},optional,{c,map,{[],any,any},unknown}},{{c,atom,[slides],unknown},optional,{c,map,{[],any,any},unknown}}],none,none},unknown}],{4,any}}],{2,{c,atom,[ok],unknown}}}]}],unknown},[any]},{emqx_gateway_cm_proto_v1,set_chan_info,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},{c,map,{[],{c,atom,any,unknown},any},unknown}]},{emqx_eviction_agent,evict_session_channel,3} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,union,[{c,atom,[undefined],unknown},none,none,{c,identifier,[pid],unknown},none,none,none,none,none,none],unknown}],{2,{c,atom,[ok],unknown}}}]},{3,[{c,tuple,[{c,atom,[ok],unknown},{c,union,[{c,atom,[undefined],unknown},none,none,{c,identifier,[pid],unknown},none,none,none,none,none,none],unknown},any],{3,{c,atom,[ok],unknown}}}]}],unknown},[any,{c,map,{[{{c,atom,[clean_start],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[clientid],unknown},optional,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[conn_mod],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[conn_props],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[connected],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[connected_at],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[disconnected_at],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[expiry_interval],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[keepalive],unknown},optional,{c,number,{int_rng,0,1114111},integer}},{{c,atom,[peercert],unknown},optional,{c,union,[{c,atom,[nossl,undefined],unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,none,none,{c,tuple,any,{any,any}},none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}},{{c,atom,[peername],unknown},mandatory,{c,tuple,[{c,union,[{c,atom,[local,undefined,unspec],unknown},none,none,none,none,none,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown},none,none,none],unknown},any],{2,any}}},{{c,atom,[proto_name],unknown},optional,{c,binary,[8,0],unknown}},{{c,atom,[proto_ver],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}},{{c,atom,[receive_maximum],unknown},optional,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[sockname],unknown},mandatory,{c,tuple,[{c,union,[{c,atom,[local,undefined,unspec],unknown},none,none,none,none,none,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown},none,none,none],unknown},any],{2,any}}},{{c,atom,[socktype],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[username],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}}],{c,atom,any,unknown},any},unknown},{c,map,{[{{c,atom,[anonymous],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[auth_result],unknown},optional,{c,atom,[bad_authentication_method,bad_clientid_or_password,bad_username_or_password,banned,client_identifier_not_valid,not_authorized,server_busy,server_unavailable,success],unknown}},{{c,atom,[clientid],unknown},mandatory,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[cn],unknown},optional,{c,binary,[8,0],unknown}},{{c,atom,[dn],unknown},optional,{c,binary,[8,0],unknown}},{{c,atom,[is_bridge],unknown},mandatory,{c,atom,[false,true],unknown}},{{c,atom,[is_superuser],unknown},mandatory,{c,atom,[false,true],unknown}},{{c,atom,[mountpoint],unknown},mandatory,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[password],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[peerhost],unknown},mandatory,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown}},{{c,atom,[protocol],unknown},mandatory,{c,atom,any,unknown}},{{c,atom,[sockport],unknown},mandatory,{c,number,{int_rng,0,pos_inf},integer}},{{c,atom,[username],unknown},mandatory,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}},{{c,atom,[ws_cookie],unknown},optional,{c,union,[{c,atom,[undefined],unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}},{{c,atom,[zone],unknown},mandatory,{c,atom,any,unknown}}],{c,atom,any,unknown},any},unknown}]},{emqx_bridge_proto_v1,stop_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_node_rebalance_proto_v1,enable_rebalance_agent,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,identifier,[pid],unknown}]},{emqx_node_rebalance_proto_v1,connection_counts,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_mgmt_api_plugins,get_plugins,0} => {{c,tuple,[{c,atom,any,unknown},{c,list,[{c,map,{[],any,any},unknown},{c,nil,[],unknown}],unknown}],{2,any}},[]},{emqx_proto_v2,clean_authz_cache,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_bridge_proto_v3,lookup_from_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_mgmt_api_trace,read_trace_file,3} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[eof],unknown},{c,union,[{c,atom,[undefined],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}],{2,{c,atom,[eof],unknown}}},{c,tuple,[{c,atom,[error],unknown},{c,union,[{c,atom,any,unknown},none,none,none,none,none,{c,tuple,[{c,atom,[no_translation],unknown},{c,atom,[unicode],unknown},{c,atom,[latin1],unknown}],{3,{c,atom,[no_translation],unknown}}},none,none,none],unknown}],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,1114111},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,binary,[8,0],unknown},any,any]},{emqx_gateway_cm,do_set_chan_info,4} => {{c,atom,[false,true],unknown},[any,any,any,any]},{emqx_resource_proto_v1,create_dry_run,2} => {any,[{c,atom,any,unknown},any]},{emqx_broker_proto_v1,forward,3} => {any,[{c,atom,any,unknown},{c,binary,[8,0],unknown},{c,tuple,[{c,atom,[delivery],unknown},{c,identifier,[pid],unknown},{c,tuple,[{c,atom,[message],unknown},{c,binary,[8,0],unknown},any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,map,{[],{c,atom,any,unknown},{c,atom,[false,true],unknown}},unknown},{c,map,{[{{c,atom,[allow_publish],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[peerhost],unknown},optional,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown}},{{c,atom,[properties],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[proto_ver],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}},{{c,atom,[protocol],unknown},optional,{c,atom,any,unknown}},{{c,atom,[username],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}}],{c,atom,any,unknown},any},unknown},{c,binary,[8,0],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,number,any,integer},any],{10,{c,atom,[message],unknown}}}],{3,{c,atom,[delivery],unknown}}}]},{emqx_node_rebalance_evacuation,stop,0} => {any,[]},{emqx_bridge_proto_v1,restart_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_management_proto_v2,unsubscribe,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,binary,[8,0],unknown}]},{emqx_gateway_api_listeners,do_listeners_cluster_status,1} => {{c,map,{[],any,any},unknown},[{c,list,[any,{c,nil,[],unknown}],unknown}]},{emqx_proto_v2,are_running,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{emqx_retainer_proto_v2,wait_dispatch_complete,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}]},{emqx_bridge_proto_v3,restart_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_node_rebalance_api_proto_v1,node_rebalance_start,2} => {any,[{c,atom,any,unknown},{c,map,{[{{c,atom,[abs_conn_threshold],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[abs_sess_threshold],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[conn_evict_rate],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[nodes],unknown},optional,{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}},{{c,atom,[rel_conn_threshold],unknown},optional,{c,number,any,unknown}},{{c,atom,[rel_sess_threshold],unknown},optional,{c,number,any,unknown}},{{c,atom,[sess_evict_rate],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[wait_health_check],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}},{{c,atom,[wait_takeover],unknown},optional,{c,number,{int_rng,1,pos_inf},integer}}],none,none},unknown}]},{emqx,get_config,2} => {any,[any,any]},{emqx_mgmt_trace_proto_v2,trace_file_detail,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[{c,atom,any,unknown},none,none,none,{c,list,[any,{c,nil,[],unknown}],unknown},{c,number,{int_rng,0,1114111},integer},none,none,none,none],unknown},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_bridge_proto_v2,stop_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_shared_sub_proto_v1,dispatch_with_ack,5} => {any,[{c,identifier,[pid],unknown},any,{c,binary,[8,0],unknown},{c,tuple,[{c,atom,[message],unknown},{c,binary,[8,0],unknown},any,{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,map,{[],{c,atom,any,unknown},{c,atom,[false,true],unknown}},unknown},{c,map,{[{{c,atom,[allow_publish],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[peerhost],unknown},optional,{c,tuple_set,[{4,[{c,tuple,[{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer},{c,number,{int_rng,0,255},integer}],{4,any}}]},{8,[{c,tuple,[{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer},{c,number,{int_rng,0,1114111},integer}],{8,any}}]}],unknown}},{{c,atom,[properties],unknown},optional,{c,map,{[],{c,atom,any,unknown},any},unknown}},{{c,atom,[proto_ver],unknown},optional,{c,union,[none,{c,binary,[8,0],unknown},none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}},{{c,atom,[protocol],unknown},optional,{c,atom,any,unknown}},{{c,atom,[username],unknown},optional,{c,union,[{c,atom,[undefined],unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}}],{c,atom,any,unknown},any},unknown},{c,binary,[8,0],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,list,[any,{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},{c,number,{int_rng,0,255},integer},none,none,none,none],unknown},{c,union,[none,{c,binary,[8,0],unknown},none,none,{c,nil,[],unknown},none,none,none,none,none],unknown}],unknown},none,none,none,none,none],unknown},{c,number,any,integer},any],{10,{c,atom,[message],unknown}}},{c,union,[{c,atom,[infinity],unknown},none,none,none,none,{c,number,{int_rng,0,pos_inf},integer},none,none,none,none],unknown}]},{emqx_gateway_http_proto_v1,get_cluster_status,2} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,atom,any,unknown}]},{emqx_management_proto_v3,list_subscriptions,1} => {any,[{c,atom,any,unknown}]},{emqx_cm_proto_v1,get_chan_stats,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_gateway_cm_proto_v1,takeover_session,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_conf_proto_v2,reset,2} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],nonempty},{c,map,{[{{c,atom,[override_to],unknown},optional,{c,atom,[cluster,local],unknown}},{{c,atom,[persistent],unknown},optional,{c,atom,[false,true],unknown}},{{c,atom,[rawconf_with_defaults],unknown},optional,{c,atom,[false,true],unknown}}],none,none},unknown}]},{emqx_node_rebalance_proto_v1,disconnected_session_counts,1} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown}]},{erlang,send,2} => {any,[any,any]},{emqx_cm_proto_v1,get_chan_info,2} => {any,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown}]},{emqx_gateway_cm_proto_v1,call,4} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any]},{emqx_eviction_agent,evict_connections,1} => {{c,union,[{c,atom,[ok],unknown},none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},{c,atom,[disabled],unknown}],{2,{c,atom,[error],unknown}}},none,none,none],unknown},[any]},{emqx_dashboard_monitor,do_sample,2} => {any,[{c,atom,any,unknown},any]},{emqx_session_router,resume_end,2} => {{c,tuple_set,[{2,[{c,tuple,[{c,atom,[error],unknown},any],{2,{c,atom,[error],unknown}}},{c,tuple,[{c,atom,[ok],unknown},{c,list,[{c,tuple,any,{any,any}},{c,nil,[],unknown}],unknown}],{2,{c,atom,[ok],unknown}}}]}],unknown},[{c,identifier,[pid],unknown},{c,binary,[8,0],unknown}]},{emqx_node_rebalance,disconnected_session_count,0} => {{c,tuple,[{c,atom,[ok],unknown},any],{2,{c,atom,[ok],unknown}}},[]},{emqx_proto_v2,delete_all_deactivated_alarms,1} => {any,[{c,atom,any,unknown}]},{emqx_cm,do_get_chann_conn_mod,2} => {any,[any,any]},{emqx_exhook_mgr,server_hooks_metrics,1} => {any,[any]},{emqx_node_rebalance_status_proto_v1,local_status,1} => {any,[{c,atom,any,unknown}]},{emqx_alarm,delete_all_deactivated_alarms,0} => {any,[]},{emqx_proto_v1,deactivate_alarm,2} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_bridge_proto_v1,restart_bridge_to_node,3} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_mgmt_cluster_proto_v1,invite_node,2} => {any,[{c,atom,any,unknown},{c,atom,any,unknown}]},{emqx_topic_metrics,metrics,1} => {{c,union,[none,none,none,none,none,none,{c,tuple,[{c,atom,[error],unknown},{c,atom,[topic_not_found],unknown}],{2,{c,atom,[error],unknown}}},none,none,{c,map,{[{{c,atom,[create_time],unknown},mandatory,any},{{c,atom,[metrics],unknown},mandatory,{c,map,{[],any,any},unknown}},{{c,atom,[reset_time],unknown},optional,any},{{c,atom,[topic],unknown},mandatory,any}],none,none},unknown}],unknown},[any]},{emqx_bridge_proto_v4,get_metrics_from_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_proto_v2,get_alarms,2} => {any,[{c,atom,any,unknown},{c,atom,[activated,all,deactivated],unknown}]},{emqx_bridge_proto_v2,lookup_from_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_gateway_cm,do_call,5} => {any,[{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown},{c,identifier,[pid],unknown},any,any]},{emqx_management_proto_v1,list_listeners,1} => {any,[{c,atom,any,unknown}]},{emqx_bridge_proto_v2,restart_bridges_to_all_nodes,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown}]},{emqx_ft_storage_fs_proxy,list_local,2} => {any,[any,any]},{emqx_gateway_cm_proto_v1,lookup_by_clientid,3} => {any,[{c,list,[{c,atom,any,unknown},{c,nil,[],unknown}],unknown},{c,atom,any,unknown},{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,none,none,none,none,none,none],unknown}]},{emqx_conf_proto_v1,get_all,1} => {any,[{c,list,[{c,union,[{c,atom,any,unknown},{c,binary,[8,0],unknown},none,none,{c,list,[{c,number,{int_rng,0,255},integer},{c,nil,[],unknown}],unknown},none,none,none,none,none],unknown},{c,nil,[],unknown}],unknown}]}}}. From 367de2a2eb50185527d3618608f62032977b0975 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Tue, 30 May 2023 08:29:43 +0200 Subject: [PATCH 39/72] docs: update bpapi readme --- apps/emqx/src/bpapi/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/emqx/src/bpapi/README.md b/apps/emqx/src/bpapi/README.md index a26d79d28..f2177a88b 100644 --- a/apps/emqx/src/bpapi/README.md +++ b/apps/emqx/src/bpapi/README.md @@ -149,6 +149,7 @@ After releasing, let's say, 5.1.0, the following actions should be performed to 1. Checkout 5.1.0 tag 1. Build the code +1. Replace api version string `"master"` in `apps/emqx/test/emqx_static_checks_data/master.bpapi` with `"5.1"` 1. Rename `apps/emqx/test/emqx_static_checks_data/master.bpapi` to `apps/emqx/test/emqx_static_checks_data/5.1.bpapi` 1. Add `apps/emqx/test/emqx_static_checks_data/5.1.bpapi` to the repo 1. Delete the previous file (e.g. `5.0.bpapi`), unless there is plan to support rolling upgrade from 5.0 to 5.2 From 65506eb15e1335ea7bc0a914c1c67bd8d4f94ba6 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Tue, 30 May 2023 09:32:42 +0200 Subject: [PATCH 40/72] build: remove changelog base compare all changes in the future will have a compare base --- scripts/rel/cut.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/rel/cut.sh b/scripts/rel/cut.sh index cf402f7f5..613fd05f0 100755 --- a/scripts/rel/cut.sh +++ b/scripts/rel/cut.sh @@ -270,10 +270,6 @@ else *beta*) true ;; - e5.1.0*) - # the first release has no change log - true - ;; *) generate_changelog ;; From 74582ad36e80359013330a40aede2c16f0ddbc37 Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Tue, 30 May 2023 09:59:57 +0200 Subject: [PATCH 41/72] test(conf-test): use major version number as prefix to find old config --- scripts/conf-test/run.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/conf-test/run.sh b/scripts/conf-test/run.sh index 4c2f18615..f01c5ca80 100755 --- a/scripts/conf-test/run.sh +++ b/scripts/conf-test/run.sh @@ -13,7 +13,7 @@ start_emqx_with_conf() { "$EMQX_ROOT"/bin/emqx stop } -MINOR_VSN=$(./pkg-vsn.sh "$PROFILE" | cut -d. -f1,2) +MAJOR_VSN=$(./pkg-vsn.sh "$PROFILE" | cut -d. -f1) if [ "$PROFILE" = "emqx" ]; then PREFIX="v" @@ -21,7 +21,7 @@ else PREFIX="e" fi -FILES=$(ls ./scripts/conf-test/old-confs/$PREFIX"$MINOR_VSN"*) +FILES=$(ls ./scripts/conf-test/old-confs/"${PREFIX}${MAJOR_VSN}"*) cp "$EMQX_ROOT"/etc/emqx.conf "$EMQX_ROOT"/etc/emqx.conf.bak cleanup() { From d2ce7c22fd09ce42ab2788c6b8b99a1dfd2dc31f Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Tue, 30 May 2023 11:32:18 +0200 Subject: [PATCH 42/72] chore: bump app vsn, core app 'emqx' is now at 5.1 --- apps/emqx/src/emqx.app.src | 3 +-- apps/emqx_ft/src/emqx_ft.app.src | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/emqx/src/emqx.app.src b/apps/emqx/src/emqx.app.src index 2a2b3c323..e156d8cf0 100644 --- a/apps/emqx/src/emqx.app.src +++ b/apps/emqx/src/emqx.app.src @@ -2,8 +2,7 @@ {application, emqx, [ {id, "emqx"}, {description, "EMQX Core"}, - % strict semver, bump manually! - {vsn, "5.0.27"}, + {vsn, "5.1.0"}, {modules, []}, {registered, []}, {applications, [ diff --git a/apps/emqx_ft/src/emqx_ft.app.src b/apps/emqx_ft/src/emqx_ft.app.src index c503ab8e0..74d1b84b7 100644 --- a/apps/emqx_ft/src/emqx_ft.app.src +++ b/apps/emqx_ft/src/emqx_ft.app.src @@ -1,6 +1,6 @@ {application, emqx_ft, [ {description, "EMQX file transfer over MQTT"}, - {vsn, "0.1.1"}, + {vsn, "0.1.2"}, {registered, []}, {mod, {emqx_ft_app, []}}, {applications, [ From 47e78ec5a0241fa51d9464fc7ed1b2a6bfeba7cb Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Tue, 30 May 2023 13:39:40 +0200 Subject: [PATCH 43/72] chore: bump helm chart versions to 5.1.0 --- deploy/charts/emqx-enterprise/Chart.yaml | 4 ++-- deploy/charts/emqx/Chart.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/charts/emqx-enterprise/Chart.yaml b/deploy/charts/emqx-enterprise/Chart.yaml index 7bc90eff7..821c19e8c 100644 --- a/deploy/charts/emqx-enterprise/Chart.yaml +++ b/deploy/charts/emqx-enterprise/Chart.yaml @@ -14,8 +14,8 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 5.0.4 +version: 5.1.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. -appVersion: 5.0.4 +appVersion: 5.1.0 diff --git a/deploy/charts/emqx/Chart.yaml b/deploy/charts/emqx/Chart.yaml index 11bc95951..045001b79 100644 --- a/deploy/charts/emqx/Chart.yaml +++ b/deploy/charts/emqx/Chart.yaml @@ -14,8 +14,8 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 5.0.26 +version: 5.1.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. -appVersion: 5.0.26 +appVersion: 5.1.0 From 4da0d83faf3e1759d74024427c21fd859a5c4760 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Thu, 18 May 2023 20:44:20 +0300 Subject: [PATCH 44/72] chore(mqttconn): remove dead code --- .../src/emqx_connector_mqtt.erl | 52 +------------------ .../src/mqtt/emqx_connector_mqtt_msg.erl | 24 +-------- 2 files changed, 2 insertions(+), 74 deletions(-) diff --git a/apps/emqx_connector/src/emqx_connector_mqtt.erl b/apps/emqx_connector/src/emqx_connector_mqtt.erl index bb8cc00d1..1efe6f3e0 100644 --- a/apps/emqx_connector/src/emqx_connector_mqtt.erl +++ b/apps/emqx_connector/src/emqx_connector_mqtt.erl @@ -15,10 +15,6 @@ %%-------------------------------------------------------------------- -module(emqx_connector_mqtt). --include("emqx_connector.hrl"). - --include_lib("typerefl/include/types.hrl"). --include_lib("hocon/include/hoconsc.hrl"). -include_lib("emqx/include/logger.hrl"). -behaviour(supervisor). @@ -47,52 +43,6 @@ -export([on_async_result/2]). --behaviour(hocon_schema). - --import(hoconsc, [mk/2]). - --export([ - roots/0, - fields/1 -]). - -%%===================================================================== -%% Hocon schema -roots() -> - fields("config"). - -fields("config") -> - emqx_connector_mqtt_schema:fields("config"); -fields("get") -> - [ - {num_of_bridges, - mk( - integer(), - #{desc => ?DESC("num_of_bridges")} - )} - ] ++ fields("post"); -fields("put") -> - emqx_connector_mqtt_schema:fields("server_configs"); -fields("post") -> - [ - {type, - mk( - mqtt, - #{ - required => true, - desc => ?DESC("type") - } - )}, - {name, - mk( - binary(), - #{ - required => true, - desc => ?DESC("name") - } - )} - ] ++ fields("put"). - %% =================================================================== %% supervisor APIs start_link() -> @@ -313,7 +263,7 @@ maybe_put_fields(Fields, Conf, Acc0) -> ms_to_s(Ms) -> erlang:ceil(Ms / 1000). -clientid(Id, _Conf = #{clientid_prefix := Prefix = <<_/binary>>}) -> +clientid(Id, _Conf = #{clientid_prefix := Prefix}) when is_binary(Prefix) -> iolist_to_binary([Prefix, ":", Id, ":", atom_to_list(node())]); clientid(Id, _Conf) -> iolist_to_binary([Id, ":", atom_to_list(node())]). diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl index df1114483..8fc70405f 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl @@ -17,12 +17,9 @@ -module(emqx_connector_mqtt_msg). -export([ - to_binary/1, - from_binary/1, make_pub_vars/2, to_remote_msg/2, - to_broker_msg/3, - estimate_size/1 + to_broker_msg/3 ]). -export([ @@ -143,25 +140,6 @@ replace_simple_var(Tokens, Data) when is_list(Tokens) -> replace_simple_var(Val, _Data) -> Val. -%% @doc Make `binary()' in order to make iodata to be persisted on disk. --spec to_binary(msg()) -> binary(). -to_binary(Msg) -> term_to_binary(Msg). - -%% @doc Unmarshal binary into `msg()'. --spec from_binary(binary()) -> msg(). -from_binary(Bin) -> binary_to_term(Bin). - -%% @doc Estimate the size of a message. -%% Count only the topic length + payload size -%% There is no topic and payload for event message. So count all `Msg` term --spec estimate_size(msg()) -> integer(). -estimate_size(#message{topic = Topic, payload = Payload}) -> - size(Topic) + size(Payload); -estimate_size(#{topic := Topic, payload := Payload}) -> - size(Topic) + size(Payload); -estimate_size(Term) -> - erlang:external_size(Term). - set_headers(Val, Msg) -> emqx_message:set_headers(Val, Msg). topic(undefined, Topic) -> Topic; From bd956d00b6aa5fd21140473f01f2a4f188377811 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Thu, 18 May 2023 20:53:42 +0300 Subject: [PATCH 45/72] feat(mqttconn): stop using gproc in hot path Also drop fiddling with `mountpoint` since this option seems not to be used anywhere. --- .../test/emqx_bridge_mqtt_SUITE.erl | 4 +- .../src/emqx_connector_mqtt.erl | 124 +++----- .../src/mqtt/emqx_connector_mqtt_msg.erl | 22 +- .../src/mqtt/emqx_connector_mqtt_worker.erl | 268 +++++++++--------- 4 files changed, 186 insertions(+), 232 deletions(-) diff --git a/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl b/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl index f0de07da2..c00eb6b14 100644 --- a/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl +++ b/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl @@ -256,11 +256,11 @@ t_mqtt_egress_bridge_ignores_clean_start(_) -> } ), - {ok, _, #{state := #{name := WorkerName}}} = + {ok, _, #{state := #{worker := WorkerPid}}} = emqx_resource:get_instance(emqx_bridge_resource:resource_id(BridgeID)), ?assertMatch( #{clean_start := true}, - maps:from_list(emqx_connector_mqtt_worker:info(WorkerName)) + maps:from_list(emqx_connector_mqtt_worker:info(WorkerPid)) ), %% delete the bridge diff --git a/apps/emqx_connector/src/emqx_connector_mqtt.erl b/apps/emqx_connector/src/emqx_connector_mqtt.erl index 1efe6f3e0..bd4bf6eb1 100644 --- a/apps/emqx_connector/src/emqx_connector_mqtt.erl +++ b/apps/emqx_connector/src/emqx_connector_mqtt.erl @@ -25,8 +25,8 @@ callback_mode/0, start_link/0, init/1, - create_bridge/1, - drop_bridge/1, + create_bridge/2, + remove_bridge/1, bridges/0 ]). @@ -56,11 +56,10 @@ init([]) -> }, {ok, {SupFlag, []}}. -bridge_spec(Config) -> - {Name, NConfig} = maps:take(name, Config), +bridge_spec(Name, Options) -> #{ id => Name, - start => {emqx_connector_mqtt_worker, start_link, [Name, NConfig]}, + start => {emqx_connector_mqtt_worker, start_link, [Name, Options]}, restart => temporary, shutdown => 1000 }. @@ -72,10 +71,10 @@ bridges() -> || {Name, _Pid, _, _} <- supervisor:which_children(?MODULE) ]. -create_bridge(Config) -> - supervisor:start_child(?MODULE, bridge_spec(Config)). +create_bridge(Name, Options) -> + supervisor:start_child(?MODULE, bridge_spec(Name, Options)). -drop_bridge(Name) -> +remove_bridge(Name) -> case supervisor:terminate_child(?MODULE, Name) of ok -> supervisor:delete_child(?MODULE, Name); @@ -95,36 +94,39 @@ on_message_received(Msg, HookPoint, ResId) -> %% =================================================================== callback_mode() -> async_if_possible. -on_start(InstanceId, Conf) -> +on_start(ResourceId, Conf) -> ?SLOG(info, #{ msg => "starting_mqtt_connector", - connector => InstanceId, + connector => ResourceId, config => emqx_utils:redact(Conf) }), BasicConf = basic_config(Conf), - BridgeConf = BasicConf#{ - name => InstanceId, - clientid => clientid(InstanceId, Conf), - subscriptions => make_sub_confs(maps:get(ingress, Conf, undefined), Conf, InstanceId), - forwards => make_forward_confs(maps:get(egress, Conf, undefined)) + BridgeOpts = BasicConf#{ + clientid => clientid(ResourceId, Conf), + subscriptions => make_sub_confs(maps:get(ingress, Conf, #{}), Conf, ResourceId), + forwards => maps:get(egress, Conf, #{}) }, - case ?MODULE:create_bridge(BridgeConf) of - {ok, _Pid} -> - ensure_mqtt_worker_started(InstanceId, BridgeConf); + case create_bridge(ResourceId, BridgeOpts) of + {ok, Pid, {ConnProps, WorkerConf}} -> + {ok, #{ + name => ResourceId, + worker => Pid, + config => WorkerConf, + props => ConnProps + }}; {error, {already_started, _Pid}} -> - ok = ?MODULE:drop_bridge(InstanceId), - {ok, _} = ?MODULE:create_bridge(BridgeConf), - ensure_mqtt_worker_started(InstanceId, BridgeConf); + ok = remove_bridge(ResourceId), + on_start(ResourceId, Conf); {error, Reason} -> {error, Reason} end. -on_stop(_InstId, #{name := InstanceId}) -> +on_stop(ResourceId, #{}) -> ?SLOG(info, #{ msg => "stopping_mqtt_connector", - connector => InstanceId + connector => ResourceId }), - case ?MODULE:drop_bridge(InstanceId) of + case remove_bridge(ResourceId) of ok -> ok; {error, not_found} -> @@ -132,24 +134,24 @@ on_stop(_InstId, #{name := InstanceId}) -> {error, Reason} -> ?SLOG(error, #{ msg => "stop_mqtt_connector_error", - connector => InstanceId, + connector => ResourceId, reason => Reason }) end. -on_query(_InstId, {send_message, Msg}, #{name := InstanceId}) -> - ?TRACE("QUERY", "send_msg_to_remote_node", #{message => Msg, connector => InstanceId}), - case emqx_connector_mqtt_worker:send_to_remote(InstanceId, Msg) of +on_query(ResourceId, {send_message, Msg}, #{worker := Pid, config := Config}) -> + ?TRACE("QUERY", "send_msg_to_remote_node", #{message => Msg, connector => ResourceId}), + case emqx_connector_mqtt_worker:send_to_remote(Pid, Msg, Config) of ok -> ok; {error, Reason} -> classify_error(Reason) end. -on_query_async(_InstId, {send_message, Msg}, CallbackIn, #{name := InstanceId}) -> - ?TRACE("QUERY", "async_send_msg_to_remote_node", #{message => Msg, connector => InstanceId}), +on_query_async(ResourceId, {send_message, Msg}, CallbackIn, #{worker := Pid, config := Config}) -> + ?TRACE("QUERY", "async_send_msg_to_remote_node", #{message => Msg, connector => ResourceId}), Callback = {fun on_async_result/2, [CallbackIn]}, - case emqx_connector_mqtt_worker:send_to_remote_async(InstanceId, Msg, Callback) of + case emqx_connector_mqtt_worker:send_to_remote_async(Pid, Msg, Callback, Config) of ok -> ok; {ok, Pid} -> @@ -172,8 +174,8 @@ apply_callback_function({F, A}, Result) when is_function(F), is_list(A) -> apply_callback_function({M, F, A}, Result) when is_atom(M), is_atom(F), is_list(A) -> erlang:apply(M, F, A ++ [Result]). -on_get_status(_InstId, #{name := InstanceId}) -> - emqx_connector_mqtt_worker:status(InstanceId). +on_get_status(_ResourceId, #{worker := Pid}) -> + emqx_connector_mqtt_worker:status(Pid). classify_error(disconnected = Reason) -> {error, {recoverable_error, Reason}}; @@ -186,33 +188,13 @@ classify_error(shutdown = Reason) -> classify_error(Reason) -> {error, {unrecoverable_error, Reason}}. -ensure_mqtt_worker_started(InstanceId, BridgeConf) -> - case emqx_connector_mqtt_worker:connect(InstanceId) of - {ok, Properties} -> - {ok, #{name => InstanceId, config => BridgeConf, props => Properties}}; - {error, Reason} -> - {error, Reason} - end. - -make_sub_confs(EmptyMap, _Conf, _) when map_size(EmptyMap) == 0 -> - undefined; -make_sub_confs(undefined, _Conf, _) -> - undefined; -make_sub_confs(SubRemoteConf, Conf, ResourceId) -> - case maps:find(hookpoint, Conf) of - error -> - error({no_hookpoint_provided, Conf}); - {ok, HookPoint} -> - MFA = {?MODULE, on_message_received, [HookPoint, ResourceId]}, - SubRemoteConf#{on_message_received => MFA} - end. - -make_forward_confs(EmptyMap) when map_size(EmptyMap) == 0 -> - undefined; -make_forward_confs(undefined) -> - undefined; -make_forward_confs(FrowardConf) -> - FrowardConf. +make_sub_confs(Subscriptions, _Conf, _) when map_size(Subscriptions) == 0 -> + Subscriptions; +make_sub_confs(Subscriptions, #{hookpoint := HookPoint}, ResourceId) -> + MFA = {?MODULE, on_message_received, [HookPoint, ResourceId]}, + Subscriptions#{on_message_received => MFA}; +make_sub_confs(_SubRemoteConf, Conf, ResourceId) -> + error({no_hookpoint_provided, ResourceId, Conf}). basic_config( #{ @@ -227,17 +209,14 @@ basic_config( } = Conf ) -> BasicConf = #{ - %% connection opts server => Server, %% 30s connect_timeout => 30, - auto_reconnect => true, proto_ver => ProtoVer, - %% Opening bridge_mode will form a non-standard mqtt connection message. + %% Opening a connection in bridge mode will form a non-standard mqtt connection message. %% A load balancing server (such as haproxy) is often set up before the emqx broker server. %% When the load balancing server enables mqtt connection packet inspection, - %% non-standard mqtt connection packets will be filtered out by LB. - %% So let's disable bridge_mode. + %% non-standard mqtt connection packets might be filtered out by LB. bridge_mode => BridgeMode, keepalive => ms_to_s(KeepAlive), clean_start => CleanStart, @@ -246,18 +225,9 @@ basic_config( ssl => EnableSsl, ssl_opts => maps:to_list(maps:remove(enable, Ssl)) }, - maybe_put_fields([username, password], Conf, BasicConf). - -maybe_put_fields(Fields, Conf, Acc0) -> - lists:foldl( - fun(Key, Acc) -> - case maps:find(Key, Conf) of - error -> Acc; - {ok, Val} -> Acc#{Key => Val} - end - end, - Acc0, - Fields + maps:merge( + BasicConf, + maps:with([username, password], Conf) ). ms_to_s(Ms) -> diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl index 8fc70405f..004819678 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl @@ -17,7 +17,6 @@ -module(emqx_connector_mqtt_msg). -export([ - make_pub_vars/2, to_remote_msg/2, to_broker_msg/3 ]). @@ -46,11 +45,6 @@ remote := remote_config() }. -make_pub_vars(_, undefined) -> - undefined; -make_pub_vars(Mountpoint, Conf) when is_map(Conf) -> - Conf#{mountpoint => Mountpoint}. - %% @doc Make export format: %% 1. Mount topic to a prefix %% 2. Fix QoS to 1 @@ -70,8 +64,7 @@ to_remote_msg(MapMsg, #{ topic := TopicToken, qos := QoSToken, retain := RetainToken - } = Remote, - mountpoint := Mountpoint + } = Remote }) when is_map(MapMsg) -> Topic = replace_vars_in_str(TopicToken, MapMsg), Payload = process_payload(Remote, MapMsg), @@ -81,12 +74,10 @@ to_remote_msg(MapMsg, #{ #mqtt_msg{ qos = QoS, retain = Retain, - topic = topic(Mountpoint, Topic), + topic = Topic, props = emqx_utils:pub_props_to_packet(PubProps), payload = Payload - }; -to_remote_msg(#message{topic = Topic} = Msg, #{mountpoint := Mountpoint}) -> - Msg#message{topic = topic(Mountpoint, Topic)}. + }. %% published from remote node over a MQTT connection to_broker_msg(Msg, Vars, undefined) -> @@ -98,8 +89,7 @@ to_broker_msg( topic := TopicToken, qos := QoSToken, retain := RetainToken - } = Local, - mountpoint := Mountpoint + } = Local }, Props ) -> @@ -112,7 +102,7 @@ to_broker_msg( Props#{properties => emqx_utils:pub_props_to_packet(PubProps)}, emqx_message:set_flags( #{dup => Dup, retain => Retain}, - emqx_message:make(bridge, QoS, topic(Mountpoint, Topic), Payload) + emqx_message:make(bridge, QoS, Topic, Payload) ) ). @@ -142,5 +132,3 @@ replace_simple_var(Val, _Data) -> set_headers(Val, Msg) -> emqx_message:set_headers(Val, Msg). -topic(undefined, Topic) -> Topic; -topic(Prefix, Topic) -> emqx_topic:prepend(Prefix, Topic). diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl index e49603e51..c49d5b180 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl @@ -72,34 +72,75 @@ %% management APIs -export([ - connect/1, status/1, ping/1, info/1, - send_to_remote/2, - send_to_remote_async/3 + send_to_remote/3, + send_to_remote_async/4 ]). -export([handle_publish/3]). -export([handle_disconnect/1]). --export_type([ - config/0, - ack_ref/0 -]). +-export_type([config/0]). + +-type template() :: emqx_plugin_libs_rule:tmpl_token(). -type name() :: term(). -% -type qos() :: emqx_types:qos(). --type config() :: map(). --type ack_ref() :: term(). -% -type topic() :: emqx_types:topic(). +-type options() :: #{ + % endpoint + server := iodata(), + % emqtt client options + proto_ver := v3 | v4 | v5, + username := binary(), + password := binary(), + clientid := binary(), + clean_start := boolean(), + max_inflight := pos_integer(), + connect_timeout := pos_integer(), + retry_interval := timeout(), + bridge_mode := boolean(), + ssl := boolean(), + ssl_opts := proplists:proplist(), + % bridge options + subscriptions := map(), + forwards := map() +}. + +-type config() :: #{ + subscriptions := subscriptions() | undefined, + forwards := forwards() | undefined +}. + +-type subscriptions() :: #{ + remote := #{ + topic := emqx_topic:topic(), + qos => emqx_types:qos() + }, + local := #{ + topic => template(), + qos => template() | emqx_types:qos(), + retain => template() | boolean(), + payload => template() | undefined + }, + on_message_received := {module(), atom(), [term()]} +}. + +-type forwards() :: #{ + local => #{ + topic => emqx_topic:topic() + }, + remote := #{ + topic := template(), + qos => template() | emqx_types:qos(), + retain => template() | boolean(), + payload => template() | undefined + } +}. -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). --define(REF(Name), {via, gproc, ?NAME(Name)}). --define(NAME(Name), {n, l, Name}). - %% @doc Start a bridge worker. Supported configs: %% mountpoint: The topic mount point for messages sent to remote node/cluster %% `undefined', `<<>>' or `""' to disable @@ -107,20 +148,19 @@ %% %% Find more connection specific configs in the callback modules %% of emqx_bridge_connect behaviour. --spec start_link(name(), map()) -> - {ok, pid()} | {error, _Reason}. +-spec start_link(name(), options()) -> + {ok, pid(), {emqtt:properties(), config()}} | {error, _Reason}. start_link(Name, BridgeOpts) -> ?SLOG(debug, #{ msg => "client_starting", name => Name, options => BridgeOpts }), - Conf = init_config(Name, BridgeOpts), - Options = mk_client_options(Conf, BridgeOpts), + Config = init_config(Name, BridgeOpts), + Options = mk_client_options(Config, BridgeOpts), case emqtt:start_link(Options) of {ok, Pid} -> - true = gproc:reg_other(?NAME(Name), Pid, Conf), - {ok, Pid}; + connect(Pid, Name, Config); {error, Reason} = Error -> ?SLOG(error, #{ msg => "client_start_failed", @@ -130,22 +170,50 @@ start_link(Name, BridgeOpts) -> Error end. +connect(Pid, Name, Config = #{subscriptions := Subscriptions}) -> + case emqtt:connect(Pid) of + {ok, Props} -> + case subscribe_remote_topics(Pid, Subscriptions) of + ok -> + {ok, Pid, {Props, Config}}; + {ok, _, _RCs} -> + {ok, Pid, {Props, Config}}; + {error, Reason} = Error -> + ?SLOG(error, #{ + msg => "client_subscribe_failed", + subscriptions => Subscriptions, + reason => Reason + }), + _ = emqtt:stop(Pid), + Error + end; + {error, Reason} = Error -> + ?SLOG(warning, #{ + msg => "client_connect_failed", + reason => Reason, + name => Name + }), + _ = emqtt:stop(Pid), + Error + end. + +subscribe_remote_topics(Pid, #{remote := #{topic := RemoteTopic, qos := QoS}}) -> + emqtt:subscribe(Pid, RemoteTopic, QoS); +subscribe_remote_topics(_Ref, undefined) -> + ok. + init_config(Name, Opts) -> - Mountpoint = maps:get(forward_mountpoint, Opts, undefined), Subscriptions = maps:get(subscriptions, Opts, undefined), Forwards = maps:get(forwards, Opts, undefined), #{ - mountpoint => format_mountpoint(Mountpoint), subscriptions => pre_process_subscriptions(Subscriptions, Name, Opts), forwards => pre_process_forwards(Forwards) }. -mk_client_options(Conf, BridgeOpts) -> +mk_client_options(Config, BridgeOpts) -> Server = iolist_to_binary(maps:get(server, BridgeOpts)), HostPort = emqx_connector_mqtt_schema:parse_server(Server), - Mountpoint = maps:get(receive_mountpoint, BridgeOpts, undefined), - Subscriptions = maps:get(subscriptions, Conf), - Vars = emqx_connector_mqtt_msg:make_pub_vars(Mountpoint, Subscriptions), + Subscriptions = maps:get(subscriptions, Config), CleanStart = case Subscriptions of #{remote := _} -> @@ -156,24 +224,26 @@ mk_client_options(Conf, BridgeOpts) -> %% to ensure proper session recovery according to the MQTT spec. true end, - Opts = maps:without( + Opts = maps:with( [ - address, - auto_reconnect, - conn_type, - mountpoint, - forwards, - receive_mountpoint, - subscriptions + proto_ver, + username, + password, + clientid, + max_inflight, + connect_timeout, + retry_interval, + bridge_mode, + ssl, + ssl_opts ], BridgeOpts ), Opts#{ - msg_handler => mk_client_event_handler(Vars, #{server => Server}), + msg_handler => mk_client_event_handler(Subscriptions, #{server => Server}), hosts => [HostPort], clean_start => CleanStart, - force_ping => true, - proto_ver => maps:get(proto_ver, BridgeOpts, v4) + force_ping => true }. mk_client_event_handler(Vars, Opts) when Vars /= undefined -> @@ -184,45 +254,15 @@ mk_client_event_handler(Vars, Opts) when Vars /= undefined -> mk_client_event_handler(undefined, _Opts) -> undefined. -connect(Name) -> - #{subscriptions := Subscriptions} = get_config(Name), - case emqtt:connect(get_pid(Name)) of - {ok, Properties} -> - case subscribe_remote_topics(Name, Subscriptions) of - ok -> - {ok, Properties}; - {ok, _, _RCs} -> - {ok, Properties}; - {error, Reason} = Error -> - ?SLOG(error, #{ - msg => "client_subscribe_failed", - subscriptions => Subscriptions, - reason => Reason - }), - Error - end; - {error, Reason} = Error -> - ?SLOG(warning, #{ - msg => "client_connect_failed", - reason => Reason, - name => Name - }), - Error - end. -subscribe_remote_topics(Ref, #{remote := #{topic := FromTopic, qos := QoS}}) -> - emqtt:subscribe(ref(Ref), FromTopic, QoS); -subscribe_remote_topics(_Ref, undefined) -> - ok. +stop(Pid) -> + emqtt:stop(Pid). -stop(Ref) -> - emqtt:stop(ref(Ref)). +info(Pid) -> + emqtt:info(Pid). -info(Ref) -> - emqtt:info(ref(Ref)). - -status(Ref) -> +status(Pid) -> try - case proplists:get_value(socket, info(Ref)) of + case proplists:get_value(socket, info(Pid)) of Socket when Socket /= undefined -> connected; undefined -> @@ -233,14 +273,14 @@ status(Ref) -> disconnected end. -ping(Ref) -> - emqtt:ping(ref(Ref)). +ping(Pid) -> + emqtt:ping(Pid). -send_to_remote(Name, MsgIn) -> - trycall(fun() -> do_send(Name, export_msg(Name, MsgIn)) end). +send_to_remote(Pid, MsgIn, Conf) -> + do_send(Pid, export_msg(MsgIn, Conf)). -do_send(Name, {true, Msg}) -> - case emqtt:publish(get_pid(Name), Msg) of +do_send(Pid, {true, Msg}) -> + case emqtt:publish(Pid, Msg) of ok -> ok; {ok, #{reason_code := RC}} when @@ -266,36 +306,15 @@ do_send(Name, {true, Msg}) -> do_send(_Name, false) -> ok. -send_to_remote_async(Name, MsgIn, Callback) -> - trycall(fun() -> do_send_async(Name, export_msg(Name, MsgIn), Callback) end). +send_to_remote_async(Pid, MsgIn, Callback, Conf) -> + do_send_async(Pid, export_msg(MsgIn, Conf), Callback). -do_send_async(Name, {true, Msg}, Callback) -> - Pid = get_pid(Name), +do_send_async(Pid, {true, Msg}, Callback) -> ok = emqtt:publish_async(Pid, Msg, _Timeout = infinity, Callback), {ok, Pid}; -do_send_async(_Name, false, _Callback) -> +do_send_async(_Pid, false, _Callback) -> ok. -ref(Pid) when is_pid(Pid) -> - Pid; -ref(Term) -> - ?REF(Term). - -trycall(Fun) -> - try - Fun() - catch - throw:noproc -> - {error, disconnected}; - exit:{noproc, _} -> - {error, disconnected} - end. - -format_mountpoint(undefined) -> - undefined; -format_mountpoint(Prefix) -> - binary:replace(iolist_to_binary(Prefix), <<"${node}">>, atom_to_binary(node(), utf8)). - pre_process_subscriptions(undefined, _, _) -> undefined; pre_process_subscriptions( @@ -356,38 +375,15 @@ downgrade_ingress_qos(2) -> downgrade_ingress_qos(QoS) -> QoS. -get_pid(Name) -> - case gproc:where(?NAME(Name)) of - Pid when is_pid(Pid) -> - Pid; - undefined -> - throw(noproc) - end. - -get_config(Name) -> - try - gproc:lookup_value(?NAME(Name)) - catch - error:badarg -> - throw(noproc) - end. - -export_msg(Name, Msg) -> - case get_config(Name) of - #{forwards := Forwards = #{}, mountpoint := Mountpoint} -> - {true, export_msg(Mountpoint, Forwards, Msg)}; - #{forwards := undefined} -> - ?SLOG(error, #{ - msg => "forwarding_unavailable", - message => Msg, - reason => "egress is not configured" - }), - false - end. - -export_msg(Mountpoint, Forwards, Msg) -> - Vars = emqx_connector_mqtt_msg:make_pub_vars(Mountpoint, Forwards), - emqx_connector_mqtt_msg:to_remote_msg(Msg, Vars). +export_msg(Msg, #{forwards := Forwards = #{}}) -> + {true, emqx_connector_mqtt_msg:to_remote_msg(Msg, Forwards)}; +export_msg(Msg, #{forwards := undefined}) -> + ?SLOG(error, #{ + msg => "forwarding_unavailable", + message => Msg, + reason => "egress is not configured" + }), + false. %% From 67d703f8c579c324ff33aa7cca6bc8b3143dbe76 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Fri, 19 May 2023 16:32:22 +0300 Subject: [PATCH 46/72] refactor(mqttconn): simplify mqtt connector Inline `emqx_connector_mqtt_msg` module code into `emqx_connector_mqtt_worker` module, since it's not really used anywhere else and does not provide any reusable abstractions. --- .../src/mqtt/emqx_connector_mqtt_msg.erl | 134 -------------- .../src/mqtt/emqx_connector_mqtt_worker.erl | 167 +++++++++++++----- 2 files changed, 122 insertions(+), 179 deletions(-) delete mode 100644 apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl deleted file mode 100644 index 004819678..000000000 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl +++ /dev/null @@ -1,134 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_connector_mqtt_msg). - --export([ - to_remote_msg/2, - to_broker_msg/3 -]). - --export([ - replace_vars_in_str/2, - replace_simple_var/2 -]). - --export_type([msg/0]). - --include_lib("emqx/include/emqx.hrl"). - --include_lib("emqtt/include/emqtt.hrl"). - --type msg() :: emqx_types:message(). --type exp_msg() :: emqx_types:message() | #mqtt_msg{}. --type remote_config() :: #{ - topic := binary(), - qos := original | integer(), - retain := original | boolean(), - payload := binary() -}. --type variables() :: #{ - mountpoint := undefined | binary(), - remote := remote_config() -}. - -%% @doc Make export format: -%% 1. Mount topic to a prefix -%% 2. Fix QoS to 1 -%% @end -%% Shame that we have to know the callback module here -%% would be great if we can get rid of #mqtt_msg{} record -%% and use #message{} in all places. --spec to_remote_msg(msg() | map(), variables()) -> - exp_msg(). -to_remote_msg(#message{flags = Flags0} = Msg, Vars) -> - Retain0 = maps:get(retain, Flags0, false), - {Columns, _} = emqx_rule_events:eventmsg_publish(Msg), - MapMsg = maps:put(retain, Retain0, Columns), - to_remote_msg(MapMsg, Vars); -to_remote_msg(MapMsg, #{ - remote := #{ - topic := TopicToken, - qos := QoSToken, - retain := RetainToken - } = Remote -}) when is_map(MapMsg) -> - Topic = replace_vars_in_str(TopicToken, MapMsg), - Payload = process_payload(Remote, MapMsg), - QoS = replace_simple_var(QoSToken, MapMsg), - Retain = replace_simple_var(RetainToken, MapMsg), - PubProps = maps:get(pub_props, MapMsg, #{}), - #mqtt_msg{ - qos = QoS, - retain = Retain, - topic = Topic, - props = emqx_utils:pub_props_to_packet(PubProps), - payload = Payload - }. - -%% published from remote node over a MQTT connection -to_broker_msg(Msg, Vars, undefined) -> - to_broker_msg(Msg, Vars, #{}); -to_broker_msg( - #{dup := Dup} = MapMsg, - #{ - local := #{ - topic := TopicToken, - qos := QoSToken, - retain := RetainToken - } = Local - }, - Props -) -> - Topic = replace_vars_in_str(TopicToken, MapMsg), - Payload = process_payload(Local, MapMsg), - QoS = replace_simple_var(QoSToken, MapMsg), - Retain = replace_simple_var(RetainToken, MapMsg), - PubProps = maps:get(pub_props, MapMsg, #{}), - set_headers( - Props#{properties => emqx_utils:pub_props_to_packet(PubProps)}, - emqx_message:set_flags( - #{dup => Dup, retain => Retain}, - emqx_message:make(bridge, QoS, Topic, Payload) - ) - ). - -process_payload(From, MapMsg) -> - do_process_payload(maps:get(payload, From, undefined), MapMsg). - -do_process_payload(undefined, Msg) -> - emqx_utils_json:encode(Msg); -do_process_payload(Tks, Msg) -> - replace_vars_in_str(Tks, Msg). - -%% Replace a string contains vars to another string in which the placeholders are replace by the -%% corresponding values. For example, given "a: ${var}", if the var=1, the result string will be: -%% "a: 1". -replace_vars_in_str(Tokens, Data) when is_list(Tokens) -> - emqx_plugin_libs_rule:proc_tmpl(Tokens, Data, #{return => full_binary}); -replace_vars_in_str(Val, _Data) -> - Val. - -%% Replace a simple var to its value. For example, given "${var}", if the var=1, then the result -%% value will be an integer 1. -replace_simple_var(Tokens, Data) when is_list(Tokens) -> - [Var] = emqx_plugin_libs_rule:proc_tmpl(Tokens, Data, #{return => rawlist}), - Var; -replace_simple_var(Val, _Data) -> - Val. - -set_headers(Val, Msg) -> - emqx_message:set_headers(Val, Msg). diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl index c49d5b180..4169c7f69 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl @@ -61,8 +61,8 @@ -module(emqx_connector_mqtt_worker). --include_lib("snabbkaffe/include/snabbkaffe.hrl"). -include_lib("emqx/include/logger.hrl"). +-include_lib("emqx/include/emqx.hrl"). %% APIs -export([ @@ -79,7 +79,7 @@ send_to_remote_async/4 ]). --export([handle_publish/3]). +-export([handle_publish/4]). -export([handle_disconnect/1]). -export_type([config/0]). @@ -117,12 +117,7 @@ topic := emqx_topic:topic(), qos => emqx_types:qos() }, - local := #{ - topic => template(), - qos => template() | emqx_types:qos(), - retain => template() | boolean(), - payload => template() | undefined - }, + local := msgvars(), on_message_received := {module(), atom(), [term()]} }. @@ -130,12 +125,14 @@ local => #{ topic => emqx_topic:topic() }, - remote := #{ - topic := template(), - qos => template() | emqx_types:qos(), - retain => template() | boolean(), - payload => template() | undefined - } + remote := msgvars() +}. + +-type msgvars() :: #{ + topic => template(), + qos => template() | emqx_types:qos(), + retain => template() | boolean(), + payload => template() | undefined }. -include_lib("emqx/include/logger.hrl"). @@ -246,9 +243,17 @@ mk_client_options(Config, BridgeOpts) -> force_ping => true }. -mk_client_event_handler(Vars, Opts) when Vars /= undefined -> +mk_client_event_handler(Subscriptions = #{}, Opts) -> + OnMessage = maps:get(on_message_received, Subscriptions, undefined), + LocalPublish = + case Subscriptions of + #{local := Local = #{topic := _}} -> + Local; + #{} -> + undefined + end, #{ - publish => {fun ?MODULE:handle_publish/3, [Vars, Opts]}, + publish => {fun ?MODULE:handle_publish/4, [OnMessage, LocalPublish, Opts]}, disconnected => {fun ?MODULE:handle_disconnect/1, []} }; mk_client_event_handler(undefined, _Opts) -> @@ -279,7 +284,7 @@ ping(Pid) -> send_to_remote(Pid, MsgIn, Conf) -> do_send(Pid, export_msg(MsgIn, Conf)). -do_send(Pid, {true, Msg}) -> +do_send(Pid, Msg) when Msg /= undefined -> case emqtt:publish(Pid, Msg) of ok -> ok; @@ -303,20 +308,18 @@ do_send(Pid, {true, Msg}) -> }), {error, Reason} end; -do_send(_Name, false) -> +do_send(_Name, undefined) -> ok. send_to_remote_async(Pid, MsgIn, Callback, Conf) -> do_send_async(Pid, export_msg(MsgIn, Conf), Callback). -do_send_async(Pid, {true, Msg}, Callback) -> +do_send_async(Pid, Msg, Callback) when Msg /= undefined -> ok = emqtt:publish_async(Pid, Msg, _Timeout = infinity, Callback), {ok, Pid}; -do_send_async(_Pid, false, _Callback) -> +do_send_async(_Pid, undefined, _Callback) -> ok. -pre_process_subscriptions(undefined, _, _) -> - undefined; pre_process_subscriptions( #{remote := RC, local := LC} = Conf, BridgeName, @@ -330,8 +333,6 @@ pre_process_subscriptions(Conf, _, _) when is_map(Conf) -> %% have no 'local' field in the config undefined. -pre_process_forwards(undefined) -> - undefined; pre_process_forwards(#{remote := RC} = Conf) when is_map(Conf) -> Conf#{remote => pre_process_in_out_common(RC)}; pre_process_forwards(Conf) when is_map(Conf) -> @@ -375,44 +376,39 @@ downgrade_ingress_qos(2) -> downgrade_ingress_qos(QoS) -> QoS. -export_msg(Msg, #{forwards := Forwards = #{}}) -> - {true, emqx_connector_mqtt_msg:to_remote_msg(Msg, Forwards)}; +export_msg(Msg, #{forwards := #{remote := Remote}}) -> + to_remote_msg(Msg, Remote); export_msg(Msg, #{forwards := undefined}) -> ?SLOG(error, #{ msg => "forwarding_unavailable", message => Msg, reason => "egress is not configured" }), - false. + undefined. %% -handle_publish(#{properties := Props} = MsgIn, Vars, Opts) -> +handle_publish(#{properties := Props} = MsgIn, OnMessage, LocalPublish, Opts) -> Msg = import_msg(MsgIn, Opts), ?SLOG(debug, #{ msg => "publish_local", - message => Msg, - vars => Vars + message => Msg }), - case Vars of - #{on_message_received := {Mod, Func, Args}} -> - _ = erlang:apply(Mod, Func, [Msg | Args]); - _ -> - ok - end, - maybe_publish_local(Msg, Vars, Props). + maybe_on_message_received(Msg, OnMessage), + maybe_publish_local(Msg, LocalPublish, Props). handle_disconnect(_Reason) -> ok. -maybe_publish_local(Msg, Vars, Props) -> - case emqx_utils_maps:deep_get([local, topic], Vars, undefined) of - %% local topic is not set, discard it - undefined -> - ok; - _ -> - emqx_broker:publish(emqx_connector_mqtt_msg:to_broker_msg(Msg, Vars, Props)) - end. +maybe_on_message_received(Msg, {Mod, Func, Args}) -> + erlang:apply(Mod, Func, [Msg | Args]); +maybe_on_message_received(_Msg, undefined) -> + ok. + +maybe_publish_local(Msg, Local = #{}, Props) -> + emqx_broker:publish(to_broker_msg(Msg, Local, Props)); +maybe_publish_local(_Msg, undefined, _Props) -> + ok. import_msg( #{ @@ -459,3 +455,84 @@ printable_maps(Headers) -> #{}, Headers ). + +%% Shame that we have to know the callback module here +%% would be great if we can get rid of #mqtt_msg{} record +%% and use #message{} in all places. +-spec to_remote_msg(emqx_types:message() | map(), msgvars()) -> + #mqtt_msg{}. +to_remote_msg(#message{flags = Flags} = Msg, Vars) -> + {EventMsg, _} = emqx_rule_events:eventmsg_publish(Msg), + to_remote_msg(EventMsg#{retain => maps:get(retain, Flags, false)}, Vars); +to_remote_msg( + MapMsg, + #{ + topic := TopicToken, + qos := QoSToken, + retain := RetainToken + } = Remote +) when is_map(MapMsg) -> + Topic = replace_vars_in_str(TopicToken, MapMsg), + Payload = process_payload(Remote, MapMsg), + QoS = replace_simple_var(QoSToken, MapMsg), + Retain = replace_simple_var(RetainToken, MapMsg), + PubProps = maps:get(pub_props, MapMsg, #{}), + #mqtt_msg{ + qos = QoS, + retain = Retain, + topic = Topic, + props = emqx_utils:pub_props_to_packet(PubProps), + payload = Payload + }. + +%% published from remote node over a MQTT connection +to_broker_msg(Msg, Vars, undefined) -> + to_broker_msg(Msg, Vars, #{}); +to_broker_msg( + #{dup := Dup} = MapMsg, + #{ + topic := TopicToken, + qos := QoSToken, + retain := RetainToken + } = Local, + Props +) -> + Topic = replace_vars_in_str(TopicToken, MapMsg), + Payload = process_payload(Local, MapMsg), + QoS = replace_simple_var(QoSToken, MapMsg), + Retain = replace_simple_var(RetainToken, MapMsg), + PubProps = maps:get(pub_props, MapMsg, #{}), + set_headers( + Props#{properties => emqx_utils:pub_props_to_packet(PubProps)}, + emqx_message:set_flags( + #{dup => Dup, retain => Retain}, + emqx_message:make(bridge, QoS, Topic, Payload) + ) + ). + +process_payload(From, MapMsg) -> + do_process_payload(maps:get(payload, From, undefined), MapMsg). + +do_process_payload(undefined, Msg) -> + emqx_utils_json:encode(Msg); +do_process_payload(Tks, Msg) -> + replace_vars_in_str(Tks, Msg). + +%% Replace a string contains vars to another string in which the placeholders are replace by the +%% corresponding values. For example, given "a: ${var}", if the var=1, the result string will be: +%% "a: 1". +replace_vars_in_str(Tokens, Data) when is_list(Tokens) -> + emqx_plugin_libs_rule:proc_tmpl(Tokens, Data, #{return => full_binary}); +replace_vars_in_str(Val, _Data) -> + Val. + +%% Replace a simple var to its value. For example, given "${var}", if the var=1, then the result +%% value will be an integer 1. +replace_simple_var(Tokens, Data) when is_list(Tokens) -> + [Var] = emqx_plugin_libs_rule:proc_tmpl(Tokens, Data, #{return => rawlist}), + Var; +replace_simple_var(Val, _Data) -> + Val. + +set_headers(Val, Msg) -> + emqx_message:set_headers(Val, Msg). From 6967f621d8860f6a5e461bd0cb0df6e9ca72a465 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Mon, 22 May 2023 17:24:11 +0300 Subject: [PATCH 47/72] fix(mqttconn): unify error interpretation in sync/async modes Also move this logic to the mqtt connector itself, in order to avoid dealing with extra callback layer. --- .../src/emqx_connector_mqtt.erl | 45 +++++++++++-------- .../src/mqtt/emqx_connector_mqtt_worker.erl | 24 +--------- 2 files changed, 27 insertions(+), 42 deletions(-) diff --git a/apps/emqx_connector/src/emqx_connector_mqtt.erl b/apps/emqx_connector/src/emqx_connector_mqtt.erl index bd4bf6eb1..cc40b1606 100644 --- a/apps/emqx_connector/src/emqx_connector_mqtt.erl +++ b/apps/emqx_connector/src/emqx_connector_mqtt.erl @@ -15,6 +15,7 @@ %%-------------------------------------------------------------------- -module(emqx_connector_mqtt). +-include_lib("emqx/include/emqx_mqtt.hrl"). -include_lib("emqx/include/logger.hrl"). -behaviour(supervisor). @@ -141,12 +142,8 @@ on_stop(ResourceId, #{}) -> on_query(ResourceId, {send_message, Msg}, #{worker := Pid, config := Config}) -> ?TRACE("QUERY", "send_msg_to_remote_node", #{message => Msg, connector => ResourceId}), - case emqx_connector_mqtt_worker:send_to_remote(Pid, Msg, Config) of - ok -> - ok; - {error, Reason} -> - classify_error(Reason) - end. + Result = emqx_connector_mqtt_worker:send_to_remote(Pid, Msg, Config), + handle_send_result(Result). on_query_async(ResourceId, {send_message, Msg}, CallbackIn, #{worker := Pid, config := Config}) -> ?TRACE("QUERY", "async_send_msg_to_remote_node", #{message => Msg, connector => ResourceId}), @@ -156,16 +153,12 @@ on_query_async(ResourceId, {send_message, Msg}, CallbackIn, #{worker := Pid, con ok; {ok, Pid} -> {ok, Pid}; - {error, Reason} -> - classify_error(Reason) + {error, _} = Error -> + handle_send_result(Error) end. -on_async_result(Callback, ok) -> - apply_callback_function(Callback, ok); -on_async_result(Callback, {ok, _} = Ok) -> - apply_callback_function(Callback, Ok); -on_async_result(Callback, {error, Reason}) -> - apply_callback_function(Callback, classify_error(Reason)). +on_async_result(Callback, Result) -> + apply_callback_function(Callback, handle_send_result(Result)). apply_callback_function(F, Result) when is_function(F) -> erlang:apply(F, [Result]); @@ -177,16 +170,30 @@ apply_callback_function({M, F, A}, Result) when is_atom(M), is_atom(F), is_list( on_get_status(_ResourceId, #{worker := Pid}) -> emqx_connector_mqtt_worker:status(Pid). +handle_send_result(ok) -> + ok; +handle_send_result({ok, #{reason_code := ?RC_SUCCESS}}) -> + ok; +handle_send_result({ok, #{reason_code := ?RC_NO_MATCHING_SUBSCRIBERS}}) -> + ok; +handle_send_result({ok, Reply}) -> + {error, classify_reply(Reply)}; +handle_send_result({error, Reason}) -> + {error, classify_error(Reason)}. + +classify_reply(Reply = #{reason_code := _}) -> + {unrecoverable_error, Reply}. + classify_error(disconnected = Reason) -> - {error, {recoverable_error, Reason}}; + {recoverable_error, Reason}; classify_error({disconnected, _RC, _} = Reason) -> - {error, {recoverable_error, Reason}}; + {recoverable_error, Reason}; classify_error({shutdown, _} = Reason) -> - {error, {recoverable_error, Reason}}; + {recoverable_error, Reason}; classify_error(shutdown = Reason) -> - {error, {recoverable_error, Reason}}; + {recoverable_error, Reason}; classify_error(Reason) -> - {error, {unrecoverable_error, Reason}}. + {unrecoverable_error, Reason}. make_sub_confs(Subscriptions, _Conf, _) when map_size(Subscriptions) == 0 -> Subscriptions; diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl index 4169c7f69..8e3ca3136 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl @@ -285,29 +285,7 @@ send_to_remote(Pid, MsgIn, Conf) -> do_send(Pid, export_msg(MsgIn, Conf)). do_send(Pid, Msg) when Msg /= undefined -> - case emqtt:publish(Pid, Msg) of - ok -> - ok; - {ok, #{reason_code := RC}} when - RC =:= ?RC_SUCCESS; - RC =:= ?RC_NO_MATCHING_SUBSCRIBERS - -> - ok; - {ok, #{reason_code := RC, reason_code_name := Reason}} -> - ?SLOG(warning, #{ - msg => "remote_publish_failed", - message => Msg, - reason_code => RC, - reason_code_name => Reason - }), - {error, Reason}; - {error, Reason} -> - ?SLOG(info, #{ - msg => "client_failed", - reason => Reason - }), - {error, Reason} - end; + emqtt:publish(Pid, Msg); do_send(_Name, undefined) -> ok. From 81e78516aa907f14853722a0256af785ce0baff6 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 23 May 2023 18:54:31 +0300 Subject: [PATCH 48/72] feat(mqttconn): employ ecpool instead of a single worker --- .../test/emqx_bridge_mqtt_SUITE.erl | 10 +- .../src/emqx_connector_mqtt.erl | 156 +++++++----------- .../emqx_connector/src/emqx_connector_sup.erl | 1 - .../src/mqtt/emqx_connector_mqtt_worker.erl | 121 ++++++-------- 4 files changed, 120 insertions(+), 168 deletions(-) diff --git a/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl b/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl index c00eb6b14..67fb5d019 100644 --- a/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl +++ b/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl @@ -256,11 +256,15 @@ t_mqtt_egress_bridge_ignores_clean_start(_) -> } ), - {ok, _, #{state := #{worker := WorkerPid}}} = - emqx_resource:get_instance(emqx_bridge_resource:resource_id(BridgeID)), + ResourceID = emqx_bridge_resource:resource_id(BridgeID), + ClientInfo = ecpool:pick_and_do( + ResourceID, + {emqx_connector_mqtt_worker, info, []}, + no_handover + ), ?assertMatch( #{clean_start := true}, - maps:from_list(emqx_connector_mqtt_worker:info(WorkerPid)) + maps:from_list(ClientInfo) ), %% delete the bridge diff --git a/apps/emqx_connector/src/emqx_connector_mqtt.erl b/apps/emqx_connector/src/emqx_connector_mqtt.erl index cc40b1606..30791afe3 100644 --- a/apps/emqx_connector/src/emqx_connector_mqtt.erl +++ b/apps/emqx_connector/src/emqx_connector_mqtt.erl @@ -18,23 +18,13 @@ -include_lib("emqx/include/emqx_mqtt.hrl"). -include_lib("emqx/include/logger.hrl"). --behaviour(supervisor). -behaviour(emqx_resource). -%% API and callbacks for supervisor --export([ - callback_mode/0, - start_link/0, - init/1, - create_bridge/2, - remove_bridge/1, - bridges/0 -]). - -export([on_message_received/3]). %% callbacks of behaviour emqx_resource -export([ + callback_mode/0, on_start/2, on_stop/2, on_query/3, @@ -44,46 +34,7 @@ -export([on_async_result/2]). -%% =================================================================== -%% supervisor APIs -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -init([]) -> - SupFlag = #{ - strategy => one_for_one, - intensity => 100, - period => 10 - }, - {ok, {SupFlag, []}}. - -bridge_spec(Name, Options) -> - #{ - id => Name, - start => {emqx_connector_mqtt_worker, start_link, [Name, Options]}, - restart => temporary, - shutdown => 1000 - }. - --spec bridges() -> [{_Name, _Status}]. -bridges() -> - [ - {Name, emqx_connector_mqtt_worker:status(Name)} - || {Name, _Pid, _, _} <- supervisor:which_children(?MODULE) - ]. - -create_bridge(Name, Options) -> - supervisor:start_child(?MODULE, bridge_spec(Name, Options)). - -remove_bridge(Name) -> - case supervisor:terminate_child(?MODULE, Name) of - ok -> - supervisor:delete_child(?MODULE, Name); - {error, not_found} -> - ok; - {error, Error} -> - {error, Error} - end. +-define(HEALTH_CHECK_TIMEOUT, 1000). %% =================================================================== %% When use this bridge as a data source, ?MODULE:on_message_received will be called @@ -101,24 +52,16 @@ on_start(ResourceId, Conf) -> connector => ResourceId, config => emqx_utils:redact(Conf) }), - BasicConf = basic_config(Conf), - BridgeOpts = BasicConf#{ - clientid => clientid(ResourceId, Conf), + BasicOpts = mk_worker_opts(ResourceId, Conf), + BridgeOpts = BasicOpts#{ subscriptions => make_sub_confs(maps:get(ingress, Conf, #{}), Conf, ResourceId), forwards => maps:get(egress, Conf, #{}) }, - case create_bridge(ResourceId, BridgeOpts) of - {ok, Pid, {ConnProps, WorkerConf}} -> - {ok, #{ - name => ResourceId, - worker => Pid, - config => WorkerConf, - props => ConnProps - }}; - {error, {already_started, _Pid}} -> - ok = remove_bridge(ResourceId), - on_start(ResourceId, Conf); - {error, Reason} -> + {ok, ClientOpts, WorkerConf} = emqx_connector_mqtt_worker:init(ResourceId, BridgeOpts), + case emqx_resource_pool:start(ResourceId, emqx_connector_mqtt_worker, ClientOpts) of + ok -> + {ok, #{config => WorkerConf}}; + {error, {start_pool_failed, _, Reason}} -> {error, Reason} end. @@ -127,34 +70,34 @@ on_stop(ResourceId, #{}) -> msg => "stopping_mqtt_connector", connector => ResourceId }), - case remove_bridge(ResourceId) of - ok -> - ok; - {error, not_found} -> - ok; - {error, Reason} -> - ?SLOG(error, #{ - msg => "stop_mqtt_connector_error", - connector => ResourceId, - reason => Reason - }) - end. + emqx_resource_pool:stop(ResourceId). -on_query(ResourceId, {send_message, Msg}, #{worker := Pid, config := Config}) -> +on_query(ResourceId, {send_message, Msg}, #{config := Config}) -> ?TRACE("QUERY", "send_msg_to_remote_node", #{message => Msg, connector => ResourceId}), - Result = emqx_connector_mqtt_worker:send_to_remote(Pid, Msg, Config), - handle_send_result(Result). + handle_send_result(with_worker(ResourceId, send_to_remote, [Msg, Config])). -on_query_async(ResourceId, {send_message, Msg}, CallbackIn, #{worker := Pid, config := Config}) -> +on_query_async(ResourceId, {send_message, Msg}, CallbackIn, #{config := Config}) -> ?TRACE("QUERY", "async_send_msg_to_remote_node", #{message => Msg, connector => ResourceId}), Callback = {fun on_async_result/2, [CallbackIn]}, - case emqx_connector_mqtt_worker:send_to_remote_async(Pid, Msg, Callback, Config) of + Result = with_worker(ResourceId, send_to_remote_async, [Msg, Callback, Config]), + case Result of ok -> ok; - {ok, Pid} -> + {ok, Pid} when is_pid(Pid) -> {ok, Pid}; - {error, _} = Error -> - handle_send_result(Error) + {error, Reason} -> + {error, classify_error(Reason)} + end. + +with_worker(ResourceId, Fun, Args) -> + Worker = ecpool:get_client(ResourceId), + case is_pid(Worker) andalso ecpool_worker:client(Worker) of + {ok, Client} -> + erlang:apply(emqx_connector_mqtt_worker, Fun, [Client | Args]); + {error, Reason} -> + {error, Reason}; + false -> + {error, disconnected} end. on_async_result(Callback, Result) -> @@ -167,9 +110,6 @@ apply_callback_function({F, A}, Result) when is_function(F), is_list(A) -> apply_callback_function({M, F, A}, Result) when is_atom(M), is_atom(F), is_list(A) -> erlang:apply(M, F, A ++ [Result]). -on_get_status(_ResourceId, #{worker := Pid}) -> - emqx_connector_mqtt_worker:status(Pid). - handle_send_result(ok) -> ok; handle_send_result({ok, #{reason_code := ?RC_SUCCESS}}) -> @@ -195,6 +135,36 @@ classify_error(shutdown = Reason) -> classify_error(Reason) -> {unrecoverable_error, Reason}. +on_get_status(ResourceId, #{}) -> + Workers = [Worker || {_Name, Worker} <- ecpool:workers(ResourceId)], + try emqx_utils:pmap(fun get_status/1, Workers, ?HEALTH_CHECK_TIMEOUT) of + Statuses -> + combine_status(Statuses) + catch + exit:timeout -> + connecting + end. + +get_status(Worker) -> + case ecpool_worker:client(Worker) of + {ok, Client} -> + emqx_connector_mqtt_worker:status(Client); + {error, _} -> + disconnected + end. + +combine_status(Statuses) -> + %% NOTE + %% Natural order of statuses: [connected, connecting, disconnected] + %% * `disconnected` wins over any other status + %% * `connecting` wins over `connected` + case lists:reverse(lists:usort(Statuses)) of + [Status | _] -> + Status; + [] -> + disconnected + end. + make_sub_confs(Subscriptions, _Conf, _) when map_size(Subscriptions) == 0 -> Subscriptions; make_sub_confs(Subscriptions, #{hookpoint := HookPoint}, ResourceId) -> @@ -203,7 +173,8 @@ make_sub_confs(Subscriptions, #{hookpoint := HookPoint}, ResourceId) -> make_sub_confs(_SubRemoteConf, Conf, ResourceId) -> error({no_hookpoint_provided, ResourceId, Conf}). -basic_config( +mk_worker_opts( + ResourceId, #{ server := Server, proto_ver := ProtoVer, @@ -215,7 +186,7 @@ basic_config( ssl := #{enable := EnableSsl} = Ssl } = Conf ) -> - BasicConf = #{ + Options = #{ server => Server, %% 30s connect_timeout => 30, @@ -224,6 +195,7 @@ basic_config( %% A load balancing server (such as haproxy) is often set up before the emqx broker server. %% When the load balancing server enables mqtt connection packet inspection, %% non-standard mqtt connection packets might be filtered out by LB. + clientid => clientid(ResourceId, Conf), bridge_mode => BridgeMode, keepalive => ms_to_s(KeepAlive), clean_start => CleanStart, @@ -233,7 +205,7 @@ basic_config( ssl_opts => maps:to_list(maps:remove(enable, Ssl)) }, maps:merge( - BasicConf, + Options, maps:with([username, password], Conf) ). diff --git a/apps/emqx_connector/src/emqx_connector_sup.erl b/apps/emqx_connector/src/emqx_connector_sup.erl index 13516813f..21c0f2677 100644 --- a/apps/emqx_connector/src/emqx_connector_sup.erl +++ b/apps/emqx_connector/src/emqx_connector_sup.erl @@ -33,7 +33,6 @@ init([]) -> period => 20 }, ChildSpecs = [ - child_spec(emqx_connector_mqtt), child_spec(emqx_connector_jwt_sup) ], {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl index 8e3ca3136..7e33a55ca 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl @@ -14,51 +14,6 @@ %% limitations under the License. %%-------------------------------------------------------------------- -%% @doc Bridge works in two layers (1) batching layer (2) transport layer -%% The `bridge' batching layer collects local messages in batches and sends over -%% to remote MQTT node/cluster via `connection' transport layer. -%% In case `REMOTE' is also an EMQX node, `connection' is recommended to be -%% the `gen_rpc' based implementation `emqx_bridge_rpc'. Otherwise `connection' -%% has to be `emqx_connector_mqtt_mod'. -%% -%% ``` -%% +------+ +--------+ -%% | EMQX | | REMOTE | -%% | | | | -%% | (bridge) <==(connection)==> | | -%% | | | | -%% | | | | -%% +------+ +--------+ -%% ''' -%% -%% -%% This module implements 2 kinds of APIs with regards to batching and -%% messaging protocol. (1) A `gen_statem' based local batch collector; -%% (2) APIs for incoming remote batches/messages. -%% -%% Batch collector state diagram -%% -%% [idle] --(0) --> [connecting] --(2)--> [connected] -%% | ^ | -%% | | | -%% '--(1)---'--------(3)------' -%% -%% (0): auto or manual start -%% (1): retry timeout -%% (2): successfully connected to remote node/cluster -%% (3): received {disconnected, Reason} OR -%% failed to send to remote node/cluster. -%% -%% NOTE: A bridge worker may subscribe to multiple (including wildcard) -%% local topics, and the underlying `emqx_bridge_connect' may subscribe to -%% multiple remote topics, however, worker/connections are not designed -%% to support automatic load-balancing, i.e. in case it can not keep up -%% with the amount of messages coming in, administrator should split and -%% balance topics between worker/connections manually. -%% -%% NOTES: -%% * Local messages are all normalised to QoS-1 when exporting to remote - -module(emqx_connector_mqtt_worker). -include_lib("emqx/include/logger.hrl"). @@ -66,7 +21,8 @@ %% APIs -export([ - start_link/2, + init/2, + connect/1, stop/1 ]). @@ -99,6 +55,7 @@ max_inflight := pos_integer(), connect_timeout := pos_integer(), retry_interval := timeout(), + keepalive := non_neg_integer(), bridge_mode := boolean(), ssl := boolean(), ssl_opts := proplists:proplist(), @@ -107,6 +64,11 @@ forwards := map() }. +-type client_option() :: + emqtt:option() + | {name, name()} + | {subscriptions, subscriptions() | undefined}. + -type config() :: #{ subscriptions := subscriptions() | undefined, forwards := forwards() | undefined @@ -138,50 +100,64 @@ -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). -%% @doc Start a bridge worker. Supported configs: -%% mountpoint: The topic mount point for messages sent to remote node/cluster -%% `undefined', `<<>>' or `""' to disable -%% forwards: Local topics to subscribe. -%% -%% Find more connection specific configs in the callback modules -%% of emqx_bridge_connect behaviour. --spec start_link(name(), options()) -> - {ok, pid(), {emqtt:properties(), config()}} | {error, _Reason}. -start_link(Name, BridgeOpts) -> +-spec init(name(), options()) -> + {ok, [client_option()], config()}. +init(Name, BridgeOpts) -> + Config = init_config(Name, BridgeOpts), + ClientOpts0 = mk_client_options(Config, BridgeOpts), + ClientOpts = ClientOpts0#{ + name => Name, + subscriptions => maps:get(subscriptions, Config) + }, + {ok, maps:to_list(ClientOpts), Config}. + +%% @doc Start a bridge worker. +-spec connect([client_option() | {ecpool_worker_id, pos_integer()}]) -> + {ok, pid()} | {error, _Reason}. +connect(ClientOpts0) -> ?SLOG(debug, #{ msg => "client_starting", - name => Name, - options => BridgeOpts + options => emqx_utils:redact(ClientOpts0) }), - Config = init_config(Name, BridgeOpts), - Options = mk_client_options(Config, BridgeOpts), - case emqtt:start_link(Options) of + {value, {_, Name}, ClientOpts1} = lists:keytake(name, 1, ClientOpts0), + {value, {_, WorkerId}, ClientOpts} = lists:keytake(ecpool_worker_id, 1, ClientOpts1), + case emqtt:start_link(mk_emqtt_opts(WorkerId, ClientOpts)) of {ok, Pid} -> - connect(Pid, Name, Config); + connect(Pid, Name, WorkerId, ClientOpts); {error, Reason} = Error -> ?SLOG(error, #{ msg => "client_start_failed", - config => emqx_utils:redact(BridgeOpts), + config => emqx_utils:redact(ClientOpts), reason => Reason }), Error end. -connect(Pid, Name, Config = #{subscriptions := Subscriptions}) -> +mk_emqtt_opts(WorkerId, ClientOpts) -> + {_, ClientId} = lists:keyfind(clientid, 1, ClientOpts), + lists:keystore(clientid, 1, ClientOpts, {clientid, mk_clientid(WorkerId, ClientId)}). + +mk_clientid(WorkerId, ClientId) -> + iolist_to_binary([ClientId, $: | integer_to_list(WorkerId)]). + +connect(Pid, Name, WorkerId, ClientOpts) -> case emqtt:connect(Pid) of - {ok, Props} -> - case subscribe_remote_topics(Pid, Subscriptions) of - ok -> - {ok, Pid, {Props, Config}}; + {ok, _Props} -> + % NOTE + % Subscribe to remote topics only when the first worker is started. + Subscriptions = proplists:get_value(subscriptions, ClientOpts), + case WorkerId =:= 1 andalso subscribe_remote_topics(Pid, Subscriptions) of + false -> + {ok, Pid}; {ok, _, _RCs} -> - {ok, Pid, {Props, Config}}; + {ok, Pid}; {error, Reason} = Error -> ?SLOG(error, #{ msg => "client_subscribe_failed", subscriptions => Subscriptions, reason => Reason }), - _ = emqtt:stop(Pid), + _ = catch emqtt:stop(Pid), Error end; {error, Reason} = Error -> @@ -190,14 +166,14 @@ connect(Pid, Name, Config = #{subscriptions := Subscriptions}) -> reason => Reason, name => Name }), - _ = emqtt:stop(Pid), + _ = catch emqtt:stop(Pid), Error end. subscribe_remote_topics(Pid, #{remote := #{topic := RemoteTopic, qos := QoS}}) -> emqtt:subscribe(Pid, RemoteTopic, QoS); subscribe_remote_topics(_Ref, undefined) -> - ok. + false. init_config(Name, Opts) -> Subscriptions = maps:get(subscriptions, Opts, undefined), @@ -230,6 +206,7 @@ mk_client_options(Config, BridgeOpts) -> max_inflight, connect_timeout, retry_interval, + keepalive, bridge_mode, ssl, ssl_opts From 4e6269bedb6beca58c280f21f847c7be9f0d5be5 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Mon, 29 May 2023 13:57:55 +0300 Subject: [PATCH 49/72] feat(mqttconn): subscribe each worker if shared subcription Also rename `subscriptions` -> `ingress` and `forwards` -> `egress` for consistency with the config schema. --- .../test/emqx_bridge_mqtt_SUITE.erl | 48 ++++++++++ .../src/emqx_connector_mqtt.erl | 14 +-- .../src/mqtt/emqx_connector_mqtt_worker.erl | 92 +++++++++---------- 3 files changed, 98 insertions(+), 56 deletions(-) diff --git a/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl b/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl index 67fb5d019..aecb04e03 100644 --- a/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl +++ b/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl @@ -221,6 +221,12 @@ t_mqtt_conn_bridge_ingress(_) -> request(put, uri(["bridges", BridgeIDIngress]), ServerConf) ), + %% non-shared subscription, verify that only one client is subscribed + ?assertEqual( + 1, + length(emqx:subscribers(<>)) + ), + %% we now test if the bridge works as expected RemoteTopic = <>, LocalTopic = <>, @@ -245,6 +251,48 @@ t_mqtt_conn_bridge_ingress(_) -> ok. +t_mqtt_conn_bridge_ingress_shared_subscription(_) -> + PoolSize = 4, + Ns = lists:seq(1, 10), + BridgeName = atom_to_binary(?FUNCTION_NAME), + BridgeID = create_bridge( + ?SERVER_CONF(<<>>)#{ + <<"type">> => ?TYPE_MQTT, + <<"name">> => BridgeName, + <<"pool_size">> => PoolSize, + <<"ingress">> => #{ + <<"remote">> => #{ + <<"topic">> => <<"$share/ingress/", ?INGRESS_REMOTE_TOPIC, "/#">>, + <<"qos">> => 1 + }, + <<"local">> => #{ + <<"topic">> => <>, + <<"qos">> => <<"${qos}">>, + <<"payload">> => <<"${clientid}">>, + <<"retain">> => <<"${retain}">> + } + } + } + ), + + RemoteTopic = <>, + LocalTopic = <>, + ok = emqx:subscribe(LocalTopic), + + _ = emqx_utils:pmap( + fun emqx:publish/1, + [emqx_message:make(RemoteTopic, <<>>) || _ <- Ns] + ), + _ = [assert_mqtt_msg_received(LocalTopic) || _ <- Ns], + + ?assertEqual( + PoolSize, + length(emqx_shared_sub:subscribers(<<"ingress">>, <>)) + ), + + {ok, 204, <<>>} = request(delete, uri(["bridges", BridgeID]), []), + ok. + t_mqtt_egress_bridge_ignores_clean_start(_) -> BridgeName = atom_to_binary(?FUNCTION_NAME), BridgeID = create_bridge( diff --git a/apps/emqx_connector/src/emqx_connector_mqtt.erl b/apps/emqx_connector/src/emqx_connector_mqtt.erl index 30791afe3..09228254a 100644 --- a/apps/emqx_connector/src/emqx_connector_mqtt.erl +++ b/apps/emqx_connector/src/emqx_connector_mqtt.erl @@ -54,8 +54,8 @@ on_start(ResourceId, Conf) -> }), BasicOpts = mk_worker_opts(ResourceId, Conf), BridgeOpts = BasicOpts#{ - subscriptions => make_sub_confs(maps:get(ingress, Conf, #{}), Conf, ResourceId), - forwards => maps:get(egress, Conf, #{}) + ingress => mk_ingress_config(maps:get(ingress, Conf, #{}), Conf, ResourceId), + egress => maps:get(egress, Conf, #{}) }, {ok, ClientOpts, WorkerConf} = emqx_connector_mqtt_worker:init(ResourceId, BridgeOpts), case emqx_resource_pool:start(ResourceId, emqx_connector_mqtt_worker, ClientOpts) of @@ -165,12 +165,12 @@ combine_status(Statuses) -> disconnected end. -make_sub_confs(Subscriptions, _Conf, _) when map_size(Subscriptions) == 0 -> - Subscriptions; -make_sub_confs(Subscriptions, #{hookpoint := HookPoint}, ResourceId) -> +mk_ingress_config(Ingress, _Conf, _) when map_size(Ingress) == 0 -> + Ingress; +mk_ingress_config(Ingress, #{hookpoint := HookPoint}, ResourceId) -> MFA = {?MODULE, on_message_received, [HookPoint, ResourceId]}, - Subscriptions#{on_message_received => MFA}; -make_sub_confs(_SubRemoteConf, Conf, ResourceId) -> + Ingress#{on_message_received => MFA}; +mk_ingress_config(_Ingress, Conf, ResourceId) -> error({no_hookpoint_provided, ResourceId, Conf}). mk_worker_opts( diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl index 7e33a55ca..ede477602 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl @@ -60,21 +60,18 @@ ssl := boolean(), ssl_opts := proplists:proplist(), % bridge options - subscriptions := map(), - forwards := map() + ingress := map(), + egress := map() }. -type client_option() :: emqtt:option() | {name, name()} - | {subscriptions, subscriptions() | undefined}. + | {ingress, ingress() | undefined}. --type config() :: #{ - subscriptions := subscriptions() | undefined, - forwards := forwards() | undefined -}. +-type config() :: egress() | undefined. --type subscriptions() :: #{ +-type ingress() :: #{ remote := #{ topic := emqx_topic:topic(), qos => emqx_types:qos() @@ -83,7 +80,7 @@ on_message_received := {module(), atom(), [term()]} }. --type forwards() :: #{ +-type egress() :: #{ local => #{ topic => emqx_topic:topic() }, @@ -103,13 +100,10 @@ -spec init(name(), options()) -> {ok, [client_option()], config()}. init(Name, BridgeOpts) -> - Config = init_config(Name, BridgeOpts), - ClientOpts0 = mk_client_options(Config, BridgeOpts), - ClientOpts = ClientOpts0#{ - name => Name, - subscriptions => maps:get(subscriptions, Config) - }, - {ok, maps:to_list(ClientOpts), Config}. + Ingress = pre_process_ingress(maps:get(ingress, BridgeOpts), Name, BridgeOpts), + Egress = pre_process_egress(maps:get(egress, BridgeOpts)), + ClientOpts = mk_client_options(Name, Ingress, BridgeOpts), + {ok, maps:to_list(ClientOpts), Egress}. %% @doc Start a bridge worker. -spec connect([client_option() | {ecpool_worker_id, pos_integer()}]) -> @@ -134,7 +128,7 @@ connect(ClientOpts0) -> end. mk_emqtt_opts(WorkerId, ClientOpts) -> - {_, ClientId} = lists:keyfind(clientid, 1, ClientOpts), + ClientId = proplists:get_value(clientid, ClientOpts), lists:keystore(clientid, 1, ClientOpts, {clientid, mk_clientid(WorkerId, ClientId)}). mk_clientid(WorkerId, ClientId) -> @@ -143,10 +137,8 @@ mk_clientid(WorkerId, ClientId) -> connect(Pid, Name, WorkerId, ClientOpts) -> case emqtt:connect(Pid) of {ok, _Props} -> - % NOTE - % Subscribe to remote topics only when the first worker is started. - Subscriptions = proplists:get_value(subscriptions, ClientOpts), - case WorkerId =:= 1 andalso subscribe_remote_topics(Pid, Subscriptions) of + Ingress = proplists:get_value(ingress, ClientOpts), + case subscribe_remote_topic(Pid, WorkerId, Ingress) of false -> {ok, Pid}; {ok, _, _RCs} -> @@ -154,7 +146,7 @@ connect(Pid, Name, WorkerId, ClientOpts) -> {error, Reason} = Error -> ?SLOG(error, #{ msg => "client_subscribe_failed", - subscriptions => Subscriptions, + ingress => Ingress, reason => Reason }), _ = catch emqtt:stop(Pid), @@ -170,25 +162,25 @@ connect(Pid, Name, WorkerId, ClientOpts) -> Error end. -subscribe_remote_topics(Pid, #{remote := #{topic := RemoteTopic, qos := QoS}}) -> - emqtt:subscribe(Pid, RemoteTopic, QoS); -subscribe_remote_topics(_Ref, undefined) -> +subscribe_remote_topic(Pid, WorkerId, #{remote := #{topic := RemoteTopic, qos := QoS}}) -> + case emqx_topic:parse(RemoteTopic) of + {_Filter, #{share := _Name}} -> + % NOTE: this is shared subscription, each worker may subscribe + emqtt:subscribe(Pid, RemoteTopic, QoS); + {_Filter, #{}} when WorkerId =:= 1 -> + % NOTE: this is regular subscription, only the first worker should subscribe + emqtt:subscribe(Pid, RemoteTopic, QoS); + {_Filter, #{}} -> + false + end; +subscribe_remote_topic(_Ref, _, undefined) -> false. -init_config(Name, Opts) -> - Subscriptions = maps:get(subscriptions, Opts, undefined), - Forwards = maps:get(forwards, Opts, undefined), - #{ - subscriptions => pre_process_subscriptions(Subscriptions, Name, Opts), - forwards => pre_process_forwards(Forwards) - }. - -mk_client_options(Config, BridgeOpts) -> +mk_client_options(Name, Ingress, BridgeOpts) -> Server = iolist_to_binary(maps:get(server, BridgeOpts)), HostPort = emqx_connector_mqtt_schema:parse_server(Server), - Subscriptions = maps:get(subscriptions, Config), CleanStart = - case Subscriptions of + case Ingress of #{remote := _} -> maps:get(clean_start, BridgeOpts); undefined -> @@ -214,16 +206,18 @@ mk_client_options(Config, BridgeOpts) -> BridgeOpts ), Opts#{ - msg_handler => mk_client_event_handler(Subscriptions, #{server => Server}), + name => Name, + ingress => Ingress, + msg_handler => mk_client_event_handler(Ingress, #{server => Server}), hosts => [HostPort], clean_start => CleanStart, force_ping => true }. -mk_client_event_handler(Subscriptions = #{}, Opts) -> - OnMessage = maps:get(on_message_received, Subscriptions, undefined), +mk_client_event_handler(Ingress = #{}, Opts) -> + OnMessage = maps:get(on_message_received, Ingress, undefined), LocalPublish = - case Subscriptions of + case Ingress of #{local := Local = #{topic := _}} -> Local; #{} -> @@ -275,26 +269,26 @@ do_send_async(Pid, Msg, Callback) when Msg /= undefined -> do_send_async(_Pid, undefined, _Callback) -> ok. -pre_process_subscriptions( +pre_process_ingress( #{remote := RC, local := LC} = Conf, BridgeName, BridgeOpts ) when is_map(Conf) -> Conf#{ remote => pre_process_in_remote(RC, BridgeName, BridgeOpts), - local => pre_process_in_out_common(LC) + local => pre_process_common(LC) }; -pre_process_subscriptions(Conf, _, _) when is_map(Conf) -> +pre_process_ingress(Conf, _, _) when is_map(Conf) -> %% have no 'local' field in the config undefined. -pre_process_forwards(#{remote := RC} = Conf) when is_map(Conf) -> - Conf#{remote => pre_process_in_out_common(RC)}; -pre_process_forwards(Conf) when is_map(Conf) -> +pre_process_egress(#{remote := RC} = Conf) when is_map(Conf) -> + Conf#{remote => pre_process_common(RC)}; +pre_process_egress(Conf) when is_map(Conf) -> %% have no 'remote' field in the config undefined. -pre_process_in_out_common(Conf0) -> +pre_process_common(Conf0) -> Conf1 = pre_process_conf(topic, Conf0), Conf2 = pre_process_conf(qos, Conf1), Conf3 = pre_process_conf(payload, Conf2), @@ -331,9 +325,9 @@ downgrade_ingress_qos(2) -> downgrade_ingress_qos(QoS) -> QoS. -export_msg(Msg, #{forwards := #{remote := Remote}}) -> +export_msg(Msg, #{remote := Remote}) -> to_remote_msg(Msg, Remote); -export_msg(Msg, #{forwards := undefined}) -> +export_msg(Msg, undefined) -> ?SLOG(error, #{ msg => "forwarding_unavailable", message => Msg, From 0f080cda664a42fb61b9b35792fed667261ad8ba Mon Sep 17 00:00:00 2001 From: "Zaiming (Stone) Shi" Date: Tue, 30 May 2023 13:50:36 +0200 Subject: [PATCH 50/72] chore: bump emqx_rule_engine app vsn --- apps/emqx_rule_engine/src/emqx_rule_engine.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine.app.src b/apps/emqx_rule_engine/src/emqx_rule_engine.app.src index c6f94f5ea..7b4d1ee98 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_engine.app.src +++ b/apps/emqx_rule_engine/src/emqx_rule_engine.app.src @@ -2,7 +2,7 @@ {application, emqx_rule_engine, [ {description, "EMQX Rule Engine"}, % strict semver, bump manually! - {vsn, "5.0.18"}, + {vsn, "5.0.19"}, {modules, []}, {registered, [emqx_rule_engine_sup, emqx_rule_engine]}, {applications, [kernel, stdlib, rulesql, getopt, emqx_ctl]}, From 04bd39861d4f6c6d319bca9c6065e7baa2e9e766 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 30 May 2023 11:08:20 -0300 Subject: [PATCH 51/72] chore: bump app vsns --- apps/emqx_rule_engine/src/emqx_rule_engine.app.src | 2 +- lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.app.src | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine.app.src b/apps/emqx_rule_engine/src/emqx_rule_engine.app.src index c6f94f5ea..7b4d1ee98 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_engine.app.src +++ b/apps/emqx_rule_engine/src/emqx_rule_engine.app.src @@ -2,7 +2,7 @@ {application, emqx_rule_engine, [ {description, "EMQX Rule Engine"}, % strict semver, bump manually! - {vsn, "5.0.18"}, + {vsn, "5.0.19"}, {modules, []}, {registered, [emqx_rule_engine_sup, emqx_rule_engine]}, {applications, [kernel, stdlib, rulesql, getopt, emqx_ctl]}, diff --git a/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.app.src b/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.app.src index 4a52b2c35..4b6acf915 100644 --- a/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.app.src +++ b/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.app.src @@ -1,6 +1,6 @@ {application, emqx_ee_bridge, [ {description, "EMQX Enterprise data bridges"}, - {vsn, "0.1.14"}, + {vsn, "0.1.15"}, {registered, []}, {applications, [ kernel, From 26992e9d9888677b57388b472d155b2557b843a2 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 30 May 2023 11:08:31 -0300 Subject: [PATCH 52/72] test(banned): attempt to improve flaky test --- apps/emqx/test/emqx_banned_SUITE.erl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/emqx/test/emqx_banned_SUITE.erl b/apps/emqx/test/emqx_banned_SUITE.erl index 9419ba4c3..47134c666 100644 --- a/apps/emqx/test/emqx_banned_SUITE.erl +++ b/apps/emqx/test/emqx_banned_SUITE.erl @@ -145,11 +145,12 @@ t_kick(_) -> t_session_taken(_) -> erlang:process_flag(trap_exit, true), Topic = <<"t/banned">>, - ClientId2 = <<"t_session_taken">>, + ClientId2 = emqx_guid:to_hexstr(emqx_guid:gen()), MsgNum = 3, Connect = fun() -> + ClientId = emqx_guid:to_hexstr(emqx_guid:gen()), {ok, C} = emqtt:start_link([ - {clientid, <<"client1">>}, + {clientid, ClientId}, {proto_ver, v5}, {clean_start, false}, {properties, #{'Session-Expiry-Interval' => 120}} @@ -188,9 +189,9 @@ t_session_taken(_) -> end, 15_000 ), - Publish(), C2 = Connect(), + Publish(), ?assertEqual(MsgNum, length(receive_messages(MsgNum + 1))), ok = emqtt:disconnect(C2), From cb7929a2fcd2d024a17765b13d24f23443e3b97a Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 30 May 2023 11:12:18 -0300 Subject: [PATCH 53/72] test(broker): attempt to reduce flakiness --- apps/emqx/test/emqx_broker_SUITE.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/emqx/test/emqx_broker_SUITE.erl b/apps/emqx/test/emqx_broker_SUITE.erl index d66f9d974..d0c26ceb5 100644 --- a/apps/emqx/test/emqx_broker_SUITE.erl +++ b/apps/emqx/test/emqx_broker_SUITE.erl @@ -93,6 +93,7 @@ end_per_group(_Group, _Config) -> emqx_common_test_helpers:stop_apps([]). init_per_suite(Config) -> + emqx_common_test_helpers:clear_screen(), Config. end_per_suite(_Config) -> @@ -442,7 +443,7 @@ t_connected_client_count_persistent(Config) when is_list(Config) -> emqx_cm_process_down ] ), - ?assertEqual(0, emqx_cm:get_connected_client_count()), + ?retry(_Sleep = 100, _Retries = 20, ?assertEqual(0, emqx_cm:get_connected_client_count())), ok; t_connected_client_count_persistent({'end', _Config}) -> snabbkaffe:stop(), From 6e97dffdb8a0b9941681e0bf599eaa574665c4d1 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Mon, 29 May 2023 14:01:59 +0300 Subject: [PATCH 54/72] feat(mqttconn): deprecate `mode` config parameter It adds no value: the only mode was `cluster_shareload` and we just as well can decide to "share" the load across cluster just by looking if the remote topic is shared subcription filter or not. --- apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl index 2a40980af..a30de141e 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl @@ -68,7 +68,8 @@ fields("server_configs") -> hoconsc:enum([cluster_shareload]), #{ default => cluster_shareload, - desc => ?DESC("mode") + desc => ?DESC("mode"), + deprecated => {since, "v5.1.0 & e5.1.0"} } )}, {server, emqx_schema:servers_sc(#{desc => ?DESC("server")}, ?MQTT_HOST_OPTS)}, From c7528e9b35c0f55a76bc1c008d5c14d8a3d0f44d Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Mon, 29 May 2023 14:06:25 +0300 Subject: [PATCH 55/72] feat(mqttconn): add `pool_size` config parameter That currently tunes the number of MQTT clients employed both for subscriptions (if shared subscription is used) and for publishing to a remote broker. --- apps/emqx_connector/src/emqx_connector_mqtt.erl | 2 ++ apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl | 1 + apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl | 3 +++ 3 files changed, 6 insertions(+) diff --git a/apps/emqx_connector/src/emqx_connector_mqtt.erl b/apps/emqx_connector/src/emqx_connector_mqtt.erl index 09228254a..0658c28a7 100644 --- a/apps/emqx_connector/src/emqx_connector_mqtt.erl +++ b/apps/emqx_connector/src/emqx_connector_mqtt.erl @@ -177,6 +177,7 @@ mk_worker_opts( ResourceId, #{ server := Server, + pool_size := PoolSize, proto_ver := ProtoVer, bridge_mode := BridgeMode, clean_start := CleanStart, @@ -188,6 +189,7 @@ mk_worker_opts( ) -> Options = #{ server => Server, + pool_size => PoolSize, %% 30s connect_timeout => 30, proto_ver => ProtoVer, diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl index a30de141e..f02fd19ad 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl @@ -73,6 +73,7 @@ fields("server_configs") -> } )}, {server, emqx_schema:servers_sc(#{desc => ?DESC("server")}, ?MQTT_HOST_OPTS)}, + {pool_size, fun emqx_connector_schema_lib:pool_size/1}, {clientid_prefix, mk(binary(), #{required => false, desc => ?DESC("clientid_prefix")})}, {reconnect_interval, mk(string(), #{deprecated => {since, "v5.0.16"}})}, {proto_ver, diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl index ede477602..223d4d058 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl @@ -46,6 +46,7 @@ -type options() :: #{ % endpoint server := iodata(), + pool_size := pos_integer(), % emqtt client options proto_ver := v3 | v4 | v5, username := binary(), @@ -66,6 +67,7 @@ -type client_option() :: emqtt:option() + | {pool_size, pos_integer()} | {name, name()} | {ingress, ingress() | undefined}. @@ -191,6 +193,7 @@ mk_client_options(Name, Ingress, BridgeOpts) -> end, Opts = maps:with( [ + pool_size, proto_ver, username, password, From a5fc26736deed3675c6614c56f1f44e891b240fc Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Mon, 29 May 2023 19:43:20 +0300 Subject: [PATCH 56/72] refactor(mqttconn): split ingress/egress into 2 separate pools Each with a more refined set of responsibilities, at the cost of slight code duplication. Also provide two different config fields for each pool size. --- .../test/emqx_bridge_api_SUITE.erl | 9 +- .../test/emqx_bridge_mqtt_SUITE.erl | 10 +- .../src/emqx_connector_mqtt.erl | 244 ++++++--- .../src/mqtt/emqx_connector_mqtt_egress.erl | 162 ++++++ .../src/mqtt/emqx_connector_mqtt_ingress.erl | 272 ++++++++++ .../src/mqtt/emqx_connector_mqtt_msg.erl | 95 ++++ .../src/mqtt/emqx_connector_mqtt_schema.erl | 21 +- .../src/mqtt/emqx_connector_mqtt_worker.erl | 490 ------------------ apps/emqx_resource/src/emqx_resource.erl | 3 +- rel/i18n/emqx_connector_mqtt_schema.hocon | 18 + 10 files changed, 761 insertions(+), 563 deletions(-) create mode 100644 apps/emqx_connector/src/mqtt/emqx_connector_mqtt_egress.erl create mode 100644 apps/emqx_connector/src/mqtt/emqx_connector_mqtt_ingress.erl create mode 100644 apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl delete mode 100644 apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl diff --git a/apps/emqx_bridge/test/emqx_bridge_api_SUITE.erl b/apps/emqx_bridge/test/emqx_bridge_api_SUITE.erl index 1ac6750a4..ecab986e8 100644 --- a/apps/emqx_bridge/test/emqx_bridge_api_SUITE.erl +++ b/apps/emqx_bridge/test/emqx_bridge_api_SUITE.erl @@ -47,7 +47,14 @@ <<"server">> => SERVER, <<"username">> => <<"user1">>, <<"password">> => <<"">>, - <<"proto_ver">> => <<"v5">> + <<"proto_ver">> => <<"v5">>, + <<"egress">> => #{ + <<"remote">> => #{ + <<"topic">> => <<"emqx/${topic}">>, + <<"qos">> => <<"${qos}">>, + <<"retain">> => false + } + } }). -define(MQTT_BRIDGE(SERVER), ?MQTT_BRIDGE(SERVER, <<"mqtt_egress_test_bridge">>)). diff --git a/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl b/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl index aecb04e03..6c36e08e7 100644 --- a/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl +++ b/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl @@ -22,9 +22,7 @@ -include("emqx/include/emqx.hrl"). -include_lib("eunit/include/eunit.hrl"). --include_lib("common_test/include/ct.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). --include("emqx_dashboard/include/emqx_dashboard.hrl"). %% output functions -export([inspect/3]). @@ -259,8 +257,8 @@ t_mqtt_conn_bridge_ingress_shared_subscription(_) -> ?SERVER_CONF(<<>>)#{ <<"type">> => ?TYPE_MQTT, <<"name">> => BridgeName, - <<"pool_size">> => PoolSize, <<"ingress">> => #{ + <<"pool_size">> => PoolSize, <<"remote">> => #{ <<"topic">> => <<"$share/ingress/", ?INGRESS_REMOTE_TOPIC, "/#">>, <<"qos">> => 1 @@ -305,9 +303,11 @@ t_mqtt_egress_bridge_ignores_clean_start(_) -> ), ResourceID = emqx_bridge_resource:resource_id(BridgeID), + {ok, _Group, #{state := #{egress_pool_name := EgressPoolName}}} = + emqx_resource_manager:lookup_cached(ResourceID), ClientInfo = ecpool:pick_and_do( - ResourceID, - {emqx_connector_mqtt_worker, info, []}, + EgressPoolName, + {emqx_connector_mqtt_egress, info, []}, no_handover ), ?assertMatch( diff --git a/apps/emqx_connector/src/emqx_connector_mqtt.erl b/apps/emqx_connector/src/emqx_connector_mqtt.erl index 0658c28a7..a75f4db39 100644 --- a/apps/emqx_connector/src/emqx_connector_mqtt.erl +++ b/apps/emqx_connector/src/emqx_connector_mqtt.erl @@ -52,34 +52,134 @@ on_start(ResourceId, Conf) -> connector => ResourceId, config => emqx_utils:redact(Conf) }), - BasicOpts = mk_worker_opts(ResourceId, Conf), - BridgeOpts = BasicOpts#{ - ingress => mk_ingress_config(maps:get(ingress, Conf, #{}), Conf, ResourceId), - egress => maps:get(egress, Conf, #{}) - }, - {ok, ClientOpts, WorkerConf} = emqx_connector_mqtt_worker:init(ResourceId, BridgeOpts), - case emqx_resource_pool:start(ResourceId, emqx_connector_mqtt_worker, ClientOpts) of + case start_ingress(ResourceId, Conf) of + {ok, Result1} -> + case start_egress(ResourceId, Conf) of + {ok, Result2} -> + {ok, maps:merge(Result1, Result2)}; + {error, Reason} -> + _ = stop_ingress(Result1), + {error, Reason} + end; + {error, Reason} -> + {error, Reason} + end. + +start_ingress(ResourceId, Conf) -> + ClientOpts = mk_client_opts(ResourceId, "ingress", Conf), + case mk_ingress_config(ResourceId, Conf) of + Ingress = #{} -> + start_ingress(ResourceId, Ingress, ClientOpts); + undefined -> + {ok, #{}} + end. + +start_ingress(ResourceId, Ingress, ClientOpts) -> + PoolName = <>, + PoolSize = choose_ingress_pool_size(Ingress), + Options = [ + {name, PoolName}, + {pool_size, PoolSize}, + {ingress, Ingress}, + {client_opts, ClientOpts} + ], + case emqx_resource_pool:start(PoolName, emqx_connector_mqtt_ingress, Options) of ok -> - {ok, #{config => WorkerConf}}; + {ok, #{ingress_pool_name => PoolName}}; {error, {start_pool_failed, _, Reason}} -> {error, Reason} end. -on_stop(ResourceId, #{}) -> +choose_ingress_pool_size(#{remote := #{topic := RemoteTopic}, pool_size := PoolSize}) -> + case emqx_topic:parse(RemoteTopic) of + {_Filter, #{share := _Name}} -> + % NOTE: this is shared subscription, many workers may subscribe + PoolSize; + {_Filter, #{}} -> + % NOTE: this is regular subscription, only one worker should subscribe + ?SLOG(warning, #{ + msg => "ingress_pool_size_ignored", + reason => + "Remote topic filter is not a shared subscription, " + "ingress pool will start with a single worker", + config_pool_size => PoolSize, + pool_size => 1 + }), + 1 + end. + +start_egress(ResourceId, Conf) -> + % NOTE + % We are ignoring the user configuration here because there's currently no reliable way + % to ensure proper session recovery according to the MQTT spec. + ClientOpts = maps:put(clean_start, true, mk_client_opts(ResourceId, "egress", Conf)), + case mk_egress_config(Conf) of + Egress = #{} -> + start_egress(ResourceId, Egress, ClientOpts); + undefined -> + {ok, #{}} + end. + +start_egress(ResourceId, Egress, ClientOpts) -> + PoolName = <>, + PoolSize = maps:get(pool_size, Egress), + Options = [ + {name, PoolName}, + {pool_size, PoolSize}, + {client_opts, ClientOpts} + ], + case emqx_resource_pool:start(PoolName, emqx_connector_mqtt_egress, Options) of + ok -> + {ok, #{ + egress_pool_name => PoolName, + egress_config => emqx_connector_mqtt_egress:config(Egress) + }}; + {error, {start_pool_failed, _, Reason}} -> + {error, Reason} + end. + +on_stop(ResourceId, State) -> ?SLOG(info, #{ msg => "stopping_mqtt_connector", connector => ResourceId }), - emqx_resource_pool:stop(ResourceId). + ok = stop_ingress(State), + ok = stop_egress(State). -on_query(ResourceId, {send_message, Msg}, #{config := Config}) -> +stop_ingress(#{ingress_pool_name := PoolName}) -> + emqx_resource_pool:stop(PoolName); +stop_ingress(#{}) -> + ok. + +stop_egress(#{egress_pool_name := PoolName}) -> + emqx_resource_pool:stop(PoolName); +stop_egress(#{}) -> + ok. + +on_query( + ResourceId, + {send_message, Msg}, + #{egress_pool_name := PoolName, egress_config := Config} +) -> ?TRACE("QUERY", "send_msg_to_remote_node", #{message => Msg, connector => ResourceId}), - handle_send_result(with_worker(ResourceId, send_to_remote, [Msg, Config])). + handle_send_result(with_worker(PoolName, send, [Msg, Config])); +on_query(ResourceId, {send_message, Msg}, #{}) -> + ?SLOG(error, #{ + msg => "forwarding_unavailable", + connector => ResourceId, + message => Msg, + reason => "Egress is not configured" + }). -on_query_async(ResourceId, {send_message, Msg}, CallbackIn, #{config := Config}) -> +on_query_async( + ResourceId, + {send_message, Msg}, + CallbackIn, + #{egress_pool_name := PoolName, egress_config := Config} +) -> ?TRACE("QUERY", "async_send_msg_to_remote_node", #{message => Msg, connector => ResourceId}), Callback = {fun on_async_result/2, [CallbackIn]}, - Result = with_worker(ResourceId, send_to_remote_async, [Msg, Callback, Config]), + Result = with_worker(PoolName, send_async, [Msg, Callback, Config]), case Result of ok -> ok; @@ -87,13 +187,20 @@ on_query_async(ResourceId, {send_message, Msg}, CallbackIn, #{config := Config}) {ok, Pid}; {error, Reason} -> {error, classify_error(Reason)} - end. + end; +on_query_async(ResourceId, {send_message, Msg}, _Callback, #{}) -> + ?SLOG(error, #{ + msg => "forwarding_unavailable", + connector => ResourceId, + message => Msg, + reason => "Egress is not configured" + }). with_worker(ResourceId, Fun, Args) -> Worker = ecpool:get_client(ResourceId), case is_pid(Worker) andalso ecpool_worker:client(Worker) of {ok, Client} -> - erlang:apply(emqx_connector_mqtt_worker, Fun, [Client | Args]); + erlang:apply(emqx_connector_mqtt_egress, Fun, [Client | Args]); {error, Reason} -> {error, Reason}; false -> @@ -135,8 +242,9 @@ classify_error(shutdown = Reason) -> classify_error(Reason) -> {unrecoverable_error, Reason}. -on_get_status(ResourceId, #{}) -> - Workers = [Worker || {_Name, Worker} <- ecpool:workers(ResourceId)], +on_get_status(_ResourceId, State) -> + Pools = maps:to_list(maps:with([ingress_pool_name, egress_pool_name], State)), + Workers = [{Pool, Worker} || {Pool, PN} <- Pools, {_Name, Worker} <- ecpool:workers(PN)], try emqx_utils:pmap(fun get_status/1, Workers, ?HEALTH_CHECK_TIMEOUT) of Statuses -> combine_status(Statuses) @@ -145,10 +253,12 @@ on_get_status(ResourceId, #{}) -> connecting end. -get_status(Worker) -> +get_status({Pool, Worker}) -> case ecpool_worker:client(Worker) of - {ok, Client} -> - emqx_connector_mqtt_worker:status(Client); + {ok, Client} when Pool == ingress_pool_name -> + emqx_connector_mqtt_ingress:status(Client); + {ok, Client} when Pool == egress_pool_name -> + emqx_connector_mqtt_egress:status(Client); {error, _} -> disconnected end. @@ -165,56 +275,68 @@ combine_status(Statuses) -> disconnected end. -mk_ingress_config(Ingress, _Conf, _) when map_size(Ingress) == 0 -> - Ingress; -mk_ingress_config(Ingress, #{hookpoint := HookPoint}, ResourceId) -> - MFA = {?MODULE, on_message_received, [HookPoint, ResourceId]}, - Ingress#{on_message_received => MFA}; -mk_ingress_config(_Ingress, Conf, ResourceId) -> - error({no_hookpoint_provided, ResourceId, Conf}). - -mk_worker_opts( +mk_ingress_config( ResourceId, #{ + ingress := Ingress = #{remote := _}, server := Server, - pool_size := PoolSize, - proto_ver := ProtoVer, - bridge_mode := BridgeMode, - clean_start := CleanStart, - keepalive := KeepAlive, - retry_interval := RetryIntv, - max_inflight := MaxInflight, - ssl := #{enable := EnableSsl} = Ssl - } = Conf + hookpoint := HookPoint + } ) -> - Options = #{ + Ingress#{ server => Server, - pool_size => PoolSize, - %% 30s + on_message_received => {?MODULE, on_message_received, [HookPoint, ResourceId]} + }; +mk_ingress_config(ResourceId, #{ingress := #{remote := _}} = Conf) -> + error({no_hookpoint_provided, ResourceId, Conf}); +mk_ingress_config(_ResourceId, #{}) -> + undefined. + +mk_egress_config(#{egress := Egress = #{remote := _}}) -> + Egress; +mk_egress_config(#{}) -> + undefined. + +mk_client_opts( + ResourceId, + ClientScope, + Config = #{ + server := Server, + keepalive := KeepAlive, + ssl := #{enable := EnableSsl} = Ssl + } +) -> + HostPort = emqx_connector_mqtt_schema:parse_server(Server), + Options = maps:with( + [ + proto_ver, + username, + password, + clean_start, + retry_interval, + max_inflight, + % Opening a connection in bridge mode will form a non-standard mqtt connection message. + % A load balancing server (such as haproxy) is often set up before the emqx broker server. + % When the load balancing server enables mqtt connection packet inspection, + % non-standard mqtt connection packets might be filtered out by LB. + bridge_mode + ], + Config + ), + Options#{ + hosts => [HostPort], + clientid => clientid(ResourceId, ClientScope, Config), connect_timeout => 30, - proto_ver => ProtoVer, - %% Opening a connection in bridge mode will form a non-standard mqtt connection message. - %% A load balancing server (such as haproxy) is often set up before the emqx broker server. - %% When the load balancing server enables mqtt connection packet inspection, - %% non-standard mqtt connection packets might be filtered out by LB. - clientid => clientid(ResourceId, Conf), - bridge_mode => BridgeMode, keepalive => ms_to_s(KeepAlive), - clean_start => CleanStart, - retry_interval => RetryIntv, - max_inflight => MaxInflight, + force_ping => true, ssl => EnableSsl, ssl_opts => maps:to_list(maps:remove(enable, Ssl)) - }, - maps:merge( - Options, - maps:with([username, password], Conf) - ). + }. ms_to_s(Ms) -> erlang:ceil(Ms / 1000). -clientid(Id, _Conf = #{clientid_prefix := Prefix}) when is_binary(Prefix) -> - iolist_to_binary([Prefix, ":", Id, ":", atom_to_list(node())]); -clientid(Id, _Conf) -> - iolist_to_binary([Id, ":", atom_to_list(node())]). +clientid(Id, ClientScope, _Conf = #{clientid_prefix := Prefix}) when is_binary(Prefix) -> + iolist_to_binary([Prefix, ":", Id, ":", ClientScope, ":", atom_to_list(node())]); +clientid(Id, ClientScope, _Conf) -> + iolist_to_binary([Id, ":", ClientScope, ":", atom_to_list(node())]). diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_egress.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_egress.erl new file mode 100644 index 000000000..0e413cbc9 --- /dev/null +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_egress.erl @@ -0,0 +1,162 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_connector_mqtt_egress). + +-include_lib("emqx/include/logger.hrl"). +-include_lib("emqx/include/emqx.hrl"). +-include_lib("emqx/include/emqx_mqtt.hrl"). + +-behaviour(ecpool_worker). + +%% ecpool +-export([connect/1]). + +-export([ + config/1, + send/3, + send_async/4 +]). + +%% management APIs +-export([ + status/1, + info/1 +]). + +-type name() :: term(). +-type message() :: emqx_types:message() | map(). +-type callback() :: {function(), [_Arg]} | {module(), atom(), [_Arg]}. +-type remote_message() :: #mqtt_msg{}. + +-type option() :: + {name, name()} + %% see `emqtt:option()` + | {client_opts, map()}. + +-type egress() :: #{ + local => #{ + topic => emqx_topic:topic() + }, + remote := emqx_connector_mqtt_msg:msgvars() +}. + +%% @doc Start an ingress bridge worker. +-spec connect([option() | {ecpool_worker_id, pos_integer()}]) -> + {ok, pid()} | {error, _Reason}. +connect(Options) -> + ?SLOG(debug, #{ + msg => "egress_client_starting", + options => emqx_utils:redact(Options) + }), + Name = proplists:get_value(name, Options), + WorkerId = proplists:get_value(ecpool_worker_id, Options), + ClientOpts = proplists:get_value(client_opts, Options), + case emqtt:start_link(mk_client_opts(WorkerId, ClientOpts)) of + {ok, Pid} -> + connect(Pid, Name); + {error, Reason} = Error -> + ?SLOG(error, #{ + msg => "egress_client_start_failed", + config => emqx_utils:redact(ClientOpts), + reason => Reason + }), + Error + end. + +mk_client_opts(WorkerId, ClientOpts = #{clientid := ClientId}) -> + ClientOpts#{clientid := mk_clientid(WorkerId, ClientId)}. + +mk_clientid(WorkerId, ClientId) -> + iolist_to_binary([ClientId, $: | integer_to_list(WorkerId)]). + +connect(Pid, Name) -> + case emqtt:connect(Pid) of + {ok, _Props} -> + {ok, Pid}; + {error, Reason} = Error -> + ?SLOG(warning, #{ + msg => "egress_client_connect_failed", + reason => Reason, + name => Name + }), + _ = catch emqtt:stop(Pid), + Error + end. + +%% + +-spec config(map()) -> + egress(). +config(#{remote := RC = #{}} = Conf) -> + Conf#{remote => emqx_connector_mqtt_msg:parse(RC)}. + +-spec send(pid(), message(), egress()) -> + ok. +send(Pid, MsgIn, Egress) -> + emqtt:publish(Pid, export_msg(MsgIn, Egress)). + +-spec send_async(pid(), message(), callback(), egress()) -> + ok | {ok, pid()}. +send_async(Pid, MsgIn, Callback, Egress) -> + ok = emqtt:publish_async(Pid, export_msg(MsgIn, Egress), _Timeout = infinity, Callback), + {ok, Pid}. + +export_msg(Msg, #{remote := Remote}) -> + to_remote_msg(Msg, Remote). + +-spec to_remote_msg(message(), emqx_connector_mqtt_msg:msgvars()) -> + remote_message(). +to_remote_msg(#message{flags = Flags} = Msg, Vars) -> + {EventMsg, _} = emqx_rule_events:eventmsg_publish(Msg), + to_remote_msg(EventMsg#{retain => maps:get(retain, Flags, false)}, Vars); +to_remote_msg(Msg = #{}, Remote) -> + #{ + topic := Topic, + payload := Payload, + qos := QoS, + retain := Retain + } = emqx_connector_mqtt_msg:render(Msg, Remote), + PubProps = maps:get(pub_props, Msg, #{}), + #mqtt_msg{ + qos = QoS, + retain = Retain, + topic = Topic, + props = emqx_utils:pub_props_to_packet(PubProps), + payload = Payload + }. + +%% + +-spec info(pid()) -> + [{atom(), term()}]. +info(Pid) -> + emqtt:info(Pid). + +-spec status(pid()) -> + emqx_resource:resource_status(). +status(Pid) -> + try + case proplists:get_value(socket, info(Pid)) of + Socket when Socket /= undefined -> + connected; + undefined -> + connecting + end + catch + exit:{noproc, _} -> + disconnected + end. diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_ingress.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_ingress.erl new file mode 100644 index 000000000..c11895c49 --- /dev/null +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_ingress.erl @@ -0,0 +1,272 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_connector_mqtt_ingress). + +-include_lib("emqx/include/logger.hrl"). + +-behaviour(ecpool_worker). + +%% ecpool +-export([connect/1]). + +%% management APIs +-export([ + status/1, + info/1 +]). + +-export([handle_publish/4]). +-export([handle_disconnect/1]). + +-type name() :: term(). + +-type option() :: + {name, name()} + | {ingress, map()} + %% see `emqtt:option()` + | {client_opts, map()}. + +-type ingress() :: #{ + server := string(), + remote := #{ + topic := emqx_topic:topic(), + qos => emqx_types:qos() + }, + local := emqx_connector_mqtt_msg:msgvars(), + on_message_received := {module(), atom(), [term()]} +}. + +%% @doc Start an ingress bridge worker. +-spec connect([option() | {ecpool_worker_id, pos_integer()}]) -> + {ok, pid()} | {error, _Reason}. +connect(Options) -> + ?SLOG(debug, #{ + msg => "ingress_client_starting", + options => emqx_utils:redact(Options) + }), + Name = proplists:get_value(name, Options), + WorkerId = proplists:get_value(ecpool_worker_id, Options), + Ingress = config(proplists:get_value(ingress, Options), Name), + ClientOpts = proplists:get_value(client_opts, Options), + case emqtt:start_link(mk_client_opts(WorkerId, Ingress, ClientOpts)) of + {ok, Pid} -> + connect(Pid, Name, Ingress); + {error, Reason} = Error -> + ?SLOG(error, #{ + msg => "client_start_failed", + config => emqx_utils:redact(ClientOpts), + reason => Reason + }), + Error + end. + +mk_client_opts(WorkerId, Ingress, ClientOpts = #{clientid := ClientId}) -> + ClientOpts#{ + clientid := mk_clientid(WorkerId, ClientId), + msg_handler => mk_client_event_handler(Ingress) + }. + +mk_clientid(WorkerId, ClientId) -> + iolist_to_binary([ClientId, $: | integer_to_list(WorkerId)]). + +mk_client_event_handler(Ingress = #{}) -> + IngressVars = maps:with([server], Ingress), + OnMessage = maps:get(on_message_received, Ingress, undefined), + LocalPublish = + case Ingress of + #{local := Local = #{topic := _}} -> + Local; + #{} -> + undefined + end, + #{ + publish => {fun ?MODULE:handle_publish/4, [OnMessage, LocalPublish, IngressVars]}, + disconnected => {fun ?MODULE:handle_disconnect/1, []} + }. + +-spec connect(pid(), name(), ingress()) -> + {ok, pid()} | {error, _Reason}. +connect(Pid, Name, Ingress) -> + case emqtt:connect(Pid) of + {ok, _Props} -> + case subscribe_remote_topic(Pid, Ingress) of + {ok, _, _RCs} -> + {ok, Pid}; + {error, Reason} = Error -> + ?SLOG(error, #{ + msg => "ingress_client_subscribe_failed", + ingress => Ingress, + reason => Reason + }), + _ = catch emqtt:stop(Pid), + Error + end; + {error, Reason} = Error -> + ?SLOG(warning, #{ + msg => "ingress_client_connect_failed", + reason => Reason, + name => Name + }), + _ = catch emqtt:stop(Pid), + Error + end. + +subscribe_remote_topic(Pid, #{remote := #{topic := RemoteTopic, qos := QoS}}) -> + emqtt:subscribe(Pid, RemoteTopic, QoS). + +%% + +-spec config(map(), name()) -> + ingress(). +config(#{remote := RC, local := LC} = Conf, BridgeName) -> + Conf#{ + remote => parse_remote(RC, BridgeName), + local => emqx_connector_mqtt_msg:parse(LC) + }. + +parse_remote(#{qos := QoSIn} = Conf, BridgeName) -> + QoS = downgrade_ingress_qos(QoSIn), + case QoS of + QoSIn -> + ok; + _ -> + ?SLOG(warning, #{ + msg => "downgraded_unsupported_ingress_qos", + qos_configured => QoSIn, + qos_used => QoS, + name => BridgeName + }) + end, + Conf#{qos => QoS}. + +downgrade_ingress_qos(2) -> + 1; +downgrade_ingress_qos(QoS) -> + QoS. + +%% + +-spec info(pid()) -> + [{atom(), term()}]. +info(Pid) -> + emqtt:info(Pid). + +-spec status(pid()) -> + emqx_resource:resource_status(). +status(Pid) -> + try + case proplists:get_value(socket, info(Pid)) of + Socket when Socket /= undefined -> + connected; + undefined -> + connecting + end + catch + exit:{noproc, _} -> + disconnected + end. + +%% + +handle_publish(#{properties := Props} = MsgIn, OnMessage, LocalPublish, IngressVars) -> + Msg = import_msg(MsgIn, IngressVars), + ?SLOG(debug, #{ + msg => "publish_local", + message => Msg + }), + maybe_on_message_received(Msg, OnMessage), + maybe_publish_local(Msg, LocalPublish, Props). + +handle_disconnect(_Reason) -> + ok. + +maybe_on_message_received(Msg, {Mod, Func, Args}) -> + erlang:apply(Mod, Func, [Msg | Args]); +maybe_on_message_received(_Msg, undefined) -> + ok. + +maybe_publish_local(Msg, Local = #{}, Props) -> + emqx_broker:publish(to_broker_msg(Msg, Local, Props)); +maybe_publish_local(_Msg, undefined, _Props) -> + ok. + +%% + +import_msg( + #{ + dup := Dup, + payload := Payload, + properties := Props, + qos := QoS, + retain := Retain, + topic := Topic + }, + #{server := Server} +) -> + #{ + id => emqx_guid:to_hexstr(emqx_guid:gen()), + server => Server, + payload => Payload, + topic => Topic, + qos => QoS, + dup => Dup, + retain => Retain, + pub_props => printable_maps(Props), + message_received_at => erlang:system_time(millisecond) + }. + +printable_maps(undefined) -> + #{}; +printable_maps(Headers) -> + maps:fold( + fun + ('User-Property', V0, AccIn) when is_list(V0) -> + AccIn#{ + 'User-Property' => maps:from_list(V0), + 'User-Property-Pairs' => [ + #{ + key => Key, + value => Value + } + || {Key, Value} <- V0 + ] + }; + (K, V0, AccIn) -> + AccIn#{K => V0} + end, + #{}, + Headers + ). + +%% published from remote node over a MQTT connection +to_broker_msg(Msg, Vars, undefined) -> + to_broker_msg(Msg, Vars, #{}); +to_broker_msg(#{dup := Dup} = Msg, Local, Props) -> + #{ + topic := Topic, + payload := Payload, + qos := QoS, + retain := Retain + } = emqx_connector_mqtt_msg:render(Msg, Local), + PubProps = maps:get(pub_props, Msg, #{}), + emqx_message:set_headers( + Props#{properties => emqx_utils:pub_props_to_packet(PubProps)}, + emqx_message:set_flags( + #{dup => Dup, retain => Retain}, + emqx_message:make(bridge, QoS, Topic, Payload) + ) + ). diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl new file mode 100644 index 000000000..b57d69df6 --- /dev/null +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl @@ -0,0 +1,95 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_connector_mqtt_msg). + +-export([parse/1]). +-export([render/2]). + +-export_type([msgvars/0]). + +-type template() :: emqx_plugin_libs_rule:tmpl_token(). + +-type msgvars() :: #{ + topic => template(), + qos => template() | emqx_types:qos(), + retain => template() | boolean(), + payload => template() | undefined +}. + +%% + +-spec parse(#{ + topic => iodata(), + qos => iodata() | emqx_types:qos(), + retain => iodata() | boolean(), + payload => iodata() +}) -> + msgvars(). +parse(Conf) -> + Acc1 = parse_field(topic, Conf, Conf), + Acc2 = parse_field(qos, Conf, Acc1), + Acc3 = parse_field(payload, Conf, Acc2), + parse_field(retain, Conf, Acc3). + +parse_field(Key, Conf, Acc) -> + case Conf of + #{Key := Val} when is_binary(Val) -> + Acc#{Key => emqx_plugin_libs_rule:preproc_tmpl(Val)}; + #{Key := Val} -> + Acc#{Key => Val}; + #{} -> + Acc + end. + +render( + Msg, + #{ + topic := TopicToken, + qos := QoSToken, + retain := RetainToken + } = Vars +) -> + #{ + topic => render_string(TopicToken, Msg), + payload => render_payload(Vars, Msg), + qos => render_simple_var(QoSToken, Msg), + retain => render_simple_var(RetainToken, Msg) + }. + +render_payload(From, MapMsg) -> + do_render_payload(maps:get(payload, From, undefined), MapMsg). + +do_render_payload(undefined, Msg) -> + emqx_utils_json:encode(Msg); +do_render_payload(Tks, Msg) -> + render_string(Tks, Msg). + +%% Replace a string contains vars to another string in which the placeholders are replace by the +%% corresponding values. For example, given "a: ${var}", if the var=1, the result string will be: +%% "a: 1". +render_string(Tokens, Data) when is_list(Tokens) -> + emqx_placeholder:proc_tmpl(Tokens, Data, #{return => full_binary}); +render_string(Val, _Data) -> + Val. + +%% Replace a simple var to its value. For example, given "${var}", if the var=1, then the result +%% value will be an integer 1. +render_simple_var(Tokens, Data) when is_list(Tokens) -> + [Var] = emqx_placeholder:proc_tmpl(Tokens, Data, #{return => rawlist}), + Var; +render_simple_var(Val, _Data) -> + Val. diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl index f02fd19ad..6d06029b9 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl +++ b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl @@ -73,7 +73,6 @@ fields("server_configs") -> } )}, {server, emqx_schema:servers_sc(#{desc => ?DESC("server")}, ?MQTT_HOST_OPTS)}, - {pool_size, fun emqx_connector_schema_lib:pool_size/1}, {clientid_prefix, mk(binary(), #{required => false, desc => ?DESC("clientid_prefix")})}, {reconnect_interval, mk(string(), #{deprecated => {since, "v5.0.16"}})}, {proto_ver, @@ -135,12 +134,13 @@ fields("server_configs") -> ] ++ emqx_connector_schema_lib:ssl_fields(); fields("ingress") -> [ - {"remote", + {pool_size, fun ingress_pool_size/1}, + {remote, mk( ref(?MODULE, "ingress_remote"), #{desc => ?DESC(emqx_connector_mqtt_schema, "ingress_remote")} )}, - {"local", + {local, mk( ref(?MODULE, "ingress_local"), #{ @@ -206,7 +206,8 @@ fields("ingress_local") -> ]; fields("egress") -> [ - {"local", + {pool_size, fun egress_pool_size/1}, + {local, mk( ref(?MODULE, "egress_local"), #{ @@ -214,7 +215,7 @@ fields("egress") -> required => false } )}, - {"remote", + {remote, mk( ref(?MODULE, "egress_remote"), #{ @@ -274,6 +275,16 @@ fields("egress_remote") -> )} ]. +ingress_pool_size(desc) -> + ?DESC("ingress_pool_size"); +ingress_pool_size(Prop) -> + emqx_connector_schema_lib:pool_size(Prop). + +egress_pool_size(desc) -> + ?DESC("egress_pool_size"); +egress_pool_size(Prop) -> + emqx_connector_schema_lib:pool_size(Prop). + desc("server_configs") -> ?DESC("server_configs"); desc("ingress") -> diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl b/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl deleted file mode 100644 index 223d4d058..000000000 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_worker.erl +++ /dev/null @@ -1,490 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_connector_mqtt_worker). - --include_lib("emqx/include/logger.hrl"). --include_lib("emqx/include/emqx.hrl"). - -%% APIs --export([ - init/2, - connect/1, - stop/1 -]). - -%% management APIs --export([ - status/1, - ping/1, - info/1, - send_to_remote/3, - send_to_remote_async/4 -]). - --export([handle_publish/4]). --export([handle_disconnect/1]). - --export_type([config/0]). - --type template() :: emqx_plugin_libs_rule:tmpl_token(). - --type name() :: term(). --type options() :: #{ - % endpoint - server := iodata(), - pool_size := pos_integer(), - % emqtt client options - proto_ver := v3 | v4 | v5, - username := binary(), - password := binary(), - clientid := binary(), - clean_start := boolean(), - max_inflight := pos_integer(), - connect_timeout := pos_integer(), - retry_interval := timeout(), - keepalive := non_neg_integer(), - bridge_mode := boolean(), - ssl := boolean(), - ssl_opts := proplists:proplist(), - % bridge options - ingress := map(), - egress := map() -}. - --type client_option() :: - emqtt:option() - | {pool_size, pos_integer()} - | {name, name()} - | {ingress, ingress() | undefined}. - --type config() :: egress() | undefined. - --type ingress() :: #{ - remote := #{ - topic := emqx_topic:topic(), - qos => emqx_types:qos() - }, - local := msgvars(), - on_message_received := {module(), atom(), [term()]} -}. - --type egress() :: #{ - local => #{ - topic => emqx_topic:topic() - }, - remote := msgvars() -}. - --type msgvars() :: #{ - topic => template(), - qos => template() | emqx_types:qos(), - retain => template() | boolean(), - payload => template() | undefined -}. - --include_lib("emqx/include/logger.hrl"). --include_lib("emqx/include/emqx_mqtt.hrl"). - --spec init(name(), options()) -> - {ok, [client_option()], config()}. -init(Name, BridgeOpts) -> - Ingress = pre_process_ingress(maps:get(ingress, BridgeOpts), Name, BridgeOpts), - Egress = pre_process_egress(maps:get(egress, BridgeOpts)), - ClientOpts = mk_client_options(Name, Ingress, BridgeOpts), - {ok, maps:to_list(ClientOpts), Egress}. - -%% @doc Start a bridge worker. --spec connect([client_option() | {ecpool_worker_id, pos_integer()}]) -> - {ok, pid()} | {error, _Reason}. -connect(ClientOpts0) -> - ?SLOG(debug, #{ - msg => "client_starting", - options => emqx_utils:redact(ClientOpts0) - }), - {value, {_, Name}, ClientOpts1} = lists:keytake(name, 1, ClientOpts0), - {value, {_, WorkerId}, ClientOpts} = lists:keytake(ecpool_worker_id, 1, ClientOpts1), - case emqtt:start_link(mk_emqtt_opts(WorkerId, ClientOpts)) of - {ok, Pid} -> - connect(Pid, Name, WorkerId, ClientOpts); - {error, Reason} = Error -> - ?SLOG(error, #{ - msg => "client_start_failed", - config => emqx_utils:redact(ClientOpts), - reason => Reason - }), - Error - end. - -mk_emqtt_opts(WorkerId, ClientOpts) -> - ClientId = proplists:get_value(clientid, ClientOpts), - lists:keystore(clientid, 1, ClientOpts, {clientid, mk_clientid(WorkerId, ClientId)}). - -mk_clientid(WorkerId, ClientId) -> - iolist_to_binary([ClientId, $: | integer_to_list(WorkerId)]). - -connect(Pid, Name, WorkerId, ClientOpts) -> - case emqtt:connect(Pid) of - {ok, _Props} -> - Ingress = proplists:get_value(ingress, ClientOpts), - case subscribe_remote_topic(Pid, WorkerId, Ingress) of - false -> - {ok, Pid}; - {ok, _, _RCs} -> - {ok, Pid}; - {error, Reason} = Error -> - ?SLOG(error, #{ - msg => "client_subscribe_failed", - ingress => Ingress, - reason => Reason - }), - _ = catch emqtt:stop(Pid), - Error - end; - {error, Reason} = Error -> - ?SLOG(warning, #{ - msg => "client_connect_failed", - reason => Reason, - name => Name - }), - _ = catch emqtt:stop(Pid), - Error - end. - -subscribe_remote_topic(Pid, WorkerId, #{remote := #{topic := RemoteTopic, qos := QoS}}) -> - case emqx_topic:parse(RemoteTopic) of - {_Filter, #{share := _Name}} -> - % NOTE: this is shared subscription, each worker may subscribe - emqtt:subscribe(Pid, RemoteTopic, QoS); - {_Filter, #{}} when WorkerId =:= 1 -> - % NOTE: this is regular subscription, only the first worker should subscribe - emqtt:subscribe(Pid, RemoteTopic, QoS); - {_Filter, #{}} -> - false - end; -subscribe_remote_topic(_Ref, _, undefined) -> - false. - -mk_client_options(Name, Ingress, BridgeOpts) -> - Server = iolist_to_binary(maps:get(server, BridgeOpts)), - HostPort = emqx_connector_mqtt_schema:parse_server(Server), - CleanStart = - case Ingress of - #{remote := _} -> - maps:get(clean_start, BridgeOpts); - undefined -> - %% NOTE - %% We are ignoring the user configuration here because there's currently no reliable way - %% to ensure proper session recovery according to the MQTT spec. - true - end, - Opts = maps:with( - [ - pool_size, - proto_ver, - username, - password, - clientid, - max_inflight, - connect_timeout, - retry_interval, - keepalive, - bridge_mode, - ssl, - ssl_opts - ], - BridgeOpts - ), - Opts#{ - name => Name, - ingress => Ingress, - msg_handler => mk_client_event_handler(Ingress, #{server => Server}), - hosts => [HostPort], - clean_start => CleanStart, - force_ping => true - }. - -mk_client_event_handler(Ingress = #{}, Opts) -> - OnMessage = maps:get(on_message_received, Ingress, undefined), - LocalPublish = - case Ingress of - #{local := Local = #{topic := _}} -> - Local; - #{} -> - undefined - end, - #{ - publish => {fun ?MODULE:handle_publish/4, [OnMessage, LocalPublish, Opts]}, - disconnected => {fun ?MODULE:handle_disconnect/1, []} - }; -mk_client_event_handler(undefined, _Opts) -> - undefined. - -stop(Pid) -> - emqtt:stop(Pid). - -info(Pid) -> - emqtt:info(Pid). - -status(Pid) -> - try - case proplists:get_value(socket, info(Pid)) of - Socket when Socket /= undefined -> - connected; - undefined -> - connecting - end - catch - exit:{noproc, _} -> - disconnected - end. - -ping(Pid) -> - emqtt:ping(Pid). - -send_to_remote(Pid, MsgIn, Conf) -> - do_send(Pid, export_msg(MsgIn, Conf)). - -do_send(Pid, Msg) when Msg /= undefined -> - emqtt:publish(Pid, Msg); -do_send(_Name, undefined) -> - ok. - -send_to_remote_async(Pid, MsgIn, Callback, Conf) -> - do_send_async(Pid, export_msg(MsgIn, Conf), Callback). - -do_send_async(Pid, Msg, Callback) when Msg /= undefined -> - ok = emqtt:publish_async(Pid, Msg, _Timeout = infinity, Callback), - {ok, Pid}; -do_send_async(_Pid, undefined, _Callback) -> - ok. - -pre_process_ingress( - #{remote := RC, local := LC} = Conf, - BridgeName, - BridgeOpts -) when is_map(Conf) -> - Conf#{ - remote => pre_process_in_remote(RC, BridgeName, BridgeOpts), - local => pre_process_common(LC) - }; -pre_process_ingress(Conf, _, _) when is_map(Conf) -> - %% have no 'local' field in the config - undefined. - -pre_process_egress(#{remote := RC} = Conf) when is_map(Conf) -> - Conf#{remote => pre_process_common(RC)}; -pre_process_egress(Conf) when is_map(Conf) -> - %% have no 'remote' field in the config - undefined. - -pre_process_common(Conf0) -> - Conf1 = pre_process_conf(topic, Conf0), - Conf2 = pre_process_conf(qos, Conf1), - Conf3 = pre_process_conf(payload, Conf2), - pre_process_conf(retain, Conf3). - -pre_process_conf(Key, Conf) -> - case maps:find(Key, Conf) of - error -> - Conf; - {ok, Val} when is_binary(Val) -> - Conf#{Key => emqx_plugin_libs_rule:preproc_tmpl(Val)}; - {ok, Val} -> - Conf#{Key => Val} - end. - -pre_process_in_remote(#{qos := QoSIn} = Conf, BridgeName, BridgeOpts) -> - QoS = downgrade_ingress_qos(QoSIn), - case QoS of - QoSIn -> - ok; - _ -> - ?SLOG(warning, #{ - msg => "downgraded_unsupported_ingress_qos", - qos_configured => QoSIn, - qos_used => QoS, - name => BridgeName, - options => BridgeOpts - }) - end, - Conf#{qos => QoS}. - -downgrade_ingress_qos(2) -> - 1; -downgrade_ingress_qos(QoS) -> - QoS. - -export_msg(Msg, #{remote := Remote}) -> - to_remote_msg(Msg, Remote); -export_msg(Msg, undefined) -> - ?SLOG(error, #{ - msg => "forwarding_unavailable", - message => Msg, - reason => "egress is not configured" - }), - undefined. - -%% - -handle_publish(#{properties := Props} = MsgIn, OnMessage, LocalPublish, Opts) -> - Msg = import_msg(MsgIn, Opts), - ?SLOG(debug, #{ - msg => "publish_local", - message => Msg - }), - maybe_on_message_received(Msg, OnMessage), - maybe_publish_local(Msg, LocalPublish, Props). - -handle_disconnect(_Reason) -> - ok. - -maybe_on_message_received(Msg, {Mod, Func, Args}) -> - erlang:apply(Mod, Func, [Msg | Args]); -maybe_on_message_received(_Msg, undefined) -> - ok. - -maybe_publish_local(Msg, Local = #{}, Props) -> - emqx_broker:publish(to_broker_msg(Msg, Local, Props)); -maybe_publish_local(_Msg, undefined, _Props) -> - ok. - -import_msg( - #{ - dup := Dup, - payload := Payload, - properties := Props, - qos := QoS, - retain := Retain, - topic := Topic - }, - #{server := Server} -) -> - #{ - id => emqx_guid:to_hexstr(emqx_guid:gen()), - server => Server, - payload => Payload, - topic => Topic, - qos => QoS, - dup => Dup, - retain => Retain, - pub_props => printable_maps(Props), - message_received_at => erlang:system_time(millisecond) - }. - -printable_maps(undefined) -> - #{}; -printable_maps(Headers) -> - maps:fold( - fun - ('User-Property', V0, AccIn) when is_list(V0) -> - AccIn#{ - 'User-Property' => maps:from_list(V0), - 'User-Property-Pairs' => [ - #{ - key => Key, - value => Value - } - || {Key, Value} <- V0 - ] - }; - (K, V0, AccIn) -> - AccIn#{K => V0} - end, - #{}, - Headers - ). - -%% Shame that we have to know the callback module here -%% would be great if we can get rid of #mqtt_msg{} record -%% and use #message{} in all places. --spec to_remote_msg(emqx_types:message() | map(), msgvars()) -> - #mqtt_msg{}. -to_remote_msg(#message{flags = Flags} = Msg, Vars) -> - {EventMsg, _} = emqx_rule_events:eventmsg_publish(Msg), - to_remote_msg(EventMsg#{retain => maps:get(retain, Flags, false)}, Vars); -to_remote_msg( - MapMsg, - #{ - topic := TopicToken, - qos := QoSToken, - retain := RetainToken - } = Remote -) when is_map(MapMsg) -> - Topic = replace_vars_in_str(TopicToken, MapMsg), - Payload = process_payload(Remote, MapMsg), - QoS = replace_simple_var(QoSToken, MapMsg), - Retain = replace_simple_var(RetainToken, MapMsg), - PubProps = maps:get(pub_props, MapMsg, #{}), - #mqtt_msg{ - qos = QoS, - retain = Retain, - topic = Topic, - props = emqx_utils:pub_props_to_packet(PubProps), - payload = Payload - }. - -%% published from remote node over a MQTT connection -to_broker_msg(Msg, Vars, undefined) -> - to_broker_msg(Msg, Vars, #{}); -to_broker_msg( - #{dup := Dup} = MapMsg, - #{ - topic := TopicToken, - qos := QoSToken, - retain := RetainToken - } = Local, - Props -) -> - Topic = replace_vars_in_str(TopicToken, MapMsg), - Payload = process_payload(Local, MapMsg), - QoS = replace_simple_var(QoSToken, MapMsg), - Retain = replace_simple_var(RetainToken, MapMsg), - PubProps = maps:get(pub_props, MapMsg, #{}), - set_headers( - Props#{properties => emqx_utils:pub_props_to_packet(PubProps)}, - emqx_message:set_flags( - #{dup => Dup, retain => Retain}, - emqx_message:make(bridge, QoS, Topic, Payload) - ) - ). - -process_payload(From, MapMsg) -> - do_process_payload(maps:get(payload, From, undefined), MapMsg). - -do_process_payload(undefined, Msg) -> - emqx_utils_json:encode(Msg); -do_process_payload(Tks, Msg) -> - replace_vars_in_str(Tks, Msg). - -%% Replace a string contains vars to another string in which the placeholders are replace by the -%% corresponding values. For example, given "a: ${var}", if the var=1, the result string will be: -%% "a: 1". -replace_vars_in_str(Tokens, Data) when is_list(Tokens) -> - emqx_plugin_libs_rule:proc_tmpl(Tokens, Data, #{return => full_binary}); -replace_vars_in_str(Val, _Data) -> - Val. - -%% Replace a simple var to its value. For example, given "${var}", if the var=1, then the result -%% value will be an integer 1. -replace_simple_var(Tokens, Data) when is_list(Tokens) -> - [Var] = emqx_plugin_libs_rule:proc_tmpl(Tokens, Data, #{return => rawlist}), - Var; -replace_simple_var(Val, _Data) -> - Val. - -set_headers(Val, Msg) -> - emqx_message:set_headers(Val, Msg). diff --git a/apps/emqx_resource/src/emqx_resource.erl b/apps/emqx_resource/src/emqx_resource.erl index 840c6cfec..37d7b1696 100644 --- a/apps/emqx_resource/src/emqx_resource.erl +++ b/apps/emqx_resource/src/emqx_resource.erl @@ -121,7 +121,8 @@ -export_type([ resource_id/0, - resource_data/0 + resource_data/0, + resource_status/0 ]). -optional_callbacks([ diff --git a/rel/i18n/emqx_connector_mqtt_schema.hocon b/rel/i18n/emqx_connector_mqtt_schema.hocon index e37e87e49..509fc4209 100644 --- a/rel/i18n/emqx_connector_mqtt_schema.hocon +++ b/rel/i18n/emqx_connector_mqtt_schema.hocon @@ -32,6 +32,14 @@ is configured, then both the data got from the rule and the MQTT messages that m egress_desc.label: """Egress Configs""" +egress_pool_size.desc: +"""Size of the pool of MQTT clients that will publish messages to the remote broker.
+ Each MQTT client will be assigned 'clientid' of the form '${clientid_prefix}:${bridge_name}:egress:${node}:${n}' + where 'n' is the number of a client inside the pool.""" + +egress_pool_size.label: +"""Pool Size""" + egress_local.desc: """The configs about receiving messages from local broker.""" @@ -75,6 +83,16 @@ ingress_desc.desc: ingress_desc.label: """Ingress Configs""" +ingress_pool_size.desc: +"""Size of the pool of MQTT clients that will ingest messages from the remote broker.
+ This value will be respected only if 'remote.topic' is a shared subscription topic filter, + otherwise only a single MQTT client will be used. + Each MQTT client will be assigned 'clientid' of the form '${clientid_prefix}:${bridge_name}:ingress:${node}:${n}' + where 'n' is the number of a client inside the pool.""" + +ingress_pool_size.label: +"""Pool Size""" + ingress_local.desc: """The configs about sending message to the local broker.""" From eed9358abdb4b2ebfa9a86cfb27408488c51a147 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Mon, 29 May 2023 21:04:21 +0300 Subject: [PATCH 57/72] chore: bump `ecpool` to 0.5.4 With fixed typings and empty pool handling. --- .../emqx_connector/src/emqx_connector_mqtt.erl | 18 ++++++------------ mix.exs | 2 +- rebar.config | 2 +- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/apps/emqx_connector/src/emqx_connector_mqtt.erl b/apps/emqx_connector/src/emqx_connector_mqtt.erl index a75f4db39..255247011 100644 --- a/apps/emqx_connector/src/emqx_connector_mqtt.erl +++ b/apps/emqx_connector/src/emqx_connector_mqtt.erl @@ -162,7 +162,7 @@ on_query( #{egress_pool_name := PoolName, egress_config := Config} ) -> ?TRACE("QUERY", "send_msg_to_remote_node", #{message => Msg, connector => ResourceId}), - handle_send_result(with_worker(PoolName, send, [Msg, Config])); + handle_send_result(with_egress_client(PoolName, send, [Msg, Config])); on_query(ResourceId, {send_message, Msg}, #{}) -> ?SLOG(error, #{ msg => "forwarding_unavailable", @@ -179,7 +179,7 @@ on_query_async( ) -> ?TRACE("QUERY", "async_send_msg_to_remote_node", #{message => Msg, connector => ResourceId}), Callback = {fun on_async_result/2, [CallbackIn]}, - Result = with_worker(PoolName, send_async, [Msg, Callback, Config]), + Result = with_egress_client(PoolName, send_async, [Msg, Callback, Config]), case Result of ok -> ok; @@ -196,16 +196,8 @@ on_query_async(ResourceId, {send_message, Msg}, _Callback, #{}) -> reason => "Egress is not configured" }). -with_worker(ResourceId, Fun, Args) -> - Worker = ecpool:get_client(ResourceId), - case is_pid(Worker) andalso ecpool_worker:client(Worker) of - {ok, Client} -> - erlang:apply(emqx_connector_mqtt_egress, Fun, [Client | Args]); - {error, Reason} -> - {error, Reason}; - false -> - {error, disconnected} - end. +with_egress_client(ResourceId, Fun, Args) -> + ecpool:pick_and_do(ResourceId, {emqx_connector_mqtt_egress, Fun, Args}, no_handover). on_async_result(Callback, Result) -> apply_callback_function(Callback, handle_send_result(Result)). @@ -233,6 +225,8 @@ classify_reply(Reply = #{reason_code := _}) -> classify_error(disconnected = Reason) -> {recoverable_error, Reason}; +classify_error(ecpool_empty) -> + {recoverable_error, disconnected}; classify_error({disconnected, _RC, _} = Reason) -> {recoverable_error, Reason}; classify_error({shutdown, _} = Reason) -> diff --git a/mix.exs b/mix.exs index 2e2882e15..3fa3cc2bc 100644 --- a/mix.exs +++ b/mix.exs @@ -59,7 +59,7 @@ defmodule EMQXUmbrella.MixProject do {:gen_rpc, github: "emqx/gen_rpc", tag: "2.8.1", override: true}, {:grpc, github: "emqx/grpc-erl", tag: "0.6.7", override: true}, {:minirest, github: "emqx/minirest", tag: "1.3.10", override: true}, - {:ecpool, github: "emqx/ecpool", tag: "0.5.3", override: true}, + {:ecpool, github: "emqx/ecpool", tag: "0.5.4", override: true}, {:replayq, github: "emqx/replayq", tag: "0.3.7", override: true}, {:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true}, # maybe forbid to fetch quicer diff --git a/rebar.config b/rebar.config index 81545a7be..6a44b8074 100644 --- a/rebar.config +++ b/rebar.config @@ -66,7 +66,7 @@ , {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.1"}}} , {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.7"}}} , {minirest, {git, "https://github.com/emqx/minirest", {tag, "1.3.10"}}} - , {ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.3"}}} + , {ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.4"}}} , {replayq, {git, "https://github.com/emqx/replayq.git", {tag, "0.3.7"}}} , {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}} , {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.8.5"}}} From b9021dfa309c59252f2bf15b42e44f8b1b401318 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Mon, 29 May 2023 21:17:35 +0300 Subject: [PATCH 58/72] chore: drop orphaned i18n file --- rel/i18n/emqx_connector_mqtt.hocon | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 rel/i18n/emqx_connector_mqtt.hocon diff --git a/rel/i18n/emqx_connector_mqtt.hocon b/rel/i18n/emqx_connector_mqtt.hocon deleted file mode 100644 index 80303b825..000000000 --- a/rel/i18n/emqx_connector_mqtt.hocon +++ /dev/null @@ -1,21 +0,0 @@ -emqx_connector_mqtt { - -name.desc: -"""Connector name, used as a human-readable description of the connector.""" - -name.label: -"""Connector Name""" - -num_of_bridges.desc: -"""The current number of bridges that are using this connector.""" - -num_of_bridges.label: -"""Num of Bridges""" - -type.desc: -"""The Connector Type.""" - -type.label: -"""Connector Type""" - -} From ebd612b194bae1a3b94f875e743050db760df71f Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 30 May 2023 14:51:53 +0300 Subject: [PATCH 59/72] chore: bump applications versions * emqx_connector 0.1.25 * emqx_rule_engine 5.0.19 * emqx_ee_bridge 0.1.15 --- apps/emqx_connector/src/emqx_connector.app.src | 2 +- apps/emqx_rule_engine/src/emqx_rule_engine.app.src | 2 +- lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.app.src | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/emqx_connector/src/emqx_connector.app.src b/apps/emqx_connector/src/emqx_connector.app.src index e9f79723a..283c27f31 100644 --- a/apps/emqx_connector/src/emqx_connector.app.src +++ b/apps/emqx_connector/src/emqx_connector.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_connector, [ {description, "EMQX Data Integration Connectors"}, - {vsn, "0.1.24"}, + {vsn, "0.1.25"}, {registered, []}, {mod, {emqx_connector_app, []}}, {applications, [ diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine.app.src b/apps/emqx_rule_engine/src/emqx_rule_engine.app.src index c6f94f5ea..7b4d1ee98 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_engine.app.src +++ b/apps/emqx_rule_engine/src/emqx_rule_engine.app.src @@ -2,7 +2,7 @@ {application, emqx_rule_engine, [ {description, "EMQX Rule Engine"}, % strict semver, bump manually! - {vsn, "5.0.18"}, + {vsn, "5.0.19"}, {modules, []}, {registered, [emqx_rule_engine_sup, emqx_rule_engine]}, {applications, [kernel, stdlib, rulesql, getopt, emqx_ctl]}, diff --git a/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.app.src b/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.app.src index 4a52b2c35..4b6acf915 100644 --- a/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.app.src +++ b/lib-ee/emqx_ee_bridge/src/emqx_ee_bridge.app.src @@ -1,6 +1,6 @@ {application, emqx_ee_bridge, [ {description, "EMQX Enterprise data bridges"}, - {vsn, "0.1.14"}, + {vsn, "0.1.15"}, {registered, []}, {applications, [ kernel, From a28580248a9b4a677d0bdc1ebdb33c86c3c9696d Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 30 May 2023 11:23:17 -0300 Subject: [PATCH 60/72] test(exclusive_sub): fix test flakiness https://github.com/emqx/emqx/actions/runs/5115608275/jobs/9208594481?pr=10864#step:8:735 ``` Testing lib.emqx.emqx_access_control_SUITE: TEST COMPLETE, 4 ok, 0 failed of 4 test cases Testing lib.emqx.emqx_exclusive_sub_SUITE: Starting test, 5 test cases - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Error detected: {'EXIT',{shutdown,econnrefused}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Testing lib.emqx.emqx_exclusive_sub_SUITE: *** FAILED test case 1 of 5 *** %%% emqx_exclusive_sub_SUITE ==> t_allow_normal_sub: FAILED %%% emqx_exclusive_sub_SUITE ==> {'EXIT',{shutdown,econnrefused}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - emqx_exclusive_sub_SUITE:t_clean_session failed on line 128 Reason: {badmatch,{error,econnrefused}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Testing lib.emqx.emqx_exclusive_sub_SUITE: *** FAILED test case 2 of 5 *** %%% emqx_exclusive_sub_SUITE ==> t_clean_session: FAILED ``` To reproduce: ``` make ct-suite SUITE=apps/emqx/test/emqx_access_control_SUITE.erl,apps/emqx/test/emqx_exclusive_sub_SUITE.erl ``` --- apps/emqx/test/emqx_access_control_SUITE.erl | 3 --- apps/emqx/test/emqx_exclusive_sub_SUITE.erl | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/emqx/test/emqx_access_control_SUITE.erl b/apps/emqx/test/emqx_access_control_SUITE.erl index f5fdf223b..305eaf5eb 100644 --- a/apps/emqx/test/emqx_access_control_SUITE.erl +++ b/apps/emqx/test/emqx_access_control_SUITE.erl @@ -120,6 +120,3 @@ clientinfo(InitProps) -> }, InitProps ). - -toggle_auth(Bool) when is_boolean(Bool) -> - emqx_config:put_zone_conf(default, [auth, enable], Bool). diff --git a/apps/emqx/test/emqx_exclusive_sub_SUITE.erl b/apps/emqx/test/emqx_exclusive_sub_SUITE.erl index 79dfc9de6..1caf4cc9b 100644 --- a/apps/emqx/test/emqx_exclusive_sub_SUITE.erl +++ b/apps/emqx/test/emqx_exclusive_sub_SUITE.erl @@ -34,6 +34,7 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> + emqx_common_test_helpers:boot_modules(all), emqx_common_test_helpers:start_apps([]), ok = ekka:start(), OldConf = emqx:get_config([zones], #{}), From 57aacb471c73c8a7e358998286f48f1c07bebd1b Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 30 May 2023 11:51:18 -0300 Subject: [PATCH 61/72] fix(rule_engine_api): don't crash when formatting empty metrics Fixes https://emqx.atlassian.net/browse/EMQX-10073 Fixes https://github.com/emqx/emqx/issues/10714#issuecomment-1567987664 Similar issue to https://github.com/emqx/emqx/pull/10743, but on the rule engine API. --- .../src/emqx_rule_engine_api.erl | 104 +++++++++++------- .../test/emqx_rule_engine_api_SUITE.erl | 20 ++++ changes/ce/fix-10884.en.md | 1 + 3 files changed, 84 insertions(+), 41 deletions(-) create mode 100644 changes/ce/fix-10884.en.md diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine_api.erl b/apps/emqx_rule_engine/src/emqx_rule_engine_api.erl index 48711d4cc..2583a5d03 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_engine_api.erl +++ b/apps/emqx_rule_engine/src/emqx_rule_engine_api.erl @@ -529,47 +529,69 @@ printable_function_name(Mod, Func) -> list_to_binary(lists:concat([Mod, ":", Func])). get_rule_metrics(Id) -> - Format = fun( - Node, - #{ - counters := - #{ - 'matched' := Matched, - 'passed' := Passed, - 'failed' := Failed, - 'failed.exception' := FailedEx, - 'failed.no_result' := FailedNoRes, - 'actions.total' := OTotal, - 'actions.failed' := OFailed, - 'actions.failed.out_of_service' := OFailedOOS, - 'actions.failed.unknown' := OFailedUnknown, - 'actions.success' := OFailedSucc - }, - rate := - #{ - 'matched' := - #{current := Current, max := Max, last5m := Last5M} - } - } - ) -> - #{ - metrics => ?METRICS( - Matched, - Passed, - Failed, - FailedEx, - FailedNoRes, - OTotal, - OFailed, - OFailedOOS, - OFailedUnknown, - OFailedSucc, - Current, - Max, - Last5M - ), - node => Node - } + Format = fun + ( + Node, + #{ + counters := + #{ + 'matched' := Matched, + 'passed' := Passed, + 'failed' := Failed, + 'failed.exception' := FailedEx, + 'failed.no_result' := FailedNoRes, + 'actions.total' := OTotal, + 'actions.failed' := OFailed, + 'actions.failed.out_of_service' := OFailedOOS, + 'actions.failed.unknown' := OFailedUnknown, + 'actions.success' := OFailedSucc + }, + rate := + #{ + 'matched' := + #{current := Current, max := Max, last5m := Last5M} + } + } + ) -> + #{ + metrics => ?METRICS( + Matched, + Passed, + Failed, + FailedEx, + FailedNoRes, + OTotal, + OFailed, + OFailedOOS, + OFailedUnknown, + OFailedSucc, + Current, + Max, + Last5M + ), + node => Node + }; + (Node, _Metrics) -> + %% Empty metrics: can happen when a node joins another and a bridge is not yet + %% replicated to it, so the counters map is empty. + #{ + metrics => ?METRICS( + _Matched = 0, + _Passed = 0, + _Failed = 0, + _FailedEx = 0, + _FailedNoRes = 0, + _OTotal = 0, + _OFailed = 0, + _OFailedOOS = 0, + _OFailedUnknown = 0, + _OFailedSucc = 0, + _Current = 0, + _Max = 0, + _Last5M = 0 + ), + node => Node + } end, [ Format(Node, emqx_plugin_libs_proto_v1:get_metrics(Node, rule_metrics, Id)) diff --git a/apps/emqx_rule_engine/test/emqx_rule_engine_api_SUITE.erl b/apps/emqx_rule_engine/test/emqx_rule_engine_api_SUITE.erl index ccee05604..e55eea977 100644 --- a/apps/emqx_rule_engine/test/emqx_rule_engine_api_SUITE.erl +++ b/apps/emqx_rule_engine/test/emqx_rule_engine_api_SUITE.erl @@ -94,6 +94,26 @@ t_crud_rule_api(_Config) -> ct:pal("RMetrics : ~p", [Metrics]), ?assertMatch(#{id := RuleId, metrics := _, node_metrics := _}, Metrics), + %% simulating a node joining a cluster and lagging the configuration replication; in + %% such cases, when fetching metrics, a rule may exist in the cluster but not on the + %% new node. We just check that it doesn't provoke a crash. + emqx_common_test_helpers:with_mock( + emqx_metrics_worker, + get_metrics, + fun(HandlerName, MetricId) -> + %% change the metric id to some unknown id. + meck:passthrough([HandlerName, <<"unknown-", MetricId/binary>>]) + end, + fun() -> + {200, Metrics1} = emqx_rule_engine_api:'/rules/:id/metrics'(get, #{ + bindings => #{id => RuleId} + }), + ct:pal("RMetrics : ~p", [Metrics1]), + ?assertMatch(#{id := RuleId, metrics := _, node_metrics := _}, Metrics1), + ok + end + ), + {200, Rule2} = emqx_rule_engine_api:'/rules/:id'(put, #{ bindings => #{id => RuleId}, body => ?SIMPLE_RULE(RuleId)#{<<"sql">> => <<"select * from \"t/b\"">>} diff --git a/changes/ce/fix-10884.en.md b/changes/ce/fix-10884.en.md new file mode 100644 index 000000000..d0848e099 --- /dev/null +++ b/changes/ce/fix-10884.en.md @@ -0,0 +1 @@ +Fixes an issue where trying to get rule info or metrics could result in a crash when a node is joining a cluster. From 7e7b50c5ba3b862834f72e75e375c6b2f0c7d5af Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Mon, 29 May 2023 22:00:38 +0300 Subject: [PATCH 62/72] refactor(mqttbridge): move into separate application --- apps/emqx_bridge/src/emqx_bridge_resource.erl | 8 ++++---- .../src/emqx_bridge_mqtt.app.src | 18 ++++++++++++++++++ .../src/emqx_bridge_mqtt_connector.erl} | 16 ++++++++-------- .../src/emqx_bridge_mqtt_connector_schema.erl} | 10 +++++----- .../src/emqx_bridge_mqtt_egress.erl} | 10 +++++----- .../src/emqx_bridge_mqtt_ingress.erl} | 8 ++++---- .../src/emqx_bridge_mqtt_msg.erl} | 2 +- .../src}/emqx_bridge_mqtt_schema.erl | 2 +- .../test/emqx_bridge_mqtt_SUITE.erl | 11 +++++------ mix.exs | 1 + rebar.config.erl | 1 + ...=> emqx_bridge_mqtt_connector_schema.hocon} | 2 +- 12 files changed, 54 insertions(+), 35 deletions(-) create mode 100644 apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt.app.src rename apps/{emqx_connector/src/emqx_connector_mqtt.erl => emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector.erl} (95%) rename apps/{emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl => emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector_schema.erl} (95%) rename apps/{emqx_connector/src/mqtt/emqx_connector_mqtt_egress.erl => emqx_bridge_mqtt/src/emqx_bridge_mqtt_egress.erl} (94%) rename apps/{emqx_connector/src/mqtt/emqx_connector_mqtt_ingress.erl => emqx_bridge_mqtt/src/emqx_bridge_mqtt_ingress.erl} (97%) rename apps/{emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl => emqx_bridge_mqtt/src/emqx_bridge_mqtt_msg.erl} (98%) rename apps/{emqx_bridge/src/schema => emqx_bridge_mqtt/src}/emqx_bridge_mqtt_schema.erl (97%) rename apps/{emqx_bridge => emqx_bridge_mqtt}/test/emqx_bridge_mqtt_SUITE.erl (99%) rename rel/i18n/{emqx_connector_mqtt_schema.hocon => emqx_bridge_mqtt_connector_schema.hocon} (99%) diff --git a/apps/emqx_bridge/src/emqx_bridge_resource.erl b/apps/emqx_bridge/src/emqx_bridge_resource.erl index fd67c622c..1a3edb484 100644 --- a/apps/emqx_bridge/src/emqx_bridge_resource.erl +++ b/apps/emqx_bridge/src/emqx_bridge_resource.erl @@ -58,14 +58,14 @@ ). -if(?EMQX_RELEASE_EDITION == ee). -bridge_to_resource_type(<<"mqtt">>) -> emqx_connector_mqtt; -bridge_to_resource_type(mqtt) -> emqx_connector_mqtt; +bridge_to_resource_type(<<"mqtt">>) -> emqx_bridge_mqtt_connector; +bridge_to_resource_type(mqtt) -> emqx_bridge_mqtt_connector; bridge_to_resource_type(<<"webhook">>) -> emqx_connector_http; bridge_to_resource_type(webhook) -> emqx_connector_http; bridge_to_resource_type(BridgeType) -> emqx_ee_bridge:resource_type(BridgeType). -else. -bridge_to_resource_type(<<"mqtt">>) -> emqx_connector_mqtt; -bridge_to_resource_type(mqtt) -> emqx_connector_mqtt; +bridge_to_resource_type(<<"mqtt">>) -> emqx_bridge_mqtt_connector; +bridge_to_resource_type(mqtt) -> emqx_bridge_mqtt_connector; bridge_to_resource_type(<<"webhook">>) -> emqx_connector_http; bridge_to_resource_type(webhook) -> emqx_connector_http. -endif. diff --git a/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt.app.src b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt.app.src new file mode 100644 index 000000000..54d7ffbed --- /dev/null +++ b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt.app.src @@ -0,0 +1,18 @@ +%% -*- mode: erlang -*- +{application, emqx_bridge_mqtt, [ + {description, "EMQX MQTT Broker Bridge"}, + {vsn, "0.1.0"}, + {registered, []}, + {applications, [ + kernel, + stdlib, + emqx, + emqx_resource, + emqx_bridge, + emqtt + ]}, + {env, []}, + {modules, []}, + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_connector/src/emqx_connector_mqtt.erl b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector.erl similarity index 95% rename from apps/emqx_connector/src/emqx_connector_mqtt.erl rename to apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector.erl index 255247011..13be52a0c 100644 --- a/apps/emqx_connector/src/emqx_connector_mqtt.erl +++ b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector.erl @@ -13,7 +13,7 @@ %% See the License for the specific language governing permissions and %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_connector_mqtt). +-module(emqx_bridge_mqtt_connector). -include_lib("emqx/include/emqx_mqtt.hrl"). -include_lib("emqx/include/logger.hrl"). @@ -83,7 +83,7 @@ start_ingress(ResourceId, Ingress, ClientOpts) -> {ingress, Ingress}, {client_opts, ClientOpts} ], - case emqx_resource_pool:start(PoolName, emqx_connector_mqtt_ingress, Options) of + case emqx_resource_pool:start(PoolName, emqx_bridge_mqtt_ingress, Options) of ok -> {ok, #{ingress_pool_name => PoolName}}; {error, {start_pool_failed, _, Reason}} -> @@ -128,11 +128,11 @@ start_egress(ResourceId, Egress, ClientOpts) -> {pool_size, PoolSize}, {client_opts, ClientOpts} ], - case emqx_resource_pool:start(PoolName, emqx_connector_mqtt_egress, Options) of + case emqx_resource_pool:start(PoolName, emqx_bridge_mqtt_egress, Options) of ok -> {ok, #{ egress_pool_name => PoolName, - egress_config => emqx_connector_mqtt_egress:config(Egress) + egress_config => emqx_bridge_mqtt_egress:config(Egress) }}; {error, {start_pool_failed, _, Reason}} -> {error, Reason} @@ -197,7 +197,7 @@ on_query_async(ResourceId, {send_message, Msg}, _Callback, #{}) -> }). with_egress_client(ResourceId, Fun, Args) -> - ecpool:pick_and_do(ResourceId, {emqx_connector_mqtt_egress, Fun, Args}, no_handover). + ecpool:pick_and_do(ResourceId, {emqx_bridge_mqtt_egress, Fun, Args}, no_handover). on_async_result(Callback, Result) -> apply_callback_function(Callback, handle_send_result(Result)). @@ -250,9 +250,9 @@ on_get_status(_ResourceId, State) -> get_status({Pool, Worker}) -> case ecpool_worker:client(Worker) of {ok, Client} when Pool == ingress_pool_name -> - emqx_connector_mqtt_ingress:status(Client); + emqx_bridge_mqtt_ingress:status(Client); {ok, Client} when Pool == egress_pool_name -> - emqx_connector_mqtt_egress:status(Client); + emqx_bridge_mqtt_egress:status(Client); {error, _} -> disconnected end. @@ -300,7 +300,7 @@ mk_client_opts( ssl := #{enable := EnableSsl} = Ssl } ) -> - HostPort = emqx_connector_mqtt_schema:parse_server(Server), + HostPort = emqx_bridge_mqtt_connector_schema:parse_server(Server), Options = maps:with( [ proto_ver, diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector_schema.erl similarity index 95% rename from apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl rename to apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector_schema.erl index 6d06029b9..1dc3ca5f8 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl +++ b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector_schema.erl @@ -14,7 +14,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_connector_mqtt_schema). +-module(emqx_bridge_mqtt_connector_schema). -include_lib("typerefl/include/types.hrl"). -include_lib("hocon/include/hoconsc.hrl"). @@ -138,13 +138,13 @@ fields("ingress") -> {remote, mk( ref(?MODULE, "ingress_remote"), - #{desc => ?DESC(emqx_connector_mqtt_schema, "ingress_remote")} + #{desc => ?DESC("ingress_remote")} )}, {local, mk( ref(?MODULE, "ingress_local"), #{ - desc => ?DESC(emqx_connector_mqtt_schema, "ingress_local") + desc => ?DESC("ingress_local") } )} ]; @@ -211,7 +211,7 @@ fields("egress") -> mk( ref(?MODULE, "egress_local"), #{ - desc => ?DESC(emqx_connector_mqtt_schema, "egress_local"), + desc => ?DESC("egress_local"), required => false } )}, @@ -219,7 +219,7 @@ fields("egress") -> mk( ref(?MODULE, "egress_remote"), #{ - desc => ?DESC(emqx_connector_mqtt_schema, "egress_remote"), + desc => ?DESC("egress_remote"), required => true } )} diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_egress.erl b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_egress.erl similarity index 94% rename from apps/emqx_connector/src/mqtt/emqx_connector_mqtt_egress.erl rename to apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_egress.erl index 0e413cbc9..673a30726 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_egress.erl +++ b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_egress.erl @@ -14,7 +14,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_connector_mqtt_egress). +-module(emqx_bridge_mqtt_egress). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx.hrl"). @@ -51,7 +51,7 @@ local => #{ topic => emqx_topic:topic() }, - remote := emqx_connector_mqtt_msg:msgvars() + remote := emqx_bridge_mqtt_msg:msgvars() }. %% @doc Start an ingress bridge worker. @@ -102,7 +102,7 @@ connect(Pid, Name) -> -spec config(map()) -> egress(). config(#{remote := RC = #{}} = Conf) -> - Conf#{remote => emqx_connector_mqtt_msg:parse(RC)}. + Conf#{remote => emqx_bridge_mqtt_msg:parse(RC)}. -spec send(pid(), message(), egress()) -> ok. @@ -118,7 +118,7 @@ send_async(Pid, MsgIn, Callback, Egress) -> export_msg(Msg, #{remote := Remote}) -> to_remote_msg(Msg, Remote). --spec to_remote_msg(message(), emqx_connector_mqtt_msg:msgvars()) -> +-spec to_remote_msg(message(), emqx_bridge_mqtt_msg:msgvars()) -> remote_message(). to_remote_msg(#message{flags = Flags} = Msg, Vars) -> {EventMsg, _} = emqx_rule_events:eventmsg_publish(Msg), @@ -129,7 +129,7 @@ to_remote_msg(Msg = #{}, Remote) -> payload := Payload, qos := QoS, retain := Retain - } = emqx_connector_mqtt_msg:render(Msg, Remote), + } = emqx_bridge_mqtt_msg:render(Msg, Remote), PubProps = maps:get(pub_props, Msg, #{}), #mqtt_msg{ qos = QoS, diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_ingress.erl b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_ingress.erl similarity index 97% rename from apps/emqx_connector/src/mqtt/emqx_connector_mqtt_ingress.erl rename to apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_ingress.erl index c11895c49..78bbf7753 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_ingress.erl +++ b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_ingress.erl @@ -14,7 +14,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_connector_mqtt_ingress). +-module(emqx_bridge_mqtt_ingress). -include_lib("emqx/include/logger.hrl"). @@ -46,7 +46,7 @@ topic := emqx_topic:topic(), qos => emqx_types:qos() }, - local := emqx_connector_mqtt_msg:msgvars(), + local := emqx_bridge_mqtt_msg:msgvars(), on_message_received := {module(), atom(), [term()]} }. @@ -135,7 +135,7 @@ subscribe_remote_topic(Pid, #{remote := #{topic := RemoteTopic, qos := QoS}}) -> config(#{remote := RC, local := LC} = Conf, BridgeName) -> Conf#{ remote => parse_remote(RC, BridgeName), - local => emqx_connector_mqtt_msg:parse(LC) + local => emqx_bridge_mqtt_msg:parse(LC) }. parse_remote(#{qos := QoSIn} = Conf, BridgeName) -> @@ -261,7 +261,7 @@ to_broker_msg(#{dup := Dup} = Msg, Local, Props) -> payload := Payload, qos := QoS, retain := Retain - } = emqx_connector_mqtt_msg:render(Msg, Local), + } = emqx_bridge_mqtt_msg:render(Msg, Local), PubProps = maps:get(pub_props, Msg, #{}), emqx_message:set_headers( Props#{properties => emqx_utils:pub_props_to_packet(PubProps)}, diff --git a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_msg.erl similarity index 98% rename from apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl rename to apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_msg.erl index b57d69df6..8a8cffe55 100644 --- a/apps/emqx_connector/src/mqtt/emqx_connector_mqtt_msg.erl +++ b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_msg.erl @@ -14,7 +14,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_connector_mqtt_msg). +-module(emqx_bridge_mqtt_msg). -export([parse/1]). -export([render/2]). diff --git a/apps/emqx_bridge/src/schema/emqx_bridge_mqtt_schema.erl b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_schema.erl similarity index 97% rename from apps/emqx_bridge/src/schema/emqx_bridge_mqtt_schema.erl rename to apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_schema.erl index 5cd1693c7..a312dfaa9 100644 --- a/apps/emqx_bridge/src/schema/emqx_bridge_mqtt_schema.erl +++ b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_schema.erl @@ -42,7 +42,7 @@ fields("config") -> } )} ] ++ - emqx_connector_mqtt_schema:fields("config"); + emqx_bridge_mqtt_connector_schema:fields("config"); fields("creation_opts") -> Opts = emqx_resource_schema:fields("creation_opts"), [O || {Field, _} = O <- Opts, not is_hidden_opts(Field)]; diff --git a/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl b/apps/emqx_bridge_mqtt/test/emqx_bridge_mqtt_SUITE.erl similarity index 99% rename from apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl rename to apps/emqx_bridge_mqtt/test/emqx_bridge_mqtt_SUITE.erl index 6c36e08e7..81f9a3573 100644 --- a/apps/emqx_bridge/test/emqx_bridge_mqtt_SUITE.erl +++ b/apps/emqx_bridge_mqtt/test/emqx_bridge_mqtt_SUITE.erl @@ -130,13 +130,11 @@ suite() -> init_per_suite(Config) -> _ = application:load(emqx_conf), - %% some testcases (may from other app) already get emqx_connector started - _ = application:stop(emqx_resource), - _ = application:stop(emqx_connector), ok = emqx_common_test_helpers:start_apps( [ emqx_rule_engine, emqx_bridge, + emqx_bridge_mqtt, emqx_dashboard ], fun set_special_configs/1 @@ -150,9 +148,10 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_common_test_helpers:stop_apps([ - emqx_rule_engine, + emqx_dashboard, + emqx_bridge_mqtt, emqx_bridge, - emqx_dashboard + emqx_rule_engine ]), ok. @@ -307,7 +306,7 @@ t_mqtt_egress_bridge_ignores_clean_start(_) -> emqx_resource_manager:lookup_cached(ResourceID), ClientInfo = ecpool:pick_and_do( EgressPoolName, - {emqx_connector_mqtt_egress, info, []}, + {emqx_bridge_mqtt_egress, info, []}, no_handover ), ?assertMatch( diff --git a/mix.exs b/mix.exs index 3fa3cc2bc..7983a7482 100644 --- a/mix.exs +++ b/mix.exs @@ -372,6 +372,7 @@ defmodule EMQXUmbrella.MixProject do emqx_gateway_exproto: :permanent, emqx_exhook: :permanent, emqx_bridge: :permanent, + emqx_bridge_mqtt: :permanent, emqx_rule_engine: :permanent, emqx_modules: :permanent, emqx_management: :permanent, diff --git a/rebar.config.erl b/rebar.config.erl index a8474a703..6c54dffca 100644 --- a/rebar.config.erl +++ b/rebar.config.erl @@ -426,6 +426,7 @@ relx_apps(ReleaseType, Edition) -> emqx_gateway_exproto, emqx_exhook, emqx_bridge, + emqx_bridge_mqtt, emqx_rule_engine, emqx_modules, emqx_management, diff --git a/rel/i18n/emqx_connector_mqtt_schema.hocon b/rel/i18n/emqx_bridge_mqtt_connector_schema.hocon similarity index 99% rename from rel/i18n/emqx_connector_mqtt_schema.hocon rename to rel/i18n/emqx_bridge_mqtt_connector_schema.hocon index 509fc4209..ed7a59dcd 100644 --- a/rel/i18n/emqx_connector_mqtt_schema.hocon +++ b/rel/i18n/emqx_bridge_mqtt_connector_schema.hocon @@ -1,4 +1,4 @@ -emqx_connector_mqtt_schema { +emqx_bridge_mqtt_connector_schema { bridge_mode.desc: """If enable bridge mode. From 1c2719236c58d7502d6847b022c9f986051c7cca Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Mon, 29 May 2023 22:28:31 +0300 Subject: [PATCH 63/72] chore(mqttbridge): add README --- apps/emqx_bridge_mqtt/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 apps/emqx_bridge_mqtt/README.md diff --git a/apps/emqx_bridge_mqtt/README.md b/apps/emqx_bridge_mqtt/README.md new file mode 100644 index 000000000..f286913e7 --- /dev/null +++ b/apps/emqx_bridge_mqtt/README.md @@ -0,0 +1,32 @@ +# EMQX MQTT Broker Bridge + +This application connects EMQX to virtually any MQTT broker adhering to either [MQTTv3][1] or [MQTTv5][2] standard. The connection is facilitated through the _MQTT bridge_ abstraction, allowing for the flow of data in both directions: from the remote broker to EMQX (ingress) and from EMQX to the remote broker (egress). + +User can create a rule and easily ingest into a remote MQTT broker by leveraging [EMQX Rules][3]. + + +# Documentation + +- Refer to [Bridge Data into MQTT Broker][4] for how to use EMQX dashboard to set up ingress or egress bridge, or even both at the same time. + +- Refer to [EMQX Rules][3] for the EMQX rules engine introduction. + + +# HTTP APIs + +Several APIs are provided for bridge management, refer to [API Docs - Bridges](https://docs.emqx.com/en/enterprise/v5.0/admin/api-docs.html#tag/Bridges) for more detailed information. + + +# Contributing + +Please see our [contributing guide](../../CONTRIBUTING.md). + + +# License + +Apache License 2.0, see [LICENSE](../../APL.txt). + +[1]: https://docs.oasis-open.org/mqtt/mqtt/v3.1.1 +[2]: https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html +[3]: https://docs.emqx.com/en/enterprise/v5.0/data-integration/rules.html +[4]: https://www.emqx.io/docs/en/v5.0/data-integration/data-bridge-mqtt.html From 95cc9b9b7239cdf8e089bb41f20ae771119d32ac Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 30 May 2023 17:18:31 +0300 Subject: [PATCH 64/72] fix(mqttbridge): ensure elixir release assembles successfully --- apps/emqx_bridge_mqtt/rebar.config | 3 +++ mix.exs | 1 + 2 files changed, 4 insertions(+) create mode 100644 apps/emqx_bridge_mqtt/rebar.config diff --git a/apps/emqx_bridge_mqtt/rebar.config b/apps/emqx_bridge_mqtt/rebar.config new file mode 100644 index 000000000..35ccc1a37 --- /dev/null +++ b/apps/emqx_bridge_mqtt/rebar.config @@ -0,0 +1,3 @@ +{deps, [ + {emqx, {path, "../../apps/emqx"}} +]}. diff --git a/mix.exs b/mix.exs index 7983a7482..dab11693c 100644 --- a/mix.exs +++ b/mix.exs @@ -310,6 +310,7 @@ defmodule EMQXUmbrella.MixProject do :emqx_connector, :emqx_exhook, :emqx_bridge, + :emqx_bridge_mqtt, :emqx_modules, :emqx_management, :emqx_retainer, From 37b4a44e23af1971ac67bae346bb27a6f8a51f75 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 30 May 2023 13:31:48 -0300 Subject: [PATCH 65/72] test(olp): fix inter-suite flakiness https://github.com/emqx/emqx/actions/runs/5122717037/jobs/9212645120?pr=10883#step:8:2538 ``` emqx_olp_SUITE:t_overload_cooldown_conn failed on line 96 Reason: {assertMatch,[{module,...},{...}|...]} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Testing lib.emqx.emqx_olp_SUITE: *** FAILED test case 3 of 4 *** %%% emqx_olp_SUITE ==> t_overload_cooldown_conn: FAILED %%% emqx_olp_SUITE ==> Failure/Error: ?assertMatch({ ok , _Pid }, emqtt : connect ( C )) expected: = { ok , _Pid } got: {error,econnrefused} line: 96 %%% emqx_olp_SUITE ==> t_overloaded_conn: OK Testing lib.emqx.emqx_olp_SUITE: TEST COMPLETE, 3 ok, 1 failed of 4 test cases ``` ```sh make ct-suite SUITE=apps/emqx/test/emqx_access_control_SUITE.erl,apps/emqx/test/emqx_olp_SUITE.erl ``` --- apps/emqx/test/emqx_olp_SUITE.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/emqx/test/emqx_olp_SUITE.erl b/apps/emqx/test/emqx_olp_SUITE.erl index 87393686e..7b7052ca4 100644 --- a/apps/emqx/test/emqx_olp_SUITE.erl +++ b/apps/emqx/test/emqx_olp_SUITE.erl @@ -28,6 +28,7 @@ all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> + emqx_common_test_helpers:boot_modules(all), emqx_common_test_helpers:start_apps([]), OldSch = erlang:system_flag(schedulers_online, 1), [{old_sch, OldSch} | Config]. From fa9afcd7e4610cac460fe3d27e110209f1f3d722 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 30 May 2023 13:46:04 -0300 Subject: [PATCH 66/72] test(quic_multistreams): attempt to fix flaky tests https://github.com/emqx/emqx/actions/runs/5122717037/jobs/9214950882#step:8:1757 ``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - test_server:ts_tc failed on line 1782 Reason: {test_case_failed,Client <0.6075.5> did not die: stacktrace: {current_stacktrace, [{gen_statem,loop_receive,3, [{file,"gen_statem.erl"}, {line,1281}]}, {proc_lib,init_p_do_apply,3, [{file,"proc_lib.erl"}, {line,240}]}]}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Testing lib.emqx.emqx_quic_multistreams_SUITE: *** FAILED test case 335 of 364 *** %%% emqx_quic_multistreams_SUITE ==> shutdown.abort_recv_shutdown.ctrl_stream_shutdown.t_multi_streams_remote_shutdown: FAILED %%% emqx_quic_multistreams_SUITE ==> {test_case_failed,"Client <0.6075.5> did not die: stacktrace: {current_stacktrace,\n [{gen_statem,loop_receive,3,\n [{file,\"gen_statem.erl\"},\n {line,1281}]},\n {proc_lib,init_p_do_apply,3,\n [{file,\"proc_lib.erl\"},\n {line,240}]}]}"} ``` ``` %%% emqx_quic_multistreams_SUITE ==> mstream.profiles.profile_max_throughput.pub_qos2.sub_qos2.qos.t_multi_streams_unsub: FAILED %%% emqx_quic_multistreams_SUITE ==> {{case_clause, {ok,#{packet_id => 8,properties => #{},reason_code => 0, reason_code_name => success, via => {quic,#Ref<0.686875463.2755788802.201649>, #Ref<0.686875463.2755788804.193527>}}}}, [{emqx_quic_multistreams_SUITE,t_multi_streams_unsub,1, [{file, "/home/thales/dev/emqx/emqx/apps/emqx/test/emqx_quic_multistreams_SUITE.erl"}, {line,1094}]}, {test_server,ts_tc,3,[{file,"test_server.erl"},{line,1783}]}, {test_server,run_test_case_eval1,6,[{file,"test_server.erl"},{line,1292}]}, {test_server,run_test_case_eval,9,[{file,"test_server.erl"},{line,1224}]}]} ``` --- apps/emqx/test/emqx_quic_multistreams_SUITE.erl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/emqx/test/emqx_quic_multistreams_SUITE.erl b/apps/emqx/test/emqx_quic_multistreams_SUITE.erl index b55a28206..1f4ccab88 100644 --- a/apps/emqx/test/emqx_quic_multistreams_SUITE.erl +++ b/apps/emqx/test/emqx_quic_multistreams_SUITE.erl @@ -24,6 +24,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("quicer/include/quicer.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). suite() -> [{timetrap, {seconds, 30}}]. @@ -1089,6 +1090,11 @@ t_multi_streams_unsub(Config) -> ), emqtt:unsubscribe_via(C, SubVia, Topic), + ?retry( + _Sleep2 = 100, + _Attempts2 = 50, + false = emqx_router:has_routes(Topic) + ), case emqtt:publish_via(C, PubVia, Topic, #{}, <<6, 7, 8, 9>>, [{qos, PubQos}]) of ok when PubQos == 0 -> @@ -1569,7 +1575,7 @@ t_multi_streams_remote_shutdown(Config) -> ok = stop_emqx(), %% Client should be closed - assert_client_die(C, 100, 50). + assert_client_die(C, 100, 200). t_multi_streams_remote_shutdown_with_reconnect(Config) -> erlang:process_flag(trap_exit, true), From 98a99d47cb4d768d08f22856e8e8dcc839847c59 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 30 May 2023 14:03:14 -0300 Subject: [PATCH 67/72] test(shared_sub): attempt to reduce test flakiness https://github.com/emqx/emqx/actions/runs/5122717037/jobs/9215439237#step:8:565 ``` %%% emqx_shared_sub_SUITE ==> t_local_fallback: FAILED %%% emqx_shared_sub_SUITE ==> {{badmatch,<<"not yet?">>}, [{emqx_shared_sub_SUITE,t_local_fallback,1, [{file,"/__w/emqx/emqx/source/apps/emqx/test/emqx_shared_sub_SUITE.erl"}, {line,669}]}, ``` --- apps/emqx/test/emqx_shared_sub_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqx/test/emqx_shared_sub_SUITE.erl b/apps/emqx/test/emqx_shared_sub_SUITE.erl index 506e97e4d..e66b42bc8 100644 --- a/apps/emqx/test/emqx_shared_sub_SUITE.erl +++ b/apps/emqx/test/emqx_shared_sub_SUITE.erl @@ -666,7 +666,7 @@ t_local_fallback(Config) when is_list(Config) -> {true, UsedSubPid1} = last_message(<<"hello1">>, [ConnPid1]), rpc:call(Node, emqx, publish, [Message2]), - {true, UsedSubPid2} = last_message(<<"hello2">>, [ConnPid1]), + {true, UsedSubPid2} = last_message(<<"hello2">>, [ConnPid1], 2_000), emqtt:stop(ConnPid1), stop_slave(Node), From 6be8ff378e8a4073d26b8e7d16fab2e001ec6f15 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 30 May 2023 14:41:59 -0300 Subject: [PATCH 68/72] fix(buffer_worker): make buffer worker enter `blocked` state when async worker dies Fixes https://emqx.atlassian.net/browse/EMQX-10074 Otherwise, requests from those async workers, now retriable, might not be retried until the buffer worker blocks for other reasons, which might take a long time. --- .../src/emqx_resource_buffer_worker.erl | 6 +++--- apps/emqx_resource/test/emqx_resource_SUITE.erl | 12 ++++++++++++ changes/ce/fix-10887.en.md | 3 +++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 changes/ce/fix-10887.en.md diff --git a/apps/emqx_resource/src/emqx_resource_buffer_worker.erl b/apps/emqx_resource/src/emqx_resource_buffer_worker.erl index 602551c33..330bf25fd 100644 --- a/apps/emqx_resource/src/emqx_resource_buffer_worker.erl +++ b/apps/emqx_resource/src/emqx_resource_buffer_worker.erl @@ -247,7 +247,7 @@ running(info, Info, _St) -> keep_state_and_data. blocked(enter, _, #{resume_interval := ResumeT} = St0) -> - ?tp(buffer_worker_enter_blocked, #{}), + ?tp(buffer_worker_enter_blocked, #{buffer_worker => self()}), %% discard the old timer, new timer will be started when entering running state again St = cancel_flush_timer(St0), {keep_state, St, {state_timeout, ResumeT, unblock}}; @@ -976,7 +976,7 @@ handle_async_worker_down(Data0, Pid) -> {AsyncWorkerMRef, AsyncWorkers} = maps:take(Pid, AsyncWorkers0), Data = Data0#{async_workers := AsyncWorkers}, mark_inflight_items_as_retriable(Data, AsyncWorkerMRef), - {keep_state, Data}. + {next_state, blocked, Data}. -spec call_query(force_sync | async_if_possible, _, _, _, _, _) -> _. call_query(QM, Id, Index, Ref, Query, QueryOpts) -> @@ -1563,7 +1563,7 @@ mark_inflight_items_as_retriable(Data, AsyncWorkerMRef) -> end ), _NumAffected = ets:select_replace(InflightTID, MatchSpec), - ?tp(buffer_worker_async_agent_down, #{num_affected => _NumAffected}), + ?tp(buffer_worker_async_agent_down, #{num_affected => _NumAffected, buffer_worker => self()}), ok. %% used to update a batch after dropping expired individual queries. diff --git a/apps/emqx_resource/test/emqx_resource_SUITE.erl b/apps/emqx_resource/test/emqx_resource_SUITE.erl index 56d878859..508b8d96b 100644 --- a/apps/emqx_resource/test/emqx_resource_SUITE.erl +++ b/apps/emqx_resource/test/emqx_resource_SUITE.erl @@ -1832,6 +1832,18 @@ t_async_pool_worker_death(_Config) -> NumReqs, lists:sum([N || #{num_affected := N} <- Events]) ), + + %% The `DOWN' signal must trigger the transition to the `blocked' state, + %% otherwise the request won't be retried until the buffer worker is `blocked' + %% for other reasons. + ?assert( + ?strict_causality( + #{?snk_kind := buffer_worker_async_agent_down, buffer_worker := _Pid0}, + #{?snk_kind := buffer_worker_enter_blocked, buffer_worker := _Pid1}, + _Pid0 =:= _Pid1, + Trace + ) + ), ok end ), diff --git a/changes/ce/fix-10887.en.md b/changes/ce/fix-10887.en.md new file mode 100644 index 000000000..5189864a8 --- /dev/null +++ b/changes/ce/fix-10887.en.md @@ -0,0 +1,3 @@ +Fixed a potential issue where requests to bridges might take a long time to be retried. + +This only affected low throughput scenarios, where the buffering layer could take a long time to detect connectivity and driver problems. From 8c565abc840de0b19b53debe3a80a41499c5b795 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 30 May 2023 15:42:53 -0300 Subject: [PATCH 69/72] test(cassandra): fix flaky test --- .../test/emqx_bridge_cassandra_SUITE.erl | 4 +++- apps/emqx_resource/src/emqx_resource_buffer_worker.erl | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/emqx_bridge_cassandra/test/emqx_bridge_cassandra_SUITE.erl b/apps/emqx_bridge_cassandra/test/emqx_bridge_cassandra_SUITE.erl index c74d6cdd1..11014a596 100644 --- a/apps/emqx_bridge_cassandra/test/emqx_bridge_cassandra_SUITE.erl +++ b/apps/emqx_bridge_cassandra/test/emqx_bridge_cassandra_SUITE.erl @@ -541,7 +541,9 @@ t_write_failure(Config) -> end), fun(Trace0) -> ct:pal("trace: ~p", [Trace0]), - Trace = ?of_kind(buffer_worker_flush_nack, Trace0), + Trace = ?of_kind( + [buffer_worker_flush_nack, buffer_worker_retry_inflight_failed], Trace0 + ), [#{result := Result} | _] = Trace, case Result of {async_return, {error, {resource_error, _}}} -> diff --git a/apps/emqx_resource/src/emqx_resource_buffer_worker.erl b/apps/emqx_resource/src/emqx_resource_buffer_worker.erl index 330bf25fd..167dcc02e 100644 --- a/apps/emqx_resource/src/emqx_resource_buffer_worker.erl +++ b/apps/emqx_resource/src/emqx_resource_buffer_worker.erl @@ -403,7 +403,8 @@ retry_inflight_sync(Ref, QueryOrBatch, Data0) -> buffer_worker_retry_inflight_failed, #{ ref => Ref, - query_or_batch => QueryOrBatch + query_or_batch => QueryOrBatch, + result => Result } ), {keep_state, Data1, {state_timeout, ResumeT, unblock}}; From 596f05f981e861455e1c2b140a6530242774f4b4 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 30 May 2023 15:48:48 -0300 Subject: [PATCH 70/72] test(channel): attempt to reduce test flakiness https://github.com/emqx/emqx/actions/runs/5124277038/jobs/9217362541?pr=10886#step:5:1582 ``` %%% emqx_channel_SUITE ==> t_handle_kicked_publish_will_msg: FAILED %%% emqx_channel_SUITE ==> will_message_not_published ``` --- apps/emqx/test/emqx_channel_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqx/test/emqx_channel_SUITE.erl b/apps/emqx/test/emqx_channel_SUITE.erl index 9f73ab163..e77bef90c 100644 --- a/apps/emqx/test/emqx_channel_SUITE.erl +++ b/apps/emqx/test/emqx_channel_SUITE.erl @@ -892,7 +892,7 @@ t_handle_kicked_publish_will_msg(_) -> ), receive {pub, Msg} -> ok - after 1_000 -> exit(will_message_not_published) + after 10_000 -> exit(will_message_not_published) end. t_handle_call_discard(_) -> From 26819a647cd4524defd41d228b9e16aa9ad84eee Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 30 May 2023 18:13:24 +0300 Subject: [PATCH 71/72] fix(mqttbridge): clarify schema descriptions + log messages Co-authored-by: Zaiming (Stone) Shi --- .../src/emqx_bridge_mqtt_connector.erl | 10 +++++++--- .../src/emqx_bridge_mqtt_ingress.erl | 20 ++++++++++--------- .../emqx_bridge_mqtt_connector_schema.hocon | 13 ++++++------ 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector.erl b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector.erl index 13be52a0c..60a512011 100644 --- a/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector.erl +++ b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_connector.erl @@ -76,7 +76,7 @@ start_ingress(ResourceId, Conf) -> start_ingress(ResourceId, Ingress, ClientOpts) -> PoolName = <>, - PoolSize = choose_ingress_pool_size(Ingress), + PoolSize = choose_ingress_pool_size(ResourceId, Ingress), Options = [ {name, PoolName}, {pool_size, PoolSize}, @@ -90,7 +90,10 @@ start_ingress(ResourceId, Ingress, ClientOpts) -> {error, Reason} end. -choose_ingress_pool_size(#{remote := #{topic := RemoteTopic}, pool_size := PoolSize}) -> +choose_ingress_pool_size( + ResourceId, + #{remote := #{topic := RemoteTopic}, pool_size := PoolSize} +) -> case emqx_topic:parse(RemoteTopic) of {_Filter, #{share := _Name}} -> % NOTE: this is shared subscription, many workers may subscribe @@ -98,7 +101,8 @@ choose_ingress_pool_size(#{remote := #{topic := RemoteTopic}, pool_size := PoolS {_Filter, #{}} -> % NOTE: this is regular subscription, only one worker should subscribe ?SLOG(warning, #{ - msg => "ingress_pool_size_ignored", + msg => "mqtt_bridge_ingress_pool_size_ignored", + connector => ResourceId, reason => "Remote topic filter is not a shared subscription, " "ingress pool will start with a single worker", diff --git a/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_ingress.erl b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_ingress.erl index 78bbf7753..91ec27e74 100644 --- a/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_ingress.erl +++ b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_ingress.erl @@ -29,7 +29,7 @@ info/1 ]). --export([handle_publish/4]). +-export([handle_publish/5]). -export([handle_disconnect/1]). -type name() :: term(). @@ -62,7 +62,7 @@ connect(Options) -> WorkerId = proplists:get_value(ecpool_worker_id, Options), Ingress = config(proplists:get_value(ingress, Options), Name), ClientOpts = proplists:get_value(client_opts, Options), - case emqtt:start_link(mk_client_opts(WorkerId, Ingress, ClientOpts)) of + case emqtt:start_link(mk_client_opts(Name, WorkerId, Ingress, ClientOpts)) of {ok, Pid} -> connect(Pid, Name, Ingress); {error, Reason} = Error -> @@ -74,16 +74,16 @@ connect(Options) -> Error end. -mk_client_opts(WorkerId, Ingress, ClientOpts = #{clientid := ClientId}) -> +mk_client_opts(Name, WorkerId, Ingress, ClientOpts = #{clientid := ClientId}) -> ClientOpts#{ clientid := mk_clientid(WorkerId, ClientId), - msg_handler => mk_client_event_handler(Ingress) + msg_handler => mk_client_event_handler(Name, Ingress) }. mk_clientid(WorkerId, ClientId) -> iolist_to_binary([ClientId, $: | integer_to_list(WorkerId)]). -mk_client_event_handler(Ingress = #{}) -> +mk_client_event_handler(Name, Ingress = #{}) -> IngressVars = maps:with([server], Ingress), OnMessage = maps:get(on_message_received, Ingress, undefined), LocalPublish = @@ -94,7 +94,7 @@ mk_client_event_handler(Ingress = #{}) -> undefined end, #{ - publish => {fun ?MODULE:handle_publish/4, [OnMessage, LocalPublish, IngressVars]}, + publish => {fun ?MODULE:handle_publish/5, [Name, OnMessage, LocalPublish, IngressVars]}, disconnected => {fun ?MODULE:handle_disconnect/1, []} }. @@ -110,6 +110,7 @@ connect(Pid, Name, Ingress) -> ?SLOG(error, #{ msg => "ingress_client_subscribe_failed", ingress => Ingress, + name => Name, reason => Reason }), _ = catch emqtt:stop(Pid), @@ -182,11 +183,12 @@ status(Pid) -> %% -handle_publish(#{properties := Props} = MsgIn, OnMessage, LocalPublish, IngressVars) -> +handle_publish(#{properties := Props} = MsgIn, Name, OnMessage, LocalPublish, IngressVars) -> Msg = import_msg(MsgIn, IngressVars), ?SLOG(debug, #{ - msg => "publish_local", - message => Msg + msg => "ingress_publish_local", + message => Msg, + name => Name }), maybe_on_message_received(Msg, OnMessage), maybe_publish_local(Msg, LocalPublish, Props). diff --git a/rel/i18n/emqx_bridge_mqtt_connector_schema.hocon b/rel/i18n/emqx_bridge_mqtt_connector_schema.hocon index ed7a59dcd..7c7bf68c9 100644 --- a/rel/i18n/emqx_bridge_mqtt_connector_schema.hocon +++ b/rel/i18n/emqx_bridge_mqtt_connector_schema.hocon @@ -34,8 +34,8 @@ egress_desc.label: egress_pool_size.desc: """Size of the pool of MQTT clients that will publish messages to the remote broker.
- Each MQTT client will be assigned 'clientid' of the form '${clientid_prefix}:${bridge_name}:egress:${node}:${n}' - where 'n' is the number of a client inside the pool.""" +Each MQTT client will be assigned 'clientid' of the form '${clientid_prefix}:${bridge_name}:egress:${node}:${n}' +where 'n' is the number of a client inside the pool.""" egress_pool_size.label: """Pool Size""" @@ -85,10 +85,11 @@ ingress_desc.label: ingress_pool_size.desc: """Size of the pool of MQTT clients that will ingest messages from the remote broker.
- This value will be respected only if 'remote.topic' is a shared subscription topic filter, - otherwise only a single MQTT client will be used. - Each MQTT client will be assigned 'clientid' of the form '${clientid_prefix}:${bridge_name}:ingress:${node}:${n}' - where 'n' is the number of a client inside the pool.""" +This value will be respected only if 'remote.topic' is a shared subscription topic or topic-filter +(for example `$share/name1/topic1` or `$share/name2/topic2/#`), otherwise only a single MQTT client will be used. +Each MQTT client will be assigned 'clientid' of the form '${clientid_prefix}:${bridge_name}:ingress:${node}:${n}' +where 'n' is the number of a client inside the pool. +NOTE: Non-shared subscription will not work well when EMQX is clustered.""" ingress_pool_size.label: """Pool Size""" From 42a4c0200d0c55ea1244d72c98ff7fa3cd8a8943 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 30 May 2023 22:21:52 +0300 Subject: [PATCH 72/72] chore: add changelog entry --- changes/ce/perf-10754.en.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changes/ce/perf-10754.en.md diff --git a/changes/ce/perf-10754.en.md b/changes/ce/perf-10754.en.md new file mode 100644 index 000000000..ef32960be --- /dev/null +++ b/changes/ce/perf-10754.en.md @@ -0,0 +1,3 @@ +The MQTT bridge has been enhanced to utilize connection pooling and leverage available parallelism, substantially improving throughput. + +As a consequence, single MQTT bridge now uses a pool of `clientid`s to connect to the remote broker.