Merge pull request #10906 from JimMoen/fix-shared-sub-unsub
refacting and small bug fix of share-sub. Part.1 of mqtt client shared sub-unsub.
This commit is contained in:
commit
d27903c1e9
|
@ -13,9 +13,19 @@
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-ifndef(EMQX_CM_HRL).
|
-ifndef(EMQX_CM_HRL).
|
||||||
-define(EMQX_CM_HRL, true).
|
-define(EMQX_CM_HRL, true).
|
||||||
|
|
||||||
|
%% Tables for channel management.
|
||||||
|
-define(CHAN_TAB, emqx_channel).
|
||||||
|
-define(CHAN_CONN_TAB, emqx_channel_conn).
|
||||||
|
-define(CHAN_INFO_TAB, emqx_channel_info).
|
||||||
|
-define(CHAN_LIVE_TAB, emqx_channel_live).
|
||||||
|
|
||||||
|
%% Mria/Mnesia Tables for channel management.
|
||||||
|
-define(CHAN_REG_TAB, emqx_channel_registry).
|
||||||
|
|
||||||
-define(T_KICK, 5_000).
|
-define(T_KICK, 5_000).
|
||||||
-define(T_GET_INFO, 5_000).
|
-define(T_GET_INFO, 5_000).
|
||||||
-define(T_TAKEOVER, 15_000).
|
-define(T_TAKEOVER, 15_000).
|
|
@ -48,6 +48,13 @@
|
||||||
{?MQTT_PROTO_V5, <<"MQTT">>}
|
{?MQTT_PROTO_V5, <<"MQTT">>}
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% MQTT Topic and TopitFilter byte length
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% MQTT-3.1.1 and MQTT-5.0 [MQTT-4.7.3-3]
|
||||||
|
-define(MAX_TOPIC_LEN, 65535).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% MQTT QoS Levels
|
%% MQTT QoS Levels
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -662,6 +669,11 @@ end).
|
||||||
end
|
end
|
||||||
).
|
).
|
||||||
|
|
||||||
|
-define(SHARE_EMPTY_FILTER, share_subscription_topic_cannot_be_empty).
|
||||||
|
-define(SHARE_EMPTY_GROUP, share_subscription_group_name_cannot_be_empty).
|
||||||
|
-define(SHARE_RECURSIVELY, '$share_cannot_be_used_as_real_topic_filter').
|
||||||
|
-define(SHARE_NAME_INVALID_CHAR, share_subscription_group_name_cannot_include_wildcard).
|
||||||
|
|
||||||
-define(FRAME_PARSE_ERROR, frame_parse_error).
|
-define(FRAME_PARSE_ERROR, frame_parse_error).
|
||||||
-define(FRAME_SERIALIZE_ERROR, frame_serialize_error).
|
-define(FRAME_SERIALIZE_ERROR, frame_serialize_error).
|
||||||
-define(THROW_FRAME_ERROR(Reason), erlang:throw({?FRAME_PARSE_ERROR, Reason})).
|
-define(THROW_FRAME_ERROR(Reason), erlang:throw({?FRAME_PARSE_ERROR, Reason})).
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2017-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.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-ifndef(EMQX_ROUTER_HRL).
|
||||||
|
-define(EMQX_ROUTER_HRL, true).
|
||||||
|
|
||||||
|
%% ETS table for message routing
|
||||||
|
-define(ROUTE_TAB, emqx_route).
|
||||||
|
|
||||||
|
%% Mnesia table for message routing
|
||||||
|
-define(ROUTING_NODE, emqx_routing_node).
|
||||||
|
|
||||||
|
%% ETS tables for PubSub
|
||||||
|
-define(SUBOPTION, emqx_suboption).
|
||||||
|
-define(SUBSCRIBER, emqx_subscriber).
|
||||||
|
-define(SUBSCRIPTION, emqx_subscription).
|
||||||
|
|
||||||
|
-endif.
|
|
@ -19,6 +19,8 @@
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
|
-include("emqx_router.hrl").
|
||||||
|
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
-include("types.hrl").
|
-include("types.hrl").
|
||||||
-include("emqx_mqtt.hrl").
|
-include("emqx_mqtt.hrl").
|
||||||
|
@ -80,11 +82,6 @@
|
||||||
|
|
||||||
-define(BROKER, ?MODULE).
|
-define(BROKER, ?MODULE).
|
||||||
|
|
||||||
%% ETS tables for PubSub
|
|
||||||
-define(SUBOPTION, emqx_suboption).
|
|
||||||
-define(SUBSCRIBER, emqx_subscriber).
|
|
||||||
-define(SUBSCRIPTION, emqx_subscription).
|
|
||||||
|
|
||||||
%% Guards
|
%% Guards
|
||||||
-define(IS_SUBID(Id), (is_binary(Id) orelse is_atom(Id))).
|
-define(IS_SUBID(Id), (is_binary(Id) orelse is_atom(Id))).
|
||||||
|
|
||||||
|
@ -106,10 +103,10 @@ start_link(Pool, Id) ->
|
||||||
create_tabs() ->
|
create_tabs() ->
|
||||||
TabOpts = [public, {read_concurrency, true}, {write_concurrency, true}],
|
TabOpts = [public, {read_concurrency, true}, {write_concurrency, true}],
|
||||||
|
|
||||||
%% SubOption: {Topic, SubPid} -> SubOption
|
%% SubOption: {TopicFilter, SubPid} -> SubOption
|
||||||
ok = emqx_utils_ets:new(?SUBOPTION, [ordered_set | TabOpts]),
|
ok = emqx_utils_ets:new(?SUBOPTION, [ordered_set | TabOpts]),
|
||||||
|
|
||||||
%% Subscription: SubPid -> Topic1, Topic2, Topic3, ...
|
%% Subscription: SubPid -> TopicFilter1, TopicFilter2, TopicFilter3, ...
|
||||||
%% duplicate_bag: o(1) insert
|
%% duplicate_bag: o(1) insert
|
||||||
ok = emqx_utils_ets:new(?SUBSCRIPTION, [duplicate_bag | TabOpts]),
|
ok = emqx_utils_ets:new(?SUBSCRIPTION, [duplicate_bag | TabOpts]),
|
||||||
|
|
||||||
|
|
|
@ -484,13 +484,13 @@ handle_in(
|
||||||
{ok, Channel}
|
{ok, Channel}
|
||||||
end;
|
end;
|
||||||
handle_in(
|
handle_in(
|
||||||
Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
|
SubPkt = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
|
||||||
Channel = #channel{clientinfo = ClientInfo}
|
Channel = #channel{clientinfo = ClientInfo}
|
||||||
) ->
|
) ->
|
||||||
case emqx_packet:check(Packet) of
|
case emqx_packet:check(SubPkt) of
|
||||||
ok ->
|
ok ->
|
||||||
TopicFilters0 = parse_topic_filters(TopicFilters),
|
TopicFilters0 = parse_topic_filters(TopicFilters),
|
||||||
TopicFilters1 = put_subid_in_subopts(Properties, TopicFilters0),
|
TopicFilters1 = enrich_subopts_subid(Properties, TopicFilters0),
|
||||||
TupleTopicFilters0 = check_sub_authzs(TopicFilters1, Channel),
|
TupleTopicFilters0 = check_sub_authzs(TopicFilters1, Channel),
|
||||||
HasAuthzDeny = lists:any(
|
HasAuthzDeny = lists:any(
|
||||||
fun({_TopicFilter, ReasonCode}) ->
|
fun({_TopicFilter, ReasonCode}) ->
|
||||||
|
@ -503,7 +503,10 @@ handle_in(
|
||||||
true ->
|
true ->
|
||||||
handle_out(disconnect, ?RC_NOT_AUTHORIZED, Channel);
|
handle_out(disconnect, ?RC_NOT_AUTHORIZED, Channel);
|
||||||
false ->
|
false ->
|
||||||
TopicFilters2 = [TopicFilter || {TopicFilter, 0} <- TupleTopicFilters0],
|
TopicFilters2 = [
|
||||||
|
TopicFilter
|
||||||
|
|| {TopicFilter, ?RC_SUCCESS} <- TupleTopicFilters0
|
||||||
|
],
|
||||||
TopicFilters3 = run_hooks(
|
TopicFilters3 = run_hooks(
|
||||||
'client.subscribe',
|
'client.subscribe',
|
||||||
[ClientInfo, Properties],
|
[ClientInfo, Properties],
|
||||||
|
@ -1866,6 +1869,9 @@ check_pub_caps(
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Check Sub Authorization
|
%% Check Sub Authorization
|
||||||
|
|
||||||
|
%% TODO: not only check topic filter. Qos chould be checked too.
|
||||||
|
%% Not implemented yet:
|
||||||
|
%% MQTT-3.1.1 [MQTT-3.8.4-6] and MQTT-5.0 [MQTT-3.8.4-7]
|
||||||
check_sub_authzs(TopicFilters, Channel) ->
|
check_sub_authzs(TopicFilters, Channel) ->
|
||||||
check_sub_authzs(TopicFilters, Channel, []).
|
check_sub_authzs(TopicFilters, Channel, []).
|
||||||
|
|
||||||
|
@ -1876,7 +1882,7 @@ check_sub_authzs(
|
||||||
) ->
|
) ->
|
||||||
case emqx_access_control:authorize(ClientInfo, subscribe, Topic) of
|
case emqx_access_control:authorize(ClientInfo, subscribe, Topic) of
|
||||||
allow ->
|
allow ->
|
||||||
check_sub_authzs(More, Channel, [{TopicFilter, 0} | Acc]);
|
check_sub_authzs(More, Channel, [{TopicFilter, ?RC_SUCCESS} | Acc]);
|
||||||
deny ->
|
deny ->
|
||||||
check_sub_authzs(More, Channel, [{TopicFilter, ?RC_NOT_AUTHORIZED} | Acc])
|
check_sub_authzs(More, Channel, [{TopicFilter, ?RC_NOT_AUTHORIZED} | Acc])
|
||||||
end;
|
end;
|
||||||
|
@ -1892,9 +1898,9 @@ check_sub_caps(TopicFilter, SubOpts, #channel{clientinfo = ClientInfo}) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Enrich SubId
|
%% Enrich SubId
|
||||||
|
|
||||||
put_subid_in_subopts(#{'Subscription-Identifier' := SubId}, TopicFilters) ->
|
enrich_subopts_subid(#{'Subscription-Identifier' := SubId}, TopicFilters) ->
|
||||||
[{Topic, SubOpts#{subid => SubId}} || {Topic, SubOpts} <- TopicFilters];
|
[{Topic, SubOpts#{subid => SubId}} || {Topic, SubOpts} <- TopicFilters];
|
||||||
put_subid_in_subopts(_Properties, TopicFilters) ->
|
enrich_subopts_subid(_Properties, TopicFilters) ->
|
||||||
TopicFilters.
|
TopicFilters.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
|
-include("emqx_cm.hrl").
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
-include("types.hrl").
|
-include("types.hrl").
|
||||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
@ -118,14 +119,6 @@
|
||||||
_Stats :: emqx_types:stats()
|
_Stats :: emqx_types:stats()
|
||||||
}.
|
}.
|
||||||
|
|
||||||
-include("emqx_cm.hrl").
|
|
||||||
|
|
||||||
%% Tables for channel management.
|
|
||||||
-define(CHAN_TAB, emqx_channel).
|
|
||||||
-define(CHAN_CONN_TAB, emqx_channel_conn).
|
|
||||||
-define(CHAN_INFO_TAB, emqx_channel_info).
|
|
||||||
-define(CHAN_LIVE_TAB, emqx_channel_live).
|
|
||||||
|
|
||||||
-define(CHAN_STATS, [
|
-define(CHAN_STATS, [
|
||||||
{?CHAN_TAB, 'channels.count', 'channels.max'},
|
{?CHAN_TAB, 'channels.count', 'channels.max'},
|
||||||
{?CHAN_TAB, 'sessions.count', 'sessions.max'},
|
{?CHAN_TAB, 'sessions.count', 'sessions.max'},
|
||||||
|
@ -669,12 +662,12 @@ lookup_client({username, Username}) ->
|
||||||
MatchSpec = [
|
MatchSpec = [
|
||||||
{{'_', #{clientinfo => #{username => '$1'}}, '_'}, [{'=:=', '$1', Username}], ['$_']}
|
{{'_', #{clientinfo => #{username => '$1'}}, '_'}, [{'=:=', '$1', Username}], ['$_']}
|
||||||
],
|
],
|
||||||
ets:select(emqx_channel_info, MatchSpec);
|
ets:select(?CHAN_INFO_TAB, MatchSpec);
|
||||||
lookup_client({clientid, ClientId}) ->
|
lookup_client({clientid, ClientId}) ->
|
||||||
[
|
[
|
||||||
Rec
|
Rec
|
||||||
|| Key <- ets:lookup(emqx_channel, ClientId),
|
|| Key <- ets:lookup(?CHAN_TAB, ClientId),
|
||||||
Rec <- ets:lookup(emqx_channel_info, Key)
|
Rec <- ets:lookup(?CHAN_INFO_TAB, Key)
|
||||||
].
|
].
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
|
-include("emqx_cm.hrl").
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
-include("types.hrl").
|
-include("types.hrl").
|
||||||
|
|
||||||
|
@ -50,7 +51,6 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(REGISTRY, ?MODULE).
|
-define(REGISTRY, ?MODULE).
|
||||||
-define(TAB, emqx_channel_registry).
|
|
||||||
-define(LOCK, {?MODULE, cleanup_down}).
|
-define(LOCK, {?MODULE, cleanup_down}).
|
||||||
|
|
||||||
-record(channel, {chid, pid}).
|
-record(channel, {chid, pid}).
|
||||||
|
@ -78,7 +78,7 @@ register_channel(ClientId) when is_binary(ClientId) ->
|
||||||
register_channel({ClientId, self()});
|
register_channel({ClientId, self()});
|
||||||
register_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) ->
|
register_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) ->
|
||||||
case is_enabled() of
|
case is_enabled() of
|
||||||
true -> mria:dirty_write(?TAB, record(ClientId, ChanPid));
|
true -> mria:dirty_write(?CHAN_REG_TAB, record(ClientId, ChanPid));
|
||||||
false -> ok
|
false -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -91,14 +91,14 @@ unregister_channel(ClientId) when is_binary(ClientId) ->
|
||||||
unregister_channel({ClientId, self()});
|
unregister_channel({ClientId, self()});
|
||||||
unregister_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) ->
|
unregister_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) ->
|
||||||
case is_enabled() of
|
case is_enabled() of
|
||||||
true -> mria:dirty_delete_object(?TAB, record(ClientId, ChanPid));
|
true -> mria:dirty_delete_object(?CHAN_REG_TAB, record(ClientId, ChanPid));
|
||||||
false -> ok
|
false -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Lookup the global channels.
|
%% @doc Lookup the global channels.
|
||||||
-spec lookup_channels(emqx_types:clientid()) -> list(pid()).
|
-spec lookup_channels(emqx_types:clientid()) -> list(pid()).
|
||||||
lookup_channels(ClientId) ->
|
lookup_channels(ClientId) ->
|
||||||
[ChanPid || #channel{pid = ChanPid} <- mnesia:dirty_read(?TAB, ClientId)].
|
[ChanPid || #channel{pid = ChanPid} <- mnesia:dirty_read(?CHAN_REG_TAB, ClientId)].
|
||||||
|
|
||||||
record(ClientId, ChanPid) ->
|
record(ClientId, ChanPid) ->
|
||||||
#channel{chid = ClientId, pid = ChanPid}.
|
#channel{chid = ClientId, pid = ChanPid}.
|
||||||
|
@ -109,7 +109,7 @@ record(ClientId, ChanPid) ->
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
mria_config:set_dirty_shard(?CM_SHARD, true),
|
mria_config:set_dirty_shard(?CM_SHARD, true),
|
||||||
ok = mria:create_table(?TAB, [
|
ok = mria:create_table(?CHAN_REG_TAB, [
|
||||||
{type, bag},
|
{type, bag},
|
||||||
{rlog_shard, ?CM_SHARD},
|
{rlog_shard, ?CM_SHARD},
|
||||||
{storage, ram_copies},
|
{storage, ram_copies},
|
||||||
|
@ -166,7 +166,7 @@ cleanup_channels(Node) ->
|
||||||
|
|
||||||
do_cleanup_channels(Node) ->
|
do_cleanup_channels(Node) ->
|
||||||
Pat = [{#channel{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}],
|
Pat = [{#channel{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}],
|
||||||
lists:foreach(fun delete_channel/1, mnesia:select(?TAB, Pat, write)).
|
lists:foreach(fun delete_channel/1, mnesia:select(?CHAN_REG_TAB, Pat, write)).
|
||||||
|
|
||||||
delete_channel(Chan) ->
|
delete_channel(Chan) ->
|
||||||
mnesia:delete_object(?TAB, Chan, write).
|
mnesia:delete_object(?CHAN_REG_TAB, Chan, write).
|
||||||
|
|
|
@ -300,6 +300,7 @@ parse_connect2(
|
||||||
ConnPacket = #mqtt_packet_connect{
|
ConnPacket = #mqtt_packet_connect{
|
||||||
proto_name = ProtoName,
|
proto_name = ProtoName,
|
||||||
proto_ver = ProtoVer,
|
proto_ver = ProtoVer,
|
||||||
|
%% For bridge mode, non-standard implementation
|
||||||
is_bridge = (BridgeTag =:= 8),
|
is_bridge = (BridgeTag =:= 8),
|
||||||
clean_start = bool(CleanStart),
|
clean_start = bool(CleanStart),
|
||||||
will_flag = bool(WillFlag),
|
will_flag = bool(WillFlag),
|
||||||
|
@ -762,6 +763,7 @@ serialize_variable(
|
||||||
#mqtt_packet_connect{
|
#mqtt_packet_connect{
|
||||||
proto_name = ProtoName,
|
proto_name = ProtoName,
|
||||||
proto_ver = ProtoVer,
|
proto_ver = ProtoVer,
|
||||||
|
%% For bridge mode, non-standard implementation
|
||||||
is_bridge = IsBridge,
|
is_bridge = IsBridge,
|
||||||
clean_start = CleanStart,
|
clean_start = CleanStart,
|
||||||
will_flag = WillFlag,
|
will_flag = WillFlag,
|
||||||
|
|
|
@ -75,11 +75,10 @@
|
||||||
|
|
||||||
-export_type([mqueue/0, options/0]).
|
-export_type([mqueue/0, options/0]).
|
||||||
|
|
||||||
-type topic() :: emqx_types:topic().
|
|
||||||
-type priority() :: infinity | integer().
|
-type priority() :: infinity | integer().
|
||||||
-type pq() :: emqx_pqueue:q().
|
-type pq() :: emqx_pqueue:q().
|
||||||
-type count() :: non_neg_integer().
|
-type count() :: non_neg_integer().
|
||||||
-type p_table() :: ?NO_PRIORITY_TABLE | #{topic() := priority()}.
|
-type p_table() :: ?NO_PRIORITY_TABLE | #{emqx_types:topic() := priority()}.
|
||||||
-type options() :: #{
|
-type options() :: #{
|
||||||
max_len := count(),
|
max_len := count(),
|
||||||
priorities => p_table(),
|
priorities => p_table(),
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
-include("types.hrl").
|
-include("types.hrl").
|
||||||
-include_lib("mria/include/mria.hrl").
|
-include_lib("mria/include/mria.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_router.hrl").
|
||||||
|
|
||||||
%% Mnesia bootstrap
|
%% Mnesia bootstrap
|
||||||
-export([mnesia/1]).
|
-export([mnesia/1]).
|
||||||
|
@ -69,8 +70,6 @@
|
||||||
|
|
||||||
-type dest() :: node() | {group(), node()}.
|
-type dest() :: node() | {group(), node()}.
|
||||||
|
|
||||||
-define(ROUTE_TAB, emqx_route).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Mnesia bootstrap
|
%% Mnesia bootstrap
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
|
-include("emqx_router.hrl").
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
-include("types.hrl").
|
-include("types.hrl").
|
||||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
@ -54,8 +55,6 @@
|
||||||
|
|
||||||
-record(routing_node, {name, const = unused}).
|
-record(routing_node, {name, const = unused}).
|
||||||
|
|
||||||
-define(ROUTE, emqx_route).
|
|
||||||
-define(ROUTING_NODE, emqx_routing_node).
|
|
||||||
-define(LOCK, {?MODULE, cleanup_routes}).
|
-define(LOCK, {?MODULE, cleanup_routes}).
|
||||||
|
|
||||||
-dialyzer({nowarn_function, [cleanup_routes/1]}).
|
-dialyzer({nowarn_function, [cleanup_routes/1]}).
|
||||||
|
@ -185,7 +184,7 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
stats_fun() ->
|
stats_fun() ->
|
||||||
case ets:info(?ROUTE, size) of
|
case ets:info(?ROUTE_TAB, size) of
|
||||||
undefined ->
|
undefined ->
|
||||||
ok;
|
ok;
|
||||||
Size ->
|
Size ->
|
||||||
|
@ -198,6 +197,6 @@ cleanup_routes(Node) ->
|
||||||
#route{_ = '_', dest = {'_', Node}}
|
#route{_ = '_', dest = {'_', Node}}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
mnesia:delete_object(?ROUTE, Route, write)
|
mnesia:delete_object(?ROUTE_TAB, Route, write)
|
||||||
|| Pat <- Patterns, Route <- mnesia:match_object(?ROUTE, Pat, write)
|
|| Pat <- Patterns, Route <- mnesia:match_object(?ROUTE_TAB, Pat, write)
|
||||||
].
|
].
|
||||||
|
|
|
@ -57,9 +57,7 @@
|
||||||
code_change/3
|
code_change/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-type group() :: binary().
|
-type dest() :: node() | {emqx_types:group(), node()}.
|
||||||
|
|
||||||
-type dest() :: node() | {group(), node()}.
|
|
||||||
|
|
||||||
-define(ROUTE_RAM_TAB, emqx_session_route_ram).
|
-define(ROUTE_RAM_TAB, emqx_session_route_ram).
|
||||||
-define(ROUTE_DISC_TAB, emqx_session_route_disc).
|
-define(ROUTE_DISC_TAB, emqx_session_route_disc).
|
||||||
|
@ -114,7 +112,7 @@ start_link(Pool, Id) ->
|
||||||
%% Route APIs
|
%% Route APIs
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec do_add_route(emqx_topic:topic(), dest()) -> ok | {error, term()}.
|
-spec do_add_route(emqx_types:topic(), dest()) -> ok | {error, term()}.
|
||||||
do_add_route(Topic, SessionID) when is_binary(Topic) ->
|
do_add_route(Topic, SessionID) when is_binary(Topic) ->
|
||||||
Route = #route{topic = Topic, dest = SessionID},
|
Route = #route{topic = Topic, dest = SessionID},
|
||||||
case lists:member(Route, lookup_routes(Topic)) of
|
case lists:member(Route, lookup_routes(Topic)) of
|
||||||
|
@ -135,7 +133,7 @@ do_add_route(Topic, SessionID) when is_binary(Topic) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Match routes
|
%% @doc Match routes
|
||||||
-spec match_routes(emqx_topic:topic()) -> [emqx_types:route()].
|
-spec match_routes(emqx_types:topic()) -> [emqx_types:route()].
|
||||||
match_routes(Topic) when is_binary(Topic) ->
|
match_routes(Topic) when is_binary(Topic) ->
|
||||||
case match_trie(Topic) of
|
case match_trie(Topic) of
|
||||||
[] -> lookup_routes(Topic);
|
[] -> lookup_routes(Topic);
|
||||||
|
@ -153,7 +151,7 @@ match_trie(Topic) ->
|
||||||
delete_routes(SessionID, Subscriptions) ->
|
delete_routes(SessionID, Subscriptions) ->
|
||||||
cast(pick(SessionID), {delete_routes, SessionID, Subscriptions}).
|
cast(pick(SessionID), {delete_routes, SessionID, Subscriptions}).
|
||||||
|
|
||||||
-spec do_delete_route(emqx_topic:topic(), dest()) -> ok | {error, term()}.
|
-spec do_delete_route(emqx_types:topic(), dest()) -> ok | {error, term()}.
|
||||||
do_delete_route(Topic, SessionID) ->
|
do_delete_route(Topic, SessionID) ->
|
||||||
Route = #route{topic = Topic, dest = SessionID},
|
Route = #route{topic = Topic, dest = SessionID},
|
||||||
case emqx_topic:wildcard(Topic) of
|
case emqx_topic:wildcard(Topic) of
|
||||||
|
@ -165,7 +163,7 @@ do_delete_route(Topic, SessionID) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Print routes to a topic
|
%% @doc Print routes to a topic
|
||||||
-spec print_routes(emqx_topic:topic()) -> ok.
|
-spec print_routes(emqx_types:topic()) -> ok.
|
||||||
print_routes(Topic) ->
|
print_routes(Topic) ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(#route{topic = To, dest = SessionID}) ->
|
fun(#route{topic = To, dest = SessionID}) ->
|
||||||
|
|
|
@ -97,7 +97,7 @@
|
||||||
-define(REDISPATCH_TO(GROUP, TOPIC), {GROUP, TOPIC}).
|
-define(REDISPATCH_TO(GROUP, TOPIC), {GROUP, TOPIC}).
|
||||||
-define(SUBSCRIBER_DOWN, noproc).
|
-define(SUBSCRIBER_DOWN, noproc).
|
||||||
|
|
||||||
-type redispatch_to() :: ?REDISPATCH_TO(emqx_topic:group(), emqx_topic:topic()).
|
-type redispatch_to() :: ?REDISPATCH_TO(emqx_types:group(), emqx_types:topic()).
|
||||||
|
|
||||||
-record(state, {pmon}).
|
-record(state, {pmon}).
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ dispatch(Group, Topic, Delivery = #delivery{message = Msg}, FailedSubs) ->
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec strategy(emqx_topic:group()) -> strategy().
|
-spec strategy(emqx_types:group()) -> strategy().
|
||||||
strategy(Group) ->
|
strategy(Group) ->
|
||||||
try
|
try
|
||||||
emqx:get_config([
|
emqx:get_config([
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
-module(emqx_topic).
|
-module(emqx_topic).
|
||||||
|
|
||||||
|
-include("emqx_mqtt.hrl").
|
||||||
|
|
||||||
%% APIs
|
%% APIs
|
||||||
-export([
|
-export([
|
||||||
match/2,
|
match/2,
|
||||||
|
@ -33,18 +35,14 @@
|
||||||
parse/2
|
parse/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export_type([
|
-type topic() :: emqx_types:topic().
|
||||||
group/0,
|
-type word() :: emqx_types:word().
|
||||||
topic/0,
|
-type words() :: emqx_types:words().
|
||||||
word/0
|
|
||||||
]).
|
|
||||||
|
|
||||||
-type group() :: binary().
|
%% Guards
|
||||||
-type topic() :: binary().
|
-define(MULTI_LEVEL_WILDCARD_NOT_LAST(C, REST),
|
||||||
-type word() :: '' | '+' | '#' | binary().
|
((C =:= '#' orelse C =:= <<"#">>) andalso REST =/= [])
|
||||||
-type words() :: list(word()).
|
).
|
||||||
|
|
||||||
-define(MAX_TOPIC_LEN, 65535).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% APIs
|
%% APIs
|
||||||
|
@ -97,11 +95,15 @@ validate({Type, Topic}) when Type =:= name; Type =:= filter ->
|
||||||
|
|
||||||
-spec validate(name | filter, topic()) -> true.
|
-spec validate(name | filter, topic()) -> true.
|
||||||
validate(_, <<>>) ->
|
validate(_, <<>>) ->
|
||||||
|
%% MQTT-5.0 [MQTT-4.7.3-1]
|
||||||
error(empty_topic);
|
error(empty_topic);
|
||||||
validate(_, Topic) when is_binary(Topic) andalso (size(Topic) > ?MAX_TOPIC_LEN) ->
|
validate(_, Topic) when is_binary(Topic) andalso (size(Topic) > ?MAX_TOPIC_LEN) ->
|
||||||
|
%% MQTT-5.0 [MQTT-4.7.3-3]
|
||||||
error(topic_too_long);
|
error(topic_too_long);
|
||||||
validate(filter, Topic) when is_binary(Topic) ->
|
validate(filter, SharedFilter = <<"$share/", _Rest/binary>>) ->
|
||||||
validate2(words(Topic));
|
validate_share(SharedFilter);
|
||||||
|
validate(filter, Filter) when is_binary(Filter) ->
|
||||||
|
validate2(words(Filter));
|
||||||
validate(name, Topic) when is_binary(Topic) ->
|
validate(name, Topic) when is_binary(Topic) ->
|
||||||
Words = words(Topic),
|
Words = words(Topic),
|
||||||
validate2(Words) andalso
|
validate2(Words) andalso
|
||||||
|
@ -113,7 +115,8 @@ validate2([]) ->
|
||||||
% end with '#'
|
% end with '#'
|
||||||
validate2(['#']) ->
|
validate2(['#']) ->
|
||||||
true;
|
true;
|
||||||
validate2(['#' | Words]) when length(Words) > 0 ->
|
%% MQTT-5.0 [MQTT-4.7.1-1]
|
||||||
|
validate2([C | Words]) when ?MULTI_LEVEL_WILDCARD_NOT_LAST(C, Words) ->
|
||||||
error('topic_invalid_#');
|
error('topic_invalid_#');
|
||||||
validate2(['' | Words]) ->
|
validate2(['' | Words]) ->
|
||||||
validate2(Words);
|
validate2(Words);
|
||||||
|
@ -129,6 +132,32 @@ validate3(<<C/utf8, _Rest/binary>>) when C == $#; C == $+; C == 0 ->
|
||||||
validate3(<<_/utf8, Rest/binary>>) ->
|
validate3(<<_/utf8, Rest/binary>>) ->
|
||||||
validate3(Rest).
|
validate3(Rest).
|
||||||
|
|
||||||
|
validate_share(<<"$share/", Rest/binary>>) when
|
||||||
|
Rest =:= <<>> orelse Rest =:= <<"/">>
|
||||||
|
->
|
||||||
|
%% MQTT-5.0 [MQTT-4.8.2-1]
|
||||||
|
error(?SHARE_EMPTY_FILTER);
|
||||||
|
validate_share(<<"$share/", Rest/binary>>) ->
|
||||||
|
case binary:split(Rest, <<"/">>) of
|
||||||
|
%% MQTT-5.0 [MQTT-4.8.2-1]
|
||||||
|
[<<>>, _] ->
|
||||||
|
error(?SHARE_EMPTY_GROUP);
|
||||||
|
%% MQTT-5.0 [MQTT-4.7.3-1]
|
||||||
|
[_, <<>>] ->
|
||||||
|
error(?SHARE_EMPTY_FILTER);
|
||||||
|
[ShareName, Filter] ->
|
||||||
|
validate_share(ShareName, Filter)
|
||||||
|
end.
|
||||||
|
|
||||||
|
validate_share(_, <<"$share/", _Rest/binary>>) ->
|
||||||
|
error(?SHARE_RECURSIVELY);
|
||||||
|
validate_share(ShareName, Filter) ->
|
||||||
|
case binary:match(ShareName, [<<"+">>, <<"#">>]) of
|
||||||
|
%% MQTT-5.0 [MQTT-4.8.2-2]
|
||||||
|
nomatch -> validate2(words(Filter));
|
||||||
|
_ -> error(?SHARE_NAME_INVALID_CHAR)
|
||||||
|
end.
|
||||||
|
|
||||||
%% @doc Prepend a topic prefix.
|
%% @doc Prepend a topic prefix.
|
||||||
%% Ensured to have only one / between prefix and suffix.
|
%% Ensured to have only one / between prefix and suffix.
|
||||||
prepend(undefined, W) ->
|
prepend(undefined, W) ->
|
||||||
|
@ -142,6 +171,7 @@ prepend(Parent0, W) ->
|
||||||
_ -> <<Parent/binary, $/, (bin(W))/binary>>
|
_ -> <<Parent/binary, $/, (bin(W))/binary>>
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec bin(word()) -> binary().
|
||||||
bin('') -> <<>>;
|
bin('') -> <<>>;
|
||||||
bin('+') -> <<"+">>;
|
bin('+') -> <<"+">>;
|
||||||
bin('#') -> <<"#">>;
|
bin('#') -> <<"#">>;
|
||||||
|
@ -163,6 +193,7 @@ tokens(Topic) ->
|
||||||
words(Topic) when is_binary(Topic) ->
|
words(Topic) when is_binary(Topic) ->
|
||||||
[word(W) || W <- tokens(Topic)].
|
[word(W) || W <- tokens(Topic)].
|
||||||
|
|
||||||
|
-spec word(binary()) -> word().
|
||||||
word(<<>>) -> '';
|
word(<<>>) -> '';
|
||||||
word(<<"+">>) -> '+';
|
word(<<"+">>) -> '+';
|
||||||
word(<<"#">>) -> '#';
|
word(<<"#">>) -> '#';
|
||||||
|
@ -185,23 +216,19 @@ feed_var(Var, Val, [Var | Words], Acc) ->
|
||||||
feed_var(Var, Val, [W | Words], Acc) ->
|
feed_var(Var, Val, [W | Words], Acc) ->
|
||||||
feed_var(Var, Val, Words, [W | Acc]).
|
feed_var(Var, Val, Words, [W | Acc]).
|
||||||
|
|
||||||
-spec join(list(binary())) -> binary().
|
-spec join(list(word())) -> binary().
|
||||||
join([]) ->
|
join([]) ->
|
||||||
<<>>;
|
<<>>;
|
||||||
join([W]) ->
|
join([Word | Words]) ->
|
||||||
bin(W);
|
do_join(bin(Word), Words).
|
||||||
join(Words) ->
|
|
||||||
{_, Bin} = lists:foldr(
|
do_join(TopicAcc, []) ->
|
||||||
fun
|
TopicAcc;
|
||||||
(W, {true, Tail}) ->
|
%% MQTT-5.0 [MQTT-4.7.1-1]
|
||||||
{false, <<W/binary, Tail/binary>>};
|
do_join(_TopicAcc, [C | Words]) when ?MULTI_LEVEL_WILDCARD_NOT_LAST(C, Words) ->
|
||||||
(W, {false, Tail}) ->
|
error('topic_invalid_#');
|
||||||
{false, <<W/binary, "/", Tail/binary>>}
|
do_join(TopicAcc, [Word | Words]) ->
|
||||||
end,
|
do_join(<<TopicAcc/binary, "/", (bin(Word))/binary>>, Words).
|
||||||
{true, <<>>},
|
|
||||||
[bin(W) || W <- Words]
|
|
||||||
),
|
|
||||||
Bin.
|
|
||||||
|
|
||||||
-spec parse(topic() | {topic(), map()}) -> {topic(), #{share => binary()}}.
|
-spec parse(topic() | {topic(), map()}) -> {topic(), #{share => binary()}}.
|
||||||
parse(TopicFilter) when is_binary(TopicFilter) ->
|
parse(TopicFilter) when is_binary(TopicFilter) ->
|
||||||
|
|
|
@ -114,7 +114,7 @@ create_session_trie(Type) ->
|
||||||
insert(Topic) when is_binary(Topic) ->
|
insert(Topic) when is_binary(Topic) ->
|
||||||
insert(Topic, ?TRIE).
|
insert(Topic, ?TRIE).
|
||||||
|
|
||||||
-spec insert_session(emqx_topic:topic()) -> ok.
|
-spec insert_session(emqx_types:topic()) -> ok.
|
||||||
insert_session(Topic) when is_binary(Topic) ->
|
insert_session(Topic) when is_binary(Topic) ->
|
||||||
insert(Topic, session_trie()).
|
insert(Topic, session_trie()).
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ delete(Topic) when is_binary(Topic) ->
|
||||||
delete(Topic, ?TRIE).
|
delete(Topic, ?TRIE).
|
||||||
|
|
||||||
%% @doc Delete a topic filter from the trie.
|
%% @doc Delete a topic filter from the trie.
|
||||||
-spec delete_session(emqx_topic:topic()) -> ok.
|
-spec delete_session(emqx_types:topic()) -> ok.
|
||||||
delete_session(Topic) when is_binary(Topic) ->
|
delete_session(Topic) when is_binary(Topic) ->
|
||||||
delete(Topic, session_trie()).
|
delete(Topic, session_trie()).
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ delete(Topic, Trie) when is_binary(Topic) ->
|
||||||
match(Topic) when is_binary(Topic) ->
|
match(Topic) when is_binary(Topic) ->
|
||||||
match(Topic, ?TRIE).
|
match(Topic, ?TRIE).
|
||||||
|
|
||||||
-spec match_session(emqx_topic:topic()) -> list(emqx_topic:topic()).
|
-spec match_session(emqx_types:topic()) -> list(emqx_types:topic()).
|
||||||
match_session(Topic) when is_binary(Topic) ->
|
match_session(Topic) when is_binary(Topic) ->
|
||||||
match(Topic, session_trie()).
|
match(Topic, session_trie()).
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,16 @@
|
||||||
-export_type([
|
-export_type([
|
||||||
zone/0,
|
zone/0,
|
||||||
pubsub/0,
|
pubsub/0,
|
||||||
topic/0,
|
|
||||||
subid/0
|
subid/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export_type([
|
||||||
|
group/0,
|
||||||
|
topic/0,
|
||||||
|
word/0,
|
||||||
|
words/0
|
||||||
|
]).
|
||||||
|
|
||||||
-export_type([
|
-export_type([
|
||||||
socktype/0,
|
socktype/0,
|
||||||
sockstate/0,
|
sockstate/0,
|
||||||
|
@ -122,9 +128,13 @@
|
||||||
|
|
||||||
-type zone() :: atom().
|
-type zone() :: atom().
|
||||||
-type pubsub() :: publish | subscribe.
|
-type pubsub() :: publish | subscribe.
|
||||||
-type topic() :: emqx_topic:topic().
|
|
||||||
-type subid() :: binary() | atom().
|
-type subid() :: binary() | atom().
|
||||||
|
|
||||||
|
-type group() :: binary() | undefined.
|
||||||
|
-type topic() :: binary().
|
||||||
|
-type word() :: '' | '+' | '#' | binary().
|
||||||
|
-type words() :: list(word()).
|
||||||
|
|
||||||
-type socktype() :: tcp | udp | ssl | proxy | atom().
|
-type socktype() :: tcp | udp | ssl | proxy | atom().
|
||||||
-type sockstate() :: idle | running | blocked | closed.
|
-type sockstate() :: idle | running | blocked | closed.
|
||||||
-type conninfo() :: #{
|
-type conninfo() :: #{
|
||||||
|
@ -230,7 +240,6 @@
|
||||||
| {share, topic(), deliver_result()}
|
| {share, topic(), deliver_result()}
|
||||||
].
|
].
|
||||||
-type route() :: #route{}.
|
-type route() :: #route{}.
|
||||||
-type group() :: emqx_topic:group().
|
|
||||||
-type route_entry() :: {topic(), node()} | {topic, group()}.
|
-type route_entry() :: {topic(), node()} | {topic, group()}.
|
||||||
-type command() :: #command{}.
|
-type command() :: #command{}.
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
-module(emqx_ws_connection).
|
-module(emqx_ws_connection).
|
||||||
|
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
|
-include("emqx_cm.hrl").
|
||||||
-include("emqx_mqtt.hrl").
|
-include("emqx_mqtt.hrl").
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
-include("types.hrl").
|
-include("types.hrl").
|
||||||
|
@ -1034,7 +1035,7 @@ check_max_connection(Type, Listener) ->
|
||||||
allow;
|
allow;
|
||||||
Max ->
|
Max ->
|
||||||
MatchSpec = [{{'_', emqx_ws_connection}, [], [true]}],
|
MatchSpec = [{{'_', emqx_ws_connection}, [], [true]}],
|
||||||
Curr = ets:select_count(emqx_channel_conn, MatchSpec),
|
Curr = ets:select_count(?CHAN_CONN_TAB, MatchSpec),
|
||||||
case Curr >= Max of
|
case Curr >= Max of
|
||||||
false ->
|
false ->
|
||||||
allow;
|
allow;
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include("bpapi.hrl").
|
-include("bpapi.hrl").
|
||||||
-include("src/emqx_cm.hrl").
|
-include_lib("emqx/include/emqx_cm.hrl").
|
||||||
|
|
||||||
introduced_in() ->
|
introduced_in() ->
|
||||||
"5.0.0".
|
"5.0.0".
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include("bpapi.hrl").
|
-include("bpapi.hrl").
|
||||||
-include("src/emqx_cm.hrl").
|
-include_lib("emqx/include/emqx_cm.hrl").
|
||||||
|
|
||||||
introduced_in() ->
|
introduced_in() ->
|
||||||
"5.0.0".
|
"5.0.0".
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_cm.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
|
||||||
|
@ -200,10 +201,10 @@ t_open_session_race_condition(_) ->
|
||||||
end,
|
end,
|
||||||
Winner = WaitForDowns(Pids),
|
Winner = WaitForDowns(Pids),
|
||||||
|
|
||||||
?assertMatch([_], ets:lookup(emqx_channel, ClientId)),
|
?assertMatch([_], ets:lookup(?CHAN_TAB, ClientId)),
|
||||||
?assertEqual([Winner], emqx_cm:lookup_channels(ClientId)),
|
?assertEqual([Winner], emqx_cm:lookup_channels(ClientId)),
|
||||||
?assertMatch([_], ets:lookup(emqx_channel_conn, {ClientId, Winner})),
|
?assertMatch([_], ets:lookup(?CHAN_CONN_TAB, {ClientId, Winner})),
|
||||||
?assertMatch([_], ets:lookup(emqx_channel_registry, ClientId)),
|
?assertMatch([_], ets:lookup(?CHAN_REG_TAB, ClientId)),
|
||||||
|
|
||||||
exit(Winner, kill),
|
exit(Winner, kill),
|
||||||
receive
|
receive
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
-include_lib("quicer/include/quicer.hrl").
|
-include_lib("quicer/include/quicer.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_cm.hrl").
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
|
||||||
|
@ -1465,7 +1466,7 @@ t_multi_streams_emqx_ctrl_kill(Config) ->
|
||||||
),
|
),
|
||||||
|
|
||||||
ClientId = proplists:get_value(clientid, emqtt:info(C)),
|
ClientId = proplists:get_value(clientid, emqtt:info(C)),
|
||||||
[{ClientId, TransPid}] = ets:lookup(emqx_channel, ClientId),
|
[{ClientId, TransPid}] = ets:lookup(?CHAN_TAB, ClientId),
|
||||||
exit(TransPid, kill),
|
exit(TransPid, kill),
|
||||||
|
|
||||||
%% Client should be closed
|
%% Client should be closed
|
||||||
|
@ -1518,7 +1519,7 @@ t_multi_streams_emqx_ctrl_exit_normal(Config) ->
|
||||||
),
|
),
|
||||||
|
|
||||||
ClientId = proplists:get_value(clientid, emqtt:info(C)),
|
ClientId = proplists:get_value(clientid, emqtt:info(C)),
|
||||||
[{ClientId, TransPid}] = ets:lookup(emqx_channel, ClientId),
|
[{ClientId, TransPid}] = ets:lookup(?CHAN_TAB, ClientId),
|
||||||
|
|
||||||
emqx_connection:stop(TransPid),
|
emqx_connection:stop(TransPid),
|
||||||
%% Client exit normal.
|
%% Client exit normal.
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_router.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
|
||||||
|
@ -127,5 +128,5 @@ t_unexpected(_) ->
|
||||||
clear_tables() ->
|
clear_tables() ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun mnesia:clear_table/1,
|
fun mnesia:clear_table/1,
|
||||||
[emqx_route, emqx_trie, emqx_trie_node]
|
[?ROUTE_TAB, ?TRIE, emqx_trie_node]
|
||||||
).
|
).
|
||||||
|
|
|
@ -20,11 +20,11 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_router.hrl").
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
|
||||||
-define(ROUTER_HELPER, emqx_router_helper).
|
-define(ROUTER_HELPER, emqx_router_helper).
|
||||||
-define(ROUTE_TAB, emqx_route).
|
|
||||||
|
|
||||||
all() -> emqx_common_test_helpers:all(?MODULE).
|
all() -> emqx_common_test_helpers:all(?MODULE).
|
||||||
|
|
||||||
|
@ -82,9 +82,9 @@ t_monitor(_) ->
|
||||||
emqx_router_helper:monitor(undefined).
|
emqx_router_helper:monitor(undefined).
|
||||||
|
|
||||||
t_mnesia(_) ->
|
t_mnesia(_) ->
|
||||||
?ROUTER_HELPER ! {mnesia_table_event, {delete, {emqx_routing_node, node()}, undefined}},
|
?ROUTER_HELPER ! {mnesia_table_event, {delete, {?ROUTING_NODE, node()}, undefined}},
|
||||||
?ROUTER_HELPER ! {mnesia_table_event, testing},
|
?ROUTER_HELPER ! {mnesia_table_event, testing},
|
||||||
?ROUTER_HELPER ! {mnesia_table_event, {write, {emqx_routing_node, node()}, undefined}},
|
?ROUTER_HELPER ! {mnesia_table_event, {write, {?ROUTING_NODE, node()}, undefined}},
|
||||||
?ROUTER_HELPER ! {membership, testing},
|
?ROUTER_HELPER ! {membership, testing},
|
||||||
?ROUTER_HELPER ! {membership, {mnesia, down, node()}},
|
?ROUTER_HELPER ! {membership, {mnesia, down, node()}},
|
||||||
ct:sleep(200).
|
ct:sleep(200).
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_cm.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
@ -117,7 +118,7 @@ load_meck(ClientId) ->
|
||||||
[ChanPid] = emqx_cm:lookup_channels(ClientId),
|
[ChanPid] = emqx_cm:lookup_channels(ClientId),
|
||||||
ChanInfo = #{conninfo := ConnInfo} = emqx_cm:get_chan_info(ClientId),
|
ChanInfo = #{conninfo := ConnInfo} = emqx_cm:get_chan_info(ClientId),
|
||||||
NChanInfo = ChanInfo#{conninfo := ConnInfo#{conn_mod := fake_conn_mod}},
|
NChanInfo = ChanInfo#{conninfo := ConnInfo#{conn_mod := fake_conn_mod}},
|
||||||
true = ets:update_element(emqx_channel_info, {ClientId, ChanPid}, {2, NChanInfo}).
|
true = ets:update_element(?CHAN_INFO_TAB, {ClientId, ChanPid}, {2, NChanInfo}).
|
||||||
|
|
||||||
unload_meck(_ClientId) ->
|
unload_meck(_ClientId) ->
|
||||||
meck:unload(fake_conn_mod).
|
meck:unload(fake_conn_mod).
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||||
-include_lib("emqx/include/emqx_placeholder.hrl").
|
-include_lib("emqx/include/emqx_placeholder.hrl").
|
||||||
|
|
||||||
-import(
|
-import(
|
||||||
|
@ -130,14 +131,35 @@ t_validate(_) ->
|
||||||
true = validate({filter, <<"x">>}),
|
true = validate({filter, <<"x">>}),
|
||||||
true = validate({name, <<"x//y">>}),
|
true = validate({name, <<"x//y">>}),
|
||||||
true = validate({filter, <<"sport/tennis/#">>}),
|
true = validate({filter, <<"sport/tennis/#">>}),
|
||||||
|
%% MQTT-5.0 [MQTT-4.7.3-1]
|
||||||
?assertError(empty_topic, validate({name, <<>>})),
|
?assertError(empty_topic, validate({name, <<>>})),
|
||||||
|
?assertError(empty_topic, validate({filter, <<>>})),
|
||||||
?assertError(topic_name_error, validate({name, <<"abc/#">>})),
|
?assertError(topic_name_error, validate({name, <<"abc/#">>})),
|
||||||
?assertError(topic_too_long, validate({name, long_topic()})),
|
?assertError(topic_too_long, validate({name, long_topic()})),
|
||||||
?assertError('topic_invalid_#', validate({filter, <<"abc/#/1">>})),
|
|
||||||
?assertError(topic_invalid_char, validate({filter, <<"abc/#xzy/+">>})),
|
?assertError(topic_invalid_char, validate({filter, <<"abc/#xzy/+">>})),
|
||||||
?assertError(topic_invalid_char, validate({filter, <<"abc/xzy/+9827">>})),
|
?assertError(topic_invalid_char, validate({filter, <<"abc/xzy/+9827">>})),
|
||||||
?assertError(topic_invalid_char, validate({filter, <<"sport/tennis#">>})),
|
?assertError(topic_invalid_char, validate({filter, <<"sport/tennis#">>})),
|
||||||
?assertError('topic_invalid_#', validate({filter, <<"sport/tennis/#/ranking">>})).
|
%% MQTT-5.0 [MQTT-4.7.1-1]
|
||||||
|
?assertError('topic_invalid_#', validate({filter, <<"abc/#/1">>})),
|
||||||
|
?assertError('topic_invalid_#', validate({filter, <<"sport/tennis/#/ranking">>})),
|
||||||
|
%% MQTT-5.0 [MQTT-4.8.2-1]
|
||||||
|
?assertError(?SHARE_EMPTY_FILTER, validate({filter, <<"$share/">>})),
|
||||||
|
?assertError(?SHARE_EMPTY_FILTER, validate({filter, <<"$share//">>})),
|
||||||
|
?assertError(?SHARE_EMPTY_GROUP, validate({filter, <<"$share//t">>})),
|
||||||
|
?assertError(?SHARE_EMPTY_GROUP, validate({filter, <<"$share//test">>})),
|
||||||
|
%% MQTT-5.0 [MQTT-4.7.3-1] for shared-sub
|
||||||
|
?assertError(?SHARE_EMPTY_FILTER, validate({filter, <<"$share/g/">>})),
|
||||||
|
?assertError(?SHARE_EMPTY_FILTER, validate({filter, <<"$share/g2/">>})),
|
||||||
|
%% MQTT-5.0 [MQTT-4.8.2-2]
|
||||||
|
?assertError(?SHARE_NAME_INVALID_CHAR, validate({filter, <<"$share/p+q/1">>})),
|
||||||
|
?assertError(?SHARE_NAME_INVALID_CHAR, validate({filter, <<"$share/m+/1">>})),
|
||||||
|
?assertError(?SHARE_NAME_INVALID_CHAR, validate({filter, <<"$share/+n/1">>})),
|
||||||
|
?assertError(?SHARE_NAME_INVALID_CHAR, validate({filter, <<"$share/x#y/1">>})),
|
||||||
|
?assertError(?SHARE_NAME_INVALID_CHAR, validate({filter, <<"$share/x#/1">>})),
|
||||||
|
?assertError(?SHARE_NAME_INVALID_CHAR, validate({filter, <<"$share/#y/1">>})),
|
||||||
|
%% share recursively
|
||||||
|
?assertError(?SHARE_RECURSIVELY, validate({filter, <<"$share/g1/$share/t">>})),
|
||||||
|
true = validate({filter, <<"$share/g1/topic/$share">>}).
|
||||||
|
|
||||||
t_sigle_level_validate(_) ->
|
t_sigle_level_validate(_) ->
|
||||||
true = validate({filter, <<"+">>}),
|
true = validate({filter, <<"+">>}),
|
||||||
|
@ -177,7 +199,10 @@ t_join(_) ->
|
||||||
?assertEqual(<<"+//#">>, join(['+', '', '#'])),
|
?assertEqual(<<"+//#">>, join(['+', '', '#'])),
|
||||||
?assertEqual(<<"x/y/z/+">>, join([<<"x">>, <<"y">>, <<"z">>, '+'])),
|
?assertEqual(<<"x/y/z/+">>, join([<<"x">>, <<"y">>, <<"z">>, '+'])),
|
||||||
?assertEqual(<<"/ab/cd/ef/">>, join(words(<<"/ab/cd/ef/">>))),
|
?assertEqual(<<"/ab/cd/ef/">>, join(words(<<"/ab/cd/ef/">>))),
|
||||||
?assertEqual(<<"ab/+/#">>, join(words(<<"ab/+/#">>))).
|
?assertEqual(<<"ab/+/#">>, join(words(<<"ab/+/#">>))),
|
||||||
|
%% MQTT-5.0 [MQTT-4.7.1-1]
|
||||||
|
?assertError('topic_invalid_#', join(['+', <<"a">>, '#', <<"b">>, '', '+'])),
|
||||||
|
?assertError('topic_invalid_#', join(['+', <<"c">>, <<"#">>, <<"d">>, '', '+'])).
|
||||||
|
|
||||||
t_systop(_) ->
|
t_systop(_) ->
|
||||||
SysTop1 = iolist_to_binary(["$SYS/brokers/", atom_to_list(node()), "/xyz"]),
|
SysTop1 = iolist_to_binary(["$SYS/brokers/", atom_to_list(node()), "/xyz"]),
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
-type clientid() :: {clientid, binary()}.
|
-type clientid() :: {clientid, binary()}.
|
||||||
-type who() :: username() | clientid() | all.
|
-type who() :: username() | clientid() | all.
|
||||||
|
|
||||||
-type rule() :: {emqx_authz_rule:permission(), emqx_authz_rule:action(), emqx_topic:topic()}.
|
-type rule() :: {emqx_authz_rule:permission(), emqx_authz_rule:action(), emqx_types:topic()}.
|
||||||
-type rules() :: [rule()].
|
-type rules() :: [rule()].
|
||||||
|
|
||||||
-record(emqx_acl, {
|
-record(emqx_acl, {
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
|
|
||||||
-type egress() :: #{
|
-type egress() :: #{
|
||||||
local => #{
|
local => #{
|
||||||
topic => emqx_topic:topic()
|
topic => emqx_types:topic()
|
||||||
},
|
},
|
||||||
remote := emqx_bridge_mqtt_msg:msgvars()
|
remote := emqx_bridge_mqtt_msg:msgvars()
|
||||||
}.
|
}.
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
-type ingress() :: #{
|
-type ingress() :: #{
|
||||||
server := string(),
|
server := string(),
|
||||||
remote := #{
|
remote := #{
|
||||||
topic := emqx_topic:topic(),
|
topic := emqx_types:topic(),
|
||||||
qos => emqx_types:qos()
|
qos => emqx_types:qos()
|
||||||
},
|
},
|
||||||
local := emqx_bridge_mqtt_msg:msgvars(),
|
local := emqx_bridge_mqtt_msg:msgvars(),
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||||
-include_lib("emqx/include/asserts.hrl").
|
-include_lib("emqx/include/asserts.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_cm.hrl").
|
||||||
|
|
||||||
-import(
|
-import(
|
||||||
emqx_eviction_agent_test_helpers,
|
emqx_eviction_agent_test_helpers,
|
||||||
|
@ -295,7 +296,7 @@ t_session_serialization(_Config) ->
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
#{data := [#{clientid := <<"client_with_session">>}]},
|
#{data := [#{clientid := <<"client_with_session">>}]},
|
||||||
emqx_mgmt_api:cluster_query(
|
emqx_mgmt_api:cluster_query(
|
||||||
emqx_channel_info,
|
?CHAN_INFO_TAB,
|
||||||
#{},
|
#{},
|
||||||
[],
|
[],
|
||||||
fun emqx_mgmt_api_clients:qs2ms/2,
|
fun emqx_mgmt_api_clients:qs2ms/2,
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
-record(state, {subscriber}).
|
-record(state, {subscriber}).
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_router.hrl").
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ unsubscribe(Topic) ->
|
||||||
gen_server:call(?MODULE, {unsubscribe, Topic}).
|
gen_server:call(?MODULE, {unsubscribe, Topic}).
|
||||||
|
|
||||||
get_subscrbied_topics() ->
|
get_subscrbied_topics() ->
|
||||||
[Topic || {_Client, Topic} <- ets:tab2list(emqx_subscription)].
|
[Topic || {_Client, Topic} <- ets:tab2list(?SUBSCRIPTION)].
|
||||||
|
|
||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
-module(emqx_mgmt).
|
-module(emqx_mgmt).
|
||||||
|
|
||||||
-include("emqx_mgmt.hrl").
|
-include("emqx_mgmt.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_cm.hrl").
|
||||||
|
|
||||||
-elvis([{elvis_style, invalid_dynamic_call, disable}]).
|
-elvis([{elvis_style, invalid_dynamic_call, disable}]).
|
||||||
-elvis([{elvis_style, god_modules, disable}]).
|
-elvis([{elvis_style, god_modules, disable}]).
|
||||||
|
|
||||||
|
@ -139,7 +141,7 @@ node_info() ->
|
||||||
max_fds => proplists:get_value(
|
max_fds => proplists:get_value(
|
||||||
max_fds, lists:usort(lists:flatten(erlang:system_info(check_io)))
|
max_fds, lists:usort(lists:flatten(erlang:system_info(check_io)))
|
||||||
),
|
),
|
||||||
connections => ets:info(emqx_channel, size),
|
connections => ets:info(?CHAN_TAB, size),
|
||||||
node_status => 'running',
|
node_status => 'running',
|
||||||
uptime => proplists:get_value(uptime, BrokerInfo),
|
uptime => proplists:get_value(uptime, BrokerInfo),
|
||||||
version => iolist_to_binary(proplists:get_value(version, BrokerInfo)),
|
version => iolist_to_binary(proplists:get_value(version, BrokerInfo)),
|
||||||
|
@ -487,7 +489,7 @@ subscribe([], _ClientId, _TopicTables) ->
|
||||||
-spec do_subscribe(emqx_types:clientid(), emqx_types:topic_filters()) ->
|
-spec do_subscribe(emqx_types:clientid(), emqx_types:topic_filters()) ->
|
||||||
{subscribe, _} | {error, atom()}.
|
{subscribe, _} | {error, atom()}.
|
||||||
do_subscribe(ClientId, TopicTables) ->
|
do_subscribe(ClientId, TopicTables) ->
|
||||||
case ets:lookup(emqx_channel, ClientId) of
|
case ets:lookup(?CHAN_TAB, ClientId) of
|
||||||
[] -> {error, channel_not_found};
|
[] -> {error, channel_not_found};
|
||||||
[{_, Pid}] -> Pid ! {subscribe, TopicTables}
|
[{_, Pid}] -> Pid ! {subscribe, TopicTables}
|
||||||
end.
|
end.
|
||||||
|
@ -514,7 +516,7 @@ unsubscribe([], _ClientId, _Topic) ->
|
||||||
-spec do_unsubscribe(emqx_types:clientid(), emqx_types:topic()) ->
|
-spec do_unsubscribe(emqx_types:clientid(), emqx_types:topic()) ->
|
||||||
{unsubscribe, _} | {error, _}.
|
{unsubscribe, _} | {error, _}.
|
||||||
do_unsubscribe(ClientId, Topic) ->
|
do_unsubscribe(ClientId, Topic) ->
|
||||||
case ets:lookup(emqx_channel, ClientId) of
|
case ets:lookup(?CHAN_TAB, ClientId) of
|
||||||
[] -> {error, channel_not_found};
|
[] -> {error, channel_not_found};
|
||||||
[{_, Pid}] -> Pid ! {unsubscribe, [emqx_topic:parse(Topic)]}
|
[{_, Pid}] -> Pid ! {unsubscribe, [emqx_topic:parse(Topic)]}
|
||||||
end.
|
end.
|
||||||
|
@ -537,7 +539,7 @@ unsubscribe_batch([], _ClientId, _Topics) ->
|
||||||
-spec do_unsubscribe_batch(emqx_types:clientid(), [emqx_types:topic()]) ->
|
-spec do_unsubscribe_batch(emqx_types:clientid(), [emqx_types:topic()]) ->
|
||||||
{unsubscribe_batch, _} | {error, _}.
|
{unsubscribe_batch, _} | {error, _}.
|
||||||
do_unsubscribe_batch(ClientId, Topics) ->
|
do_unsubscribe_batch(ClientId, Topics) ->
|
||||||
case ets:lookup(emqx_channel, ClientId) of
|
case ets:lookup(?CHAN_TAB, ClientId) of
|
||||||
[] -> {error, channel_not_found};
|
[] -> {error, channel_not_found};
|
||||||
[{_, Pid}] -> Pid ! {unsubscribe, [emqx_topic:parse(Topic) || Topic <- Topics]}
|
[{_, Pid}] -> Pid ! {unsubscribe, [emqx_topic:parse(Topic) || Topic <- Topics]}
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
-include_lib("typerefl/include/types.hrl").
|
-include_lib("typerefl/include/types.hrl").
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_cm.hrl").
|
||||||
-include_lib("hocon/include/hoconsc.hrl").
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
@ -57,7 +58,6 @@
|
||||||
%% for batch operation
|
%% for batch operation
|
||||||
-export([do_subscribe/3]).
|
-export([do_subscribe/3]).
|
||||||
|
|
||||||
-define(CLIENT_QTAB, emqx_channel_info).
|
|
||||||
-define(TAGS, [<<"Clients">>]).
|
-define(TAGS, [<<"Clients">>]).
|
||||||
|
|
||||||
-define(CLIENT_QSCHEMA, [
|
-define(CLIENT_QSCHEMA, [
|
||||||
|
@ -666,7 +666,7 @@ list_clients(QString) ->
|
||||||
case maps:get(<<"node">>, QString, undefined) of
|
case maps:get(<<"node">>, QString, undefined) of
|
||||||
undefined ->
|
undefined ->
|
||||||
emqx_mgmt_api:cluster_query(
|
emqx_mgmt_api:cluster_query(
|
||||||
?CLIENT_QTAB,
|
?CHAN_INFO_TAB,
|
||||||
QString,
|
QString,
|
||||||
?CLIENT_QSCHEMA,
|
?CLIENT_QSCHEMA,
|
||||||
fun ?MODULE:qs2ms/2,
|
fun ?MODULE:qs2ms/2,
|
||||||
|
@ -678,7 +678,7 @@ list_clients(QString) ->
|
||||||
QStringWithoutNode = maps:without([<<"node">>], QString),
|
QStringWithoutNode = maps:without([<<"node">>], QString),
|
||||||
emqx_mgmt_api:node_query(
|
emqx_mgmt_api:node_query(
|
||||||
Node1,
|
Node1,
|
||||||
?CLIENT_QTAB,
|
?CHAN_INFO_TAB,
|
||||||
QStringWithoutNode,
|
QStringWithoutNode,
|
||||||
?CLIENT_QSCHEMA,
|
?CLIENT_QSCHEMA,
|
||||||
fun ?MODULE:qs2ms/2,
|
fun ?MODULE:qs2ms/2,
|
||||||
|
@ -753,7 +753,7 @@ subscribe_batch(#{clientid := ClientID, topics := Topics}) ->
|
||||||
%% We use emqx_channel instead of emqx_channel_info (used by the emqx_mgmt:lookup_client/2),
|
%% We use emqx_channel instead of emqx_channel_info (used by the emqx_mgmt:lookup_client/2),
|
||||||
%% as the emqx_channel_info table will only be populated after the hook `client.connected`
|
%% as the emqx_channel_info table will only be populated after the hook `client.connected`
|
||||||
%% has returned. So if one want to subscribe topics in this hook, it will fail.
|
%% has returned. So if one want to subscribe topics in this hook, it will fail.
|
||||||
case ets:lookup(emqx_channel, ClientID) of
|
case ets:lookup(?CHAN_TAB, ClientID) of
|
||||||
[] ->
|
[] ->
|
||||||
{404, ?CLIENTID_NOT_FOUND};
|
{404, ?CLIENTID_NOT_FOUND};
|
||||||
_ ->
|
_ ->
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
-module(emqx_mgmt_api_topics).
|
-module(emqx_mgmt_api_topics).
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_router.hrl").
|
||||||
-include_lib("typerefl/include/types.hrl").
|
-include_lib("typerefl/include/types.hrl").
|
||||||
-include_lib("hocon/include/hoconsc.hrl").
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
|
||||||
|
@ -111,7 +112,7 @@ do_list(Params) ->
|
||||||
case
|
case
|
||||||
emqx_mgmt_api:node_query(
|
emqx_mgmt_api:node_query(
|
||||||
node(),
|
node(),
|
||||||
emqx_route,
|
?ROUTE_TAB,
|
||||||
Params,
|
Params,
|
||||||
?TOPICS_QUERY_SCHEMA,
|
?TOPICS_QUERY_SCHEMA,
|
||||||
fun ?MODULE:qs2ms/2,
|
fun ?MODULE:qs2ms/2,
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
-module(emqx_mgmt_cli).
|
-module(emqx_mgmt_cli).
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_cm.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_router.hrl").
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
|
@ -168,7 +170,7 @@ sort_map_list_field(Field, Map) ->
|
||||||
%% @doc Query clients
|
%% @doc Query clients
|
||||||
|
|
||||||
clients(["list"]) ->
|
clients(["list"]) ->
|
||||||
dump(emqx_channel, client);
|
dump(?CHAN_TAB, client);
|
||||||
clients(["show", ClientId]) ->
|
clients(["show", ClientId]) ->
|
||||||
if_client(ClientId, fun print/1);
|
if_client(ClientId, fun print/1);
|
||||||
clients(["kick", ClientId]) ->
|
clients(["kick", ClientId]) ->
|
||||||
|
@ -182,7 +184,7 @@ clients(_) ->
|
||||||
]).
|
]).
|
||||||
|
|
||||||
if_client(ClientId, Fun) ->
|
if_client(ClientId, Fun) ->
|
||||||
case ets:lookup(emqx_channel, (bin(ClientId))) of
|
case ets:lookup(?CHAN_TAB, (bin(ClientId))) of
|
||||||
[] -> emqx_ctl:print("Not Found.~n");
|
[] -> emqx_ctl:print("Not Found.~n");
|
||||||
[Channel] -> Fun({client, Channel})
|
[Channel] -> Fun({client, Channel})
|
||||||
end.
|
end.
|
||||||
|
@ -191,9 +193,9 @@ if_client(ClientId, Fun) ->
|
||||||
%% @doc Topics Command
|
%% @doc Topics Command
|
||||||
|
|
||||||
topics(["list"]) ->
|
topics(["list"]) ->
|
||||||
dump(emqx_route, emqx_topic);
|
dump(?ROUTE_TAB, emqx_topic);
|
||||||
topics(["show", Topic]) ->
|
topics(["show", Topic]) ->
|
||||||
Routes = ets:lookup(emqx_route, bin(Topic)),
|
Routes = ets:lookup(?ROUTE_TAB, bin(Topic)),
|
||||||
[print({emqx_topic, Route}) || Route <- Routes];
|
[print({emqx_topic, Route}) || Route <- Routes];
|
||||||
topics(_) ->
|
topics(_) ->
|
||||||
emqx_ctl:usage([
|
emqx_ctl:usage([
|
||||||
|
@ -204,23 +206,23 @@ topics(_) ->
|
||||||
subscriptions(["list"]) ->
|
subscriptions(["list"]) ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(Suboption) ->
|
fun(Suboption) ->
|
||||||
print({emqx_suboption, Suboption})
|
print({?SUBOPTION, Suboption})
|
||||||
end,
|
end,
|
||||||
ets:tab2list(emqx_suboption)
|
ets:tab2list(?SUBOPTION)
|
||||||
);
|
);
|
||||||
subscriptions(["show", ClientId]) ->
|
subscriptions(["show", ClientId]) ->
|
||||||
case ets:lookup(emqx_subid, bin(ClientId)) of
|
case ets:lookup(emqx_subid, bin(ClientId)) of
|
||||||
[] ->
|
[] ->
|
||||||
emqx_ctl:print("Not Found.~n");
|
emqx_ctl:print("Not Found.~n");
|
||||||
[{_, Pid}] ->
|
[{_, Pid}] ->
|
||||||
case ets:match_object(emqx_suboption, {{'_', Pid}, '_'}) of
|
case ets:match_object(?SUBOPTION, {{'_', Pid}, '_'}) of
|
||||||
[] -> emqx_ctl:print("Not Found.~n");
|
[] -> emqx_ctl:print("Not Found.~n");
|
||||||
Suboption -> [print({emqx_suboption, Sub}) || Sub <- Suboption]
|
Suboption -> [print({?SUBOPTION, Sub}) || Sub <- Suboption]
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
subscriptions(["add", ClientId, Topic, QoS]) ->
|
subscriptions(["add", ClientId, Topic, QoS]) ->
|
||||||
if_valid_qos(QoS, fun(IntQos) ->
|
if_valid_qos(QoS, fun(IntQos) ->
|
||||||
case ets:lookup(emqx_channel, bin(ClientId)) of
|
case ets:lookup(?CHAN_TAB, bin(ClientId)) of
|
||||||
[] ->
|
[] ->
|
||||||
emqx_ctl:print("Error: Channel not found!");
|
emqx_ctl:print("Error: Channel not found!");
|
||||||
[{_, Pid}] ->
|
[{_, Pid}] ->
|
||||||
|
@ -230,7 +232,7 @@ subscriptions(["add", ClientId, Topic, QoS]) ->
|
||||||
end
|
end
|
||||||
end);
|
end);
|
||||||
subscriptions(["del", ClientId, Topic]) ->
|
subscriptions(["del", ClientId, Topic]) ->
|
||||||
case ets:lookup(emqx_channel, bin(ClientId)) of
|
case ets:lookup(?CHAN_TAB, bin(ClientId)) of
|
||||||
[] ->
|
[] ->
|
||||||
emqx_ctl:print("Error: Channel not found!");
|
emqx_ctl:print("Error: Channel not found!");
|
||||||
[{_, Pid}] ->
|
[{_, Pid}] ->
|
||||||
|
@ -841,7 +843,7 @@ print({emqx_topic, #route{topic = Topic, dest = {_, Node}}}) ->
|
||||||
emqx_ctl:print("~ts -> ~ts~n", [Topic, Node]);
|
emqx_ctl:print("~ts -> ~ts~n", [Topic, Node]);
|
||||||
print({emqx_topic, #route{topic = Topic, dest = Node}}) ->
|
print({emqx_topic, #route{topic = Topic, dest = Node}}) ->
|
||||||
emqx_ctl:print("~ts -> ~ts~n", [Topic, Node]);
|
emqx_ctl:print("~ts -> ~ts~n", [Topic, Node]);
|
||||||
print({emqx_suboption, {{Topic, Pid}, Options}}) when is_pid(Pid) ->
|
print({?SUBOPTION, {{Topic, Pid}, Options}}) when is_pid(Pid) ->
|
||||||
SubId = maps:get(subid, Options),
|
SubId = maps:get(subid, Options),
|
||||||
QoS = maps:get(qos, Options, 0),
|
QoS = maps:get(qos, Options, 0),
|
||||||
NL = maps:get(nl, Options, 0),
|
NL = maps:get(nl, Options, 0),
|
||||||
|
|
|
@ -18,11 +18,10 @@
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("emqx/include/emqx_router.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
|
||||||
-define(ROUTE_TAB, emqx_route).
|
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
emqx_common_test_helpers:all(?MODULE).
|
emqx_common_test_helpers:all(?MODULE).
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
-type index() :: list(pos_integer()).
|
-type index() :: list(pos_integer()).
|
||||||
|
|
||||||
%% @doc Index key is a term that can be effectively searched in the index table.
|
%% @doc Index key is a term that can be effectively searched in the index table.
|
||||||
-type index_key() :: {index(), {emqx_topic:words(), emqx_topic:words()}}.
|
-type index_key() :: {index(), {emqx_types:words(), emqx_types:words()}}.
|
||||||
|
|
||||||
-type match_pattern_part() :: term().
|
-type match_pattern_part() :: term().
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
%% @doc Given words of a concrete topic (`Tokens') and a list of `Indices',
|
%% @doc Given words of a concrete topic (`Tokens') and a list of `Indices',
|
||||||
%% constructs index keys for the topic and each of the indices.
|
%% constructs index keys for the topic and each of the indices.
|
||||||
%% `Fun' is called with each of these keys.
|
%% `Fun' is called with each of these keys.
|
||||||
-spec foreach_index_key(fun((index_key()) -> any()), list(index()), emqx_topic:words()) -> ok.
|
-spec foreach_index_key(fun((index_key()) -> any()), list(index()), emqx_types:words()) -> ok.
|
||||||
foreach_index_key(_Fun, [], _Tokens) ->
|
foreach_index_key(_Fun, [], _Tokens) ->
|
||||||
ok;
|
ok;
|
||||||
foreach_index_key(Fun, [Index | Indices], Tokens) ->
|
foreach_index_key(Fun, [Index | Indices], Tokens) ->
|
||||||
|
@ -59,7 +59,7 @@ foreach_index_key(Fun, [Index | Indices], Tokens) ->
|
||||||
%% returns `{[2, 3], {[<<"b">>, <<"c">>], [<<"a">>, <<"d">>]}}' term.
|
%% returns `{[2, 3], {[<<"b">>, <<"c">>], [<<"a">>, <<"d">>]}}' term.
|
||||||
%%
|
%%
|
||||||
%% @see foreach_index_key/3
|
%% @see foreach_index_key/3
|
||||||
-spec to_index_key(index(), emqx_topic:words()) -> index_key().
|
-spec to_index_key(index(), emqx_types:words()) -> index_key().
|
||||||
to_index_key(Index, Tokens) ->
|
to_index_key(Index, Tokens) ->
|
||||||
{Index, split_index_tokens(Index, Tokens, 1, [], [])}.
|
{Index, split_index_tokens(Index, Tokens, 1, [], [])}.
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ to_index_key(Index, Tokens) ->
|
||||||
%%
|
%%
|
||||||
%% @see foreach_index_key/3
|
%% @see foreach_index_key/3
|
||||||
%% @see to_index_key/2
|
%% @see to_index_key/2
|
||||||
-spec index_score(index(), emqx_topic:words()) -> non_neg_integer().
|
-spec index_score(index(), emqx_types:words()) -> non_neg_integer().
|
||||||
index_score(Index, Tokens) ->
|
index_score(Index, Tokens) ->
|
||||||
index_score(Index, Tokens, 1, 0).
|
index_score(Index, Tokens, 1, 0).
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ select_index(Tokens, Indices) ->
|
||||||
%%
|
%%
|
||||||
%% E.g. for `[2, 3]' index and <code>['+', <<"b">>, '+', <<"d">>]</code> wildcard topic
|
%% E.g. for `[2, 3]' index and <code>['+', <<"b">>, '+', <<"d">>]</code> wildcard topic
|
||||||
%% returns <code>{[2, 3], {[<<"b">>, '_'], ['_', <<"d">>]}}</code> pattern.
|
%% returns <code>{[2, 3], {[<<"b">>, '_'], ['_', <<"d">>]}}</code> pattern.
|
||||||
-spec condition(index(), emqx_topic:words()) -> match_pattern_part().
|
-spec condition(index(), emqx_types:words()) -> match_pattern_part().
|
||||||
condition(Index, Tokens) ->
|
condition(Index, Tokens) ->
|
||||||
{Index, condition(Index, Tokens, 1, [], [])}.
|
{Index, condition(Index, Tokens, 1, [], [])}.
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ condition(Index, Tokens) ->
|
||||||
%%
|
%%
|
||||||
%% E.g. for <code>['+', <<"b">>, '+', <<"d">>, '#']</code> wildcard topic
|
%% E.g. for <code>['+', <<"b">>, '+', <<"d">>, '#']</code> wildcard topic
|
||||||
%% returns <code>['_', <<"b">>, '_', <<"d">> | '_']</code> pattern.
|
%% returns <code>['_', <<"b">>, '_', <<"d">> | '_']</code> pattern.
|
||||||
-spec condition(emqx_topic:words()) -> match_pattern_part().
|
-spec condition(emqx_types:words()) -> match_pattern_part().
|
||||||
condition(Tokens) ->
|
condition(Tokens) ->
|
||||||
Tokens1 = [
|
Tokens1 = [
|
||||||
case W =:= '+' of
|
case W =:= '+' of
|
||||||
|
@ -118,7 +118,7 @@ condition(Tokens) ->
|
||||||
%%
|
%%
|
||||||
%% E.g given `{[2, 3], {[<<"b">>, <<"c">>], [<<"a">>, <<"d">>]}}' index key
|
%% E.g given `{[2, 3], {[<<"b">>, <<"c">>], [<<"a">>, <<"d">>]}}' index key
|
||||||
%% returns `[<<"a">>, <<"b">>, <<"c">>, <<"d">>]' topic.
|
%% returns `[<<"a">>, <<"b">>, <<"c">>, <<"d">>]' topic.
|
||||||
-spec restore_topic(index_key()) -> emqx_topic:words().
|
-spec restore_topic(index_key()) -> emqx_types:words().
|
||||||
restore_topic({Index, {IndexTokens, OtherTokens}}) ->
|
restore_topic({Index, {IndexTokens, OtherTokens}}) ->
|
||||||
restore_topic(Index, IndexTokens, OtherTokens, 1, []).
|
restore_topic(Index, IndexTokens, OtherTokens, 1, []).
|
||||||
|
|
||||||
|
|
|
@ -976,15 +976,13 @@ fields_tcp_opts_nodelay.label:
|
||||||
"""TCP_NODELAY"""
|
"""TCP_NODELAY"""
|
||||||
|
|
||||||
fields_tcp_opts_keepalive.desc:
|
fields_tcp_opts_keepalive.desc:
|
||||||
"""
|
"""Enable TCP keepalive for MQTT connections over TCP or SSL.
|
||||||
Enable TCP keepalive for MQTT connections over TCP or SSL.
|
|
||||||
The value is three comma separated numbers in the format of 'Idle,Interval,Probes'
|
The value is three comma separated numbers in the format of 'Idle,Interval,Probes'
|
||||||
- Idle: The number of seconds a connection needs to be idle before the server begins to send out keep-alive probes (Linux default 7200).
|
- Idle: The number of seconds a connection needs to be idle before the server begins to send out keep-alive probes (Linux default 7200).
|
||||||
- Interval: The number of seconds between TCP keep-alive probes (Linux default 75).
|
- Interval: The number of seconds between TCP keep-alive probes (Linux default 75).
|
||||||
- Probes: The maximum number of TCP keep-alive probes to send before giving up and killing the connection if no response is obtained from the other end (Linux default 9).
|
- Probes: The maximum number of TCP keep-alive probes to send before giving up and killing the connection if no response is obtained from the other end (Linux default 9).
|
||||||
For example "240,30,5" means: EMQX should start sending TCP keepalive probes after the connection is in idle for 240 seconds, and the probes are sent every 30 seconds until a response is received from the MQTT client, if it misses 5 consecutive responses, EMQX should close the connection.
|
For example "240,30,5" means: EMQX should start sending TCP keepalive probes after the connection is in idle for 240 seconds, and the probes are sent every 30 seconds until a response is received from the MQTT client, if it misses 5 consecutive responses, EMQX should close the connection.
|
||||||
Default: 'none'
|
Default: 'none'"""
|
||||||
"""
|
|
||||||
|
|
||||||
fields_tcp_opts_keepalive.label:
|
fields_tcp_opts_keepalive.label:
|
||||||
"""TCP keepalive options"""
|
"""TCP keepalive options"""
|
||||||
|
|
Loading…
Reference in New Issue