Revert "Format MQTT code with `erlfmt`"

This commit is contained in:
Chunyi Lyu 2023-01-27 18:25:57 +00:00 committed by GitHub
parent 6806a9a45e
commit 209f23fa2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 3480 additions and 5365 deletions

View File

@ -1 +0,0 @@
1de9fcf582def91d1cee6bea457dd24e8a53a431

View File

@ -10,20 +10,18 @@
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour'). -behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
-export([ -export([scopes/0,
scopes/0, switches/0,
switches/0, aliases/0,
aliases/0, usage/0,
usage/0, usage_doc_guides/0,
usage_doc_guides/0, banner/2,
banner/2, validate/2,
validate/2, merge_defaults/2,
merge_defaults/2, run/2,
run/2, output/2,
output/2, description/0,
description/0, help_section/0]).
help_section/0
]).
scopes() -> [ctl]. scopes() -> [ctl].
switches() -> []. switches() -> [].
@ -50,29 +48,20 @@ usage() ->
usage_doc_guides() -> usage_doc_guides() ->
[?MQTT_GUIDE_URL]. [?MQTT_GUIDE_URL].
run([Node], #{ run([Node], #{node := NodeName,
node := NodeName, timeout := Timeout}) ->
timeout := Timeout
}) ->
case rabbit_misc:rpc_call(NodeName, rabbit_mqtt_collector, leave, [Node], Timeout) of case rabbit_misc:rpc_call(NodeName, rabbit_mqtt_collector, leave, [Node], Timeout) of
{badrpc, _} = Error -> {badrpc, _} = Error ->
Error; Error;
nodedown -> nodedown ->
{ok, {ok, list_to_binary(io_lib:format("Node ~ts is down but has been successfully removed"
list_to_binary( " from the cluster", [Node]))};
io_lib:format(
"Node ~ts is down but has been successfully removed"
" from the cluster",
[Node]
)
)};
Result -> Result ->
%% 'ok' or 'timeout' %% 'ok' or 'timeout'
Result Result
end. end.
banner([Node], _) -> banner([Node], _) -> list_to_binary(io_lib:format("Removing node ~ts from the list of MQTT nodes...", [Node])).
list_to_binary(io_lib:format("Removing node ~ts from the list of MQTT nodes...", [Node])).
output(Result, _Opts) -> output(Result, _Opts) ->
'Elixir.RabbitMQ.CLI.DefaultOutput':output(Result). 'Elixir.RabbitMQ.CLI.DefaultOutput':output(Result).

View File

@ -10,22 +10,20 @@
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour'). -behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
-export([ -export([formatter/0,
formatter/0, scopes/0,
scopes/0, switches/0,
switches/0, aliases/0,
aliases/0, usage/0,
usage/0, usage_additional/0,
usage_additional/0, usage_doc_guides/0,
usage_doc_guides/0, banner/2,
banner/2, validate/2,
validate/2, merge_defaults/2,
merge_defaults/2, run/2,
run/2, output/2,
output/2, description/0,
description/0, help_section/0]).
help_section/0
]).
formatter() -> 'Elixir.RabbitMQ.CLI.Formatters.Table'. formatter() -> 'Elixir.RabbitMQ.CLI.Formatters.Table'.
scopes() -> [ctl, diagnostics]. scopes() -> [ctl, diagnostics].
@ -39,14 +37,10 @@ help_section() ->
validate(Args, _) -> validate(Args, _) ->
InfoItems = lists:map(fun atom_to_list/1, ?INFO_ITEMS), InfoItems = lists:map(fun atom_to_list/1, ?INFO_ITEMS),
case case 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':validate_info_keys(Args,
'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':validate_info_keys( InfoItems) of
Args,
InfoItems
)
of
{ok, _} -> ok; {ok, _} -> ok;
Error -> Error Error -> Error
end. end.
merge_defaults([], Opts) -> merge_defaults([], Opts) ->
@ -61,22 +55,19 @@ usage_additional() ->
Prefix = <<" must be one of ">>, Prefix = <<" must be one of ">>,
InfoItems = 'Elixir.Enum':join(lists:usort(?INFO_ITEMS), <<", ">>), InfoItems = 'Elixir.Enum':join(lists:usort(?INFO_ITEMS), <<", ">>),
[ [
{<<"<column>">>, <<Prefix/binary, InfoItems/binary>>} {<<"<column>">>, <<Prefix/binary, InfoItems/binary>>}
]. ].
usage_doc_guides() -> usage_doc_guides() ->
[?MQTT_GUIDE_URL]. [?MQTT_GUIDE_URL].
run(Args, #{ run(Args, #{node := NodeName,
node := NodeName, timeout := Timeout,
timeout := Timeout, verbose := Verbose}) ->
verbose := Verbose InfoKeys = case Verbose of
}) -> true -> ?INFO_ITEMS;
InfoKeys = false -> 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':prepare_info_keys(Args)
case Verbose of end,
true -> ?INFO_ITEMS;
false -> 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':prepare_info_keys(Args)
end,
Nodes = 'Elixir.RabbitMQ.CLI.Core.Helpers':nodes_in_cluster(NodeName), Nodes = 'Elixir.RabbitMQ.CLI.Core.Helpers':nodes_in_cluster(NodeName),
@ -87,8 +78,7 @@ run(Args, #{
[Nodes, InfoKeys], [Nodes, InfoKeys],
Timeout, Timeout,
InfoKeys, InfoKeys,
length(Nodes) length(Nodes)).
).
banner(_, _) -> <<"Listing MQTT connections ...">>. banner(_, _) -> <<"Listing MQTT connections ...">>.

View File

@ -9,15 +9,13 @@
-include("mqtt_machine.hrl"). -include("mqtt_machine.hrl").
-export([ -export([version/0,
version/0, which_module/1,
which_module/1, init/1,
init/1, apply/3,
apply/3, state_enter/2,
state_enter/2, notify_connection/2,
notify_connection/2, overview/1]).
overview/1
]).
-type state() :: #machine_state{}. -type state() :: #machine_state{}.
@ -26,10 +24,9 @@
-type reply() :: {ok, term()} | {error, term()}. -type reply() :: {ok, term()} | {error, term()}.
-type client_id() :: term(). -type client_id() :: term().
-type command() :: -type command() :: {register, client_id(), pid()} |
{register, client_id(), pid()} {unregister, client_id(), pid()} |
| {unregister, client_id(), pid()} list.
| list.
version() -> 1. version() -> 1.
which_module(1) -> ?MODULE; which_module(1) -> ?MODULE;
@ -41,130 +38,93 @@ init(_Conf) ->
-spec apply(map(), command(), state()) -> -spec apply(map(), command(), state()) ->
{state(), reply(), ra_machine:effects()}. {state(), reply(), ra_machine:effects()}.
apply( apply(_Meta, {register, ClientId, Pid},
_Meta, #machine_state{client_ids = Ids,
{register, ClientId, Pid}, pids = Pids0} = State0) ->
#machine_state{
client_ids = Ids,
pids = Pids0
} = State0
) ->
{Effects, Ids1, Pids} = {Effects, Ids1, Pids} =
case maps:find(ClientId, Ids) of case maps:find(ClientId, Ids) of
{ok, OldPid} when Pid =/= OldPid -> {ok, OldPid} when Pid =/= OldPid ->
Effects0 = [ Effects0 = [{demonitor, process, OldPid},
{demonitor, process, OldPid}, {monitor, process, Pid},
{monitor, process, Pid}, {mod_call, ?MODULE, notify_connection,
{mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]} [OldPid, duplicate_id]}],
], Pids2 = case maps:take(OldPid, Pids0) of
Pids2 = error ->
case maps:take(OldPid, Pids0) of Pids0;
error -> {[ClientId], Pids1} ->
Pids0; Pids1;
{[ClientId], Pids1} -> {ClientIds, Pids1} ->
Pids1; Pids1#{ClientId => lists:delete(ClientId, ClientIds)}
{ClientIds, Pids1} -> end,
Pids1#{ClientId => lists:delete(ClientId, ClientIds)} Pids3 = maps:update_with(Pid, fun(CIds) -> [ClientId | CIds] end,
end, [ClientId], Pids2),
Pids3 = maps:update_with(
Pid,
fun(CIds) -> [ClientId | CIds] end,
[ClientId],
Pids2
),
{Effects0, maps:remove(ClientId, Ids), Pids3}; {Effects0, maps:remove(ClientId, Ids), Pids3};
{ok, Pid} ->
{ok, Pid} ->
{[], Ids, Pids0}; {[], Ids, Pids0};
error -> error ->
Pids1 = maps:update_with( Pids1 = maps:update_with(Pid, fun(CIds) -> [ClientId | CIds] end,
Pid, [ClientId], Pids0),
fun(CIds) -> [ClientId | CIds] end,
[ClientId],
Pids0
),
Effects0 = [{monitor, process, Pid}], Effects0 = [{monitor, process, Pid}],
{Effects0, Ids, Pids1} {Effects0, Ids, Pids1}
end, end,
State = State0#machine_state{ State = State0#machine_state{client_ids = maps:put(ClientId, Pid, Ids1),
client_ids = maps:put(ClientId, Pid, Ids1), pids = Pids},
pids = Pids
},
{State, ok, Effects}; {State, ok, Effects};
apply(
Meta,
{unregister, ClientId, Pid},
#machine_state{
client_ids = Ids,
pids = Pids0
} = State0
) ->
State =
case maps:find(ClientId, Ids) of
{ok, Pid} ->
Pids =
case maps:get(Pid, Pids0, undefined) of
undefined ->
Pids0;
[ClientId] ->
maps:remove(Pid, Pids0);
Cids ->
Pids0#{Pid => lists:delete(ClientId, Cids)}
end,
State0#machine_state{ apply(Meta, {unregister, ClientId, Pid}, #machine_state{client_ids = Ids,
client_ids = maps:remove(ClientId, Ids), pids = Pids0} = State0) ->
pids = Pids State = case maps:find(ClientId, Ids) of
}; {ok, Pid} ->
%% don't delete client id that might belong to a newer connection Pids = case maps:get(Pid, Pids0, undefined) of
%% that kicked the one with Pid out undefined ->
{ok, _AnotherPid} -> Pids0;
State0; [ClientId] ->
error -> maps:remove(Pid, Pids0);
State0 Cids ->
end, Pids0#{Pid => lists:delete(ClientId, Cids)}
end,
State0#machine_state{client_ids = maps:remove(ClientId, Ids),
pids = Pids};
%% don't delete client id that might belong to a newer connection
%% that kicked the one with Pid out
{ok, _AnotherPid} ->
State0;
error ->
State0
end,
Effects0 = [{demonitor, process, Pid}], Effects0 = [{demonitor, process, Pid}],
%% snapshot only when the map has changed %% snapshot only when the map has changed
Effects = Effects = case State of
case State of State0 -> Effects0;
State0 -> Effects0; _ -> Effects0 ++ snapshot_effects(Meta, State)
_ -> Effects0 ++ snapshot_effects(Meta, State) end,
end,
{State, ok, Effects}; {State, ok, Effects};
apply(_Meta, {down, DownPid, noconnection}, State) -> apply(_Meta, {down, DownPid, noconnection}, State) ->
%% Monitor the node the pid is on (see {nodeup, Node} below) %% Monitor the node the pid is on (see {nodeup, Node} below)
%% so that we can detect when the node is re-connected and discover the %% so that we can detect when the node is re-connected and discover the
%% actual fate of the connection processes on it %% actual fate of the connection processes on it
Effect = {monitor, node, node(DownPid)}, Effect = {monitor, node, node(DownPid)},
{State, ok, Effect}; {State, ok, Effect};
apply(
Meta, apply(Meta, {down, DownPid, _}, #machine_state{client_ids = Ids,
{down, DownPid, _}, pids = Pids0} = State0) ->
#machine_state{
client_ids = Ids,
pids = Pids0
} = State0
) ->
case maps:get(DownPid, Pids0, undefined) of case maps:get(DownPid, Pids0, undefined) of
undefined -> undefined ->
{State0, ok, []}; {State0, ok, []};
ClientIds -> ClientIds ->
Ids1 = maps:without(ClientIds, Ids), Ids1 = maps:without(ClientIds, Ids),
State = State0#machine_state{ State = State0#machine_state{client_ids = Ids1,
client_ids = Ids1, pids = maps:remove(DownPid, Pids0)},
pids = maps:remove(DownPid, Pids0) Effects = lists:map(fun(Id) ->
}, [{mod_call, rabbit_log, debug,
Effects = lists:map( ["MQTT connection with client id '~ts' failed", [Id]]}]
fun(Id) -> end, ClientIds),
[
{mod_call, rabbit_log, debug, [
"MQTT connection with client id '~ts' failed", [Id]
]}
]
end,
ClientIds
),
{State, ok, Effects ++ snapshot_effects(Meta, State)} {State, ok, Effects ++ snapshot_effects(Meta, State)}
end; end;
apply(_Meta, {nodeup, Node}, State) -> apply(_Meta, {nodeup, Node}, State) ->
%% Work out if any pids that were disconnected are still %% Work out if any pids that were disconnected are still
%% alive. %% alive.
@ -173,69 +133,41 @@ apply(_Meta, {nodeup, Node}, State) ->
{State, ok, Effects}; {State, ok, Effects};
apply(_Meta, {nodedown, _Node}, State) -> apply(_Meta, {nodedown, _Node}, State) ->
{State, ok}; {State, ok};
apply(
Meta,
{leave, Node},
#machine_state{
client_ids = Ids,
pids = Pids0
} = State0
) ->
{Keep, Remove} = maps:fold(
fun(ClientId, Pid, {In, Out}) ->
case node(Pid) =/= Node of
true ->
{In#{ClientId => Pid}, Out};
false ->
{In, Out#{ClientId => Pid}}
end
end,
{#{}, #{}},
Ids
),
Effects = maps:fold(
fun(ClientId, _Pid, Acc) ->
Pid = maps:get(ClientId, Ids),
[
{demonitor, process, Pid},
{mod_call, ?MODULE, notify_connection, [Pid, decommission_node]},
{mod_call, rabbit_log, debug, [
"MQTT will remove client ID '~ts' from known "
"as its node has been decommissioned",
[ClientId]
]}
] ++ Acc
end,
[],
Remove
),
State = State0#machine_state{ apply(Meta, {leave, Node}, #machine_state{client_ids = Ids,
client_ids = Keep, pids = Pids0} = State0) ->
pids = maps:without(maps:keys(Remove), Pids0) {Keep, Remove} = maps:fold(
}, fun (ClientId, Pid, {In, Out}) ->
case node(Pid) =/= Node of
true ->
{In#{ClientId => Pid}, Out};
false ->
{In, Out#{ClientId => Pid}}
end
end, {#{}, #{}}, Ids),
Effects = maps:fold(fun (ClientId, _Pid, Acc) ->
Pid = maps:get(ClientId, Ids),
[
{demonitor, process, Pid},
{mod_call, ?MODULE, notify_connection, [Pid, decommission_node]},
{mod_call, rabbit_log, debug,
["MQTT will remove client ID '~ts' from known "
"as its node has been decommissioned", [ClientId]]}
] ++ Acc
end, [], Remove),
State = State0#machine_state{client_ids = Keep,
pids = maps:without(maps:keys(Remove), Pids0)},
{State, ok, Effects ++ snapshot_effects(Meta, State)}; {State, ok, Effects ++ snapshot_effects(Meta, State)};
apply(_Meta, {machine_version, 0, 1}, {machine_state, Ids}) -> apply(_Meta, {machine_version, 0, 1}, {machine_state, Ids}) ->
Pids = maps:fold( Pids = maps:fold(
fun(Id, Pid, Acc) -> fun(Id, Pid, Acc) ->
maps:update_with( maps:update_with(Pid,
Pid, fun(CIds) -> [Id | CIds] end,
fun(CIds) -> [Id | CIds] end, [Id], Acc)
[Id], end, #{}, Ids),
Acc {#machine_state{client_ids = Ids,
) pids = Pids}, ok, []};
end,
#{},
Ids
),
{
#machine_state{
client_ids = Ids,
pids = Pids
},
ok,
[]
};
apply(_Meta, Unknown, State) -> apply(_Meta, Unknown, State) ->
logger:error("MQTT Raft state machine v1 received unknown command ~tp", [Unknown]), logger:error("MQTT Raft state machine v1 received unknown command ~tp", [Unknown]),
{State, {error, {unknown_command, Unknown}}, []}. {State, {error, {unknown_command, Unknown}}, []}.
@ -250,21 +182,17 @@ state_enter(_, _) ->
[]. [].
-spec overview(state()) -> map(). -spec overview(state()) -> map().
overview(#machine_state{ overview(#machine_state{client_ids = ClientIds,
client_ids = ClientIds, pids = Pids}) ->
pids = Pids #{num_client_ids => maps:size(ClientIds),
}) -> num_pids => maps:size(Pids)}.
#{
num_client_ids => maps:size(ClientIds),
num_pids => maps:size(Pids)
}.
%% ========================== %% ==========================
%% Avoids blocking the Raft leader. %% Avoids blocking the Raft leader.
-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid(). -spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
notify_connection(Pid, Reason) -> notify_connection(Pid, Reason) ->
spawn(fun() -> gen_server2:cast(Pid, Reason) end). spawn(fun() -> gen_server2:cast(Pid, Reason) end).
-spec snapshot_effects(map(), state()) -> ra_machine:effects(). -spec snapshot_effects(map(), state()) -> ra_machine:effects().
snapshot_effects(#{index := RaftIdx}, State) -> snapshot_effects(#{index := RaftIdx}, State) ->

View File

@ -9,12 +9,10 @@
-include("mqtt_machine_v0.hrl"). -include("mqtt_machine_v0.hrl").
-export([ -export([init/1,
init/1, apply/3,
apply/3, state_enter/2,
state_enter/2, notify_connection/2]).
notify_connection/2
]).
-type state() :: #machine_state{}. -type state() :: #machine_state{}.
@ -23,10 +21,9 @@
-type reply() :: {ok, term()} | {error, term()}. -type reply() :: {ok, term()} | {error, term()}.
-type client_id() :: term(). -type client_id() :: term().
-type command() :: -type command() :: {register, client_id(), pid()} |
{register, client_id(), pid()} {unregister, client_id(), pid()} |
| {unregister, client_id(), pid()} list.
| list.
-spec init(config()) -> state(). -spec init(config()) -> state().
init(_Conf) -> init(_Conf) ->
@ -38,60 +35,53 @@ apply(_Meta, {register, ClientId, Pid}, #machine_state{client_ids = Ids} = State
{Effects, Ids1} = {Effects, Ids1} =
case maps:find(ClientId, Ids) of case maps:find(ClientId, Ids) of
{ok, OldPid} when Pid =/= OldPid -> {ok, OldPid} when Pid =/= OldPid ->
Effects0 = [ Effects0 = [{demonitor, process, OldPid},
{demonitor, process, OldPid}, {monitor, process, Pid},
{monitor, process, Pid}, {mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]}],
{mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]}
],
{Effects0, maps:remove(ClientId, Ids)}; {Effects0, maps:remove(ClientId, Ids)};
_ -> _ ->
Effects0 = [{monitor, process, Pid}], Effects0 = [{monitor, process, Pid}],
{Effects0, Ids} {Effects0, Ids}
end, end,
State = State0#machine_state{client_ids = maps:put(ClientId, Pid, Ids1)}, State = State0#machine_state{client_ids = maps:put(ClientId, Pid, Ids1)},
{State, ok, Effects}; {State, ok, Effects};
apply(Meta, {unregister, ClientId, Pid}, #machine_state{client_ids = Ids} = State0) -> apply(Meta, {unregister, ClientId, Pid}, #machine_state{client_ids = Ids} = State0) ->
State = State = case maps:find(ClientId, Ids) of
case maps:find(ClientId, Ids) of {ok, Pid} -> State0#machine_state{client_ids = maps:remove(ClientId, Ids)};
{ok, Pid} -> State0#machine_state{client_ids = maps:remove(ClientId, Ids)}; %% don't delete client id that might belong to a newer connection
%% don't delete client id that might belong to a newer connection %% that kicked the one with Pid out
%% that kicked the one with Pid out {ok, _AnotherPid} -> State0;
{ok, _AnotherPid} -> State0; error -> State0
error -> State0 end,
end,
Effects0 = [{demonitor, process, Pid}], Effects0 = [{demonitor, process, Pid}],
%% snapshot only when the map has changed %% snapshot only when the map has changed
Effects = Effects = case State of
case State of State0 -> Effects0;
State0 -> Effects0; _ -> Effects0 ++ snapshot_effects(Meta, State)
_ -> Effects0 ++ snapshot_effects(Meta, State) end,
end,
{State, ok, Effects}; {State, ok, Effects};
apply(_Meta, {down, DownPid, noconnection}, State) -> apply(_Meta, {down, DownPid, noconnection}, State) ->
%% Monitor the node the pid is on (see {nodeup, Node} below) %% Monitor the node the pid is on (see {nodeup, Node} below)
%% so that we can detect when the node is re-connected and discover the %% so that we can detect when the node is re-connected and discover the
%% actual fate of the connection processes on it %% actual fate of the connection processes on it
Effect = {monitor, node, node(DownPid)}, Effect = {monitor, node, node(DownPid)},
{State, ok, Effect}; {State, ok, Effect};
apply(Meta, {down, DownPid, _}, #machine_state{client_ids = Ids} = State0) -> apply(Meta, {down, DownPid, _}, #machine_state{client_ids = Ids} = State0) ->
Ids1 = maps:filter( Ids1 = maps:filter(fun (_ClientId, Pid) when Pid =:= DownPid ->
fun false;
(_ClientId, Pid) when Pid =:= DownPid -> (_, _) ->
false; true
(_, _) -> end, Ids),
true
end,
Ids
),
State = State0#machine_state{client_ids = Ids1}, State = State0#machine_state{client_ids = Ids1},
Delta = maps:keys(Ids) -- maps:keys(Ids1), Delta = maps:keys(Ids) -- maps:keys(Ids1),
Effects = lists:map( Effects = lists:map(fun(Id) ->
fun(Id) -> [{mod_call, rabbit_log, debug,
[{mod_call, rabbit_log, debug, ["MQTT connection with client id '~ts' failed", [Id]]}] ["MQTT connection with client id '~ts' failed", [Id]]}] end, Delta),
end,
Delta
),
{State, ok, Effects ++ snapshot_effects(Meta, State)}; {State, ok, Effects ++ snapshot_effects(Meta, State)};
apply(_Meta, {nodeup, Node}, State) -> apply(_Meta, {nodeup, Node}, State) ->
%% Work out if any pids that were disconnected are still %% Work out if any pids that were disconnected are still
%% alive. %% alive.
@ -100,29 +90,25 @@ apply(_Meta, {nodeup, Node}, State) ->
{State, ok, Effects}; {State, ok, Effects};
apply(_Meta, {nodedown, _Node}, State) -> apply(_Meta, {nodedown, _Node}, State) ->
{State, ok}; {State, ok};
apply(Meta, {leave, Node}, #machine_state{client_ids = Ids} = State0) -> apply(Meta, {leave, Node}, #machine_state{client_ids = Ids} = State0) ->
Ids1 = maps:filter(fun(_ClientId, Pid) -> node(Pid) =/= Node end, Ids), Ids1 = maps:filter(fun (_ClientId, Pid) -> node(Pid) =/= Node end, Ids),
Delta = maps:keys(Ids) -- maps:keys(Ids1), Delta = maps:keys(Ids) -- maps:keys(Ids1),
Effects = lists:foldl( Effects = lists:foldl(fun (ClientId, Acc) ->
fun(ClientId, Acc) -> Pid = maps:get(ClientId, Ids),
Pid = maps:get(ClientId, Ids), [
[ {demonitor, process, Pid},
{demonitor, process, Pid}, {mod_call, ?MODULE, notify_connection, [Pid, decommission_node]},
{mod_call, ?MODULE, notify_connection, [Pid, decommission_node]}, {mod_call, rabbit_log, debug,
{mod_call, rabbit_log, debug, [ ["MQTT will remove client ID '~ts' from known "
"MQTT will remove client ID '~ts' from known " "as its node has been decommissioned", [ClientId]]}
"as its node has been decommissioned", ] ++ Acc
[ClientId] end, [], Delta),
]}
] ++ Acc
end,
[],
Delta
),
State = State0#machine_state{client_ids = Ids1}, State = State0#machine_state{client_ids = Ids1},
{State, ok, Effects ++ snapshot_effects(Meta, State)}; {State, ok, Effects ++ snapshot_effects(Meta, State)};
apply(_Meta, Unknown, State) -> apply(_Meta, Unknown, State) ->
logger:error("MQTT Raft state machine received an unknown command ~tp", [Unknown]), logger:error("MQTT Raft state machine received an unknown command ~tp", [Unknown]),
{State, {error, {unknown_command, Unknown}}, []}. {State, {error, {unknown_command, Unknown}}, []}.
@ -141,7 +127,7 @@ state_enter(_, _) ->
%% Avoids blocking the Raft leader. %% Avoids blocking the Raft leader.
-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid(). -spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
notify_connection(Pid, Reason) -> notify_connection(Pid, Reason) ->
spawn(fun() -> gen_server2:cast(Pid, Reason) end). spawn(fun() -> gen_server2:cast(Pid, Reason) end).
-spec snapshot_effects(map(), state()) -> ra_machine:effects(). -spec snapshot_effects(map(), state()) -> ra_machine:effects().
snapshot_effects(#{index := RaftIdx}, State) -> snapshot_effects(#{index := RaftIdx}, State) ->

View File

@ -6,15 +6,8 @@
%% %%
-module(mqtt_node). -module(mqtt_node).
-export([ -export([start/0, node_id/0, server_id/0, all_node_ids/0, leave/1, trigger_election/0,
start/0, delete/1]).
node_id/0,
server_id/0,
all_node_ids/0,
leave/1,
trigger_election/0,
delete/1
]).
-define(ID_NAME, mqtt_node). -define(ID_NAME, mqtt_node).
-define(START_TIMEOUT, 100_000). -define(START_TIMEOUT, 100_000).
@ -32,11 +25,8 @@ server_id(Node) ->
{?ID_NAME, Node}. {?ID_NAME, Node}.
all_node_ids() -> all_node_ids() ->
[ [server_id(N) || N <- rabbit_nodes:all(),
server_id(N) can_participate_in_clientid_tracking(N)].
|| N <- rabbit_nodes:all(),
can_participate_in_clientid_tracking(N)
].
start() -> start() ->
%% 3s to 6s randomized %% 3s to 6s randomized
@ -50,41 +40,34 @@ start(Delay, AttemptsLeft) ->
NodeId = server_id(), NodeId = server_id(),
Nodes = compatible_peer_servers(), Nodes = compatible_peer_servers(),
case ra_directory:uid_of(?RA_SYSTEM, ?ID_NAME) of case ra_directory:uid_of(?RA_SYSTEM, ?ID_NAME) of
undefined -> undefined ->
case Nodes of case Nodes of
[] -> [] ->
%% Since cluster members are not known ahead of time and initial boot can be happening in parallel, %% Since cluster members are not known ahead of time and initial boot can be happening in parallel,
%% we wait and check a few times (up to a few seconds) to see if we can discover any peers to %% we wait and check a few times (up to a few seconds) to see if we can discover any peers to
%% join before forming a cluster. This reduces the probability of N independent clusters being %% join before forming a cluster. This reduces the probability of N independent clusters being
%% formed in the common scenario of N nodes booting in parallel e.g. because they were started %% formed in the common scenario of N nodes booting in parallel e.g. because they were started
%% at the same time by a deployment tool. %% at the same time by a deployment tool.
%% %%
%% This scenario does not guarantee single cluster formation but without knowing the list of members %% This scenario does not guarantee single cluster formation but without knowing the list of members
%% ahead of time, this is a best effort workaround. Multi-node consensus is apparently hard %% ahead of time, this is a best effort workaround. Multi-node consensus is apparently hard
%% to achieve without having consensus around expected cluster members. %% to achieve without having consensus around expected cluster members.
rabbit_log:info( rabbit_log:info("MQTT: will wait for ~tp more ms for cluster members to join before triggering a Raft leader election", [Delay]),
"MQTT: will wait for ~tp more ms for cluster members to join before triggering a Raft leader election", timer:sleep(Delay),
[Delay] start(Delay, AttemptsLeft - 1);
), Peers ->
timer:sleep(Delay), %% Trigger an election.
start(Delay, AttemptsLeft - 1); %% This is required when we start a node for the first time.
Peers -> %% Using default timeout because it supposed to reply fast.
%% Trigger an election. rabbit_log:info("MQTT: discovered ~tp cluster peers that support client ID tracking", [length(Peers)]),
%% This is required when we start a node for the first time. ok = start_server(),
%% Using default timeout because it supposed to reply fast. _ = join_peers(NodeId, Peers),
rabbit_log:info( ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
"MQTT: discovered ~tp cluster peers that support client ID tracking", [ end;
length(Peers) _ ->
] _ = join_peers(NodeId, Nodes),
), ok = ra:restart_server(?RA_SYSTEM, NodeId),
ok = start_server(), ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
_ = join_peers(NodeId, Peers),
ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
end;
_ ->
_ = join_peers(NodeId, Nodes),
ok = ra:restart_server(?RA_SYSTEM, NodeId),
ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
end. end.
compatible_peer_servers() -> compatible_peer_servers() ->
@ -95,15 +78,14 @@ start_server() ->
Nodes = compatible_peer_servers(), Nodes = compatible_peer_servers(),
UId = ra:new_uid(ra_lib:to_binary(?ID_NAME)), UId = ra:new_uid(ra_lib:to_binary(?ID_NAME)),
Timeout = application:get_env(kernel, net_ticktime, 60) + 5, Timeout = application:get_env(kernel, net_ticktime, 60) + 5,
Conf = #{ Conf = #{cluster_name => ?ID_NAME,
cluster_name => ?ID_NAME, id => NodeId,
id => NodeId, uid => UId,
uid => UId, friendly_name => atom_to_list(?ID_NAME),
friendly_name => atom_to_list(?ID_NAME), initial_members => Nodes,
initial_members => Nodes, log_init_args => #{uid => UId},
log_init_args => #{uid => UId}, tick_timeout => Timeout,
tick_timeout => Timeout, machine => {module, mqtt_machine, #{}}
machine => {module, mqtt_machine, #{}}
}, },
ra:start_server(?RA_SYSTEM, Conf). ra:start_server(?RA_SYSTEM, Conf).
@ -121,13 +103,11 @@ join_peers(NodeId, Nodes, RetriesLeft) ->
case ra:members(Nodes, ?START_TIMEOUT) of case ra:members(Nodes, ?START_TIMEOUT) of
{ok, Members, _} -> {ok, Members, _} ->
case lists:member(NodeId, Members) of case lists:member(NodeId, Members) of
true -> ok; true -> ok;
false -> ra:add_member(Members, NodeId) false -> ra:add_member(Members, NodeId)
end; end;
{timeout, _} -> {timeout, _} ->
rabbit_log:debug("MQTT: timed out contacting cluster peers, %s retries left", [ rabbit_log:debug("MQTT: timed out contacting cluster peers, %s retries left", [RetriesLeft]),
RetriesLeft
]),
timer:sleep(?RETRY_INTERVAL), timer:sleep(?RETRY_INTERVAL),
join_peers(NodeId, Nodes, RetriesLeft - 1); join_peers(NodeId, Nodes, RetriesLeft - 1);
Err -> Err ->
@ -148,12 +128,12 @@ leave(Node) ->
can_participate_in_clientid_tracking(Node) -> can_participate_in_clientid_tracking(Node) ->
case rpc:call(Node, mqtt_machine, module_info, []) of case rpc:call(Node, mqtt_machine, module_info, []) of
{badrpc, _} -> false; {badrpc, _} -> false;
_ -> true _ -> true
end. end.
-spec delete(Args) -> Ret when -spec delete(Args) -> Ret when
Args :: rabbit_feature_flags:enable_callback_args(), Args :: rabbit_feature_flags:enable_callback_args(),
Ret :: rabbit_feature_flags:enable_callback_ret(). Ret :: rabbit_feature_flags:enable_callback_ret().
delete(_) -> delete(_) ->
RaNodes = all_node_ids(), RaNodes = all_node_ids(),
Nodes = lists:map(fun({_, N}) -> N end, RaNodes), Nodes = lists:map(fun({_, N}) -> N end, RaNodes),
@ -171,13 +151,12 @@ delete(_) ->
{ok, _Leader} -> {ok, _Leader} ->
rabbit_log:info("Successfully deleted Ra cluster ~s", [?ID_NAME]), rabbit_log:info("Successfully deleted Ra cluster ~s", [?ID_NAME]),
ok; ok;
{error, _} = Err -> {error, _} = Err ->
rabbit_log:info("Failed to delete Ra cluster ~s: ~p", [?ID_NAME, Err]), rabbit_log:info("Failed to delete Ra cluster ~s: ~p", [?ID_NAME, Err]),
Err Err
catch catch exit:{{shutdown, delete}, _Stacktrace} ->
exit:{{shutdown, delete}, _Stacktrace} -> rabbit_log:info("Ra cluster ~s already being deleted", [?ID_NAME]),
rabbit_log:info("Ra cluster ~s already being deleted", [?ID_NAME]), ok
ok
end end
after after
true = global:del_lock(LockId, Nodes), true = global:del_lock(LockId, Nodes),

View File

@ -13,13 +13,11 @@
-include_lib("stdlib/include/assert.hrl"). -include_lib("stdlib/include/assert.hrl").
-export([start/2, stop/1]). -export([start/2, stop/1]).
-export([ -export([emit_connection_info_all/4,
emit_connection_info_all/4, emit_connection_info_local/3,
emit_connection_info_local/3, close_local_client_connections/1,
close_local_client_connections/1, %% Exported for tests, but could also be used for debugging.
%% Exported for tests, but could also be used for debugging. local_connection_pids/0]).
local_connection_pids/0
]).
start(normal, []) -> start(normal, []) ->
init_global_counters(), init_global_counters(),
@ -33,11 +31,10 @@ start(normal, []) ->
ok ok
end, end,
Result = rabbit_mqtt_sup:start_link({Listeners, SslListeners}, []), Result = rabbit_mqtt_sup:start_link({Listeners, SslListeners}, []),
EMPid = EMPid = case rabbit_event:start_link() of
case rabbit_event:start_link() of {ok, Pid} -> Pid;
{ok, Pid} -> Pid; {error, {already_started, Pid}} -> Pid
{error, {already_started, Pid}} -> Pid end,
end,
gen_event:add_handler(EMPid, rabbit_mqtt_internal_event_handler, []), gen_event:add_handler(EMPid, rabbit_mqtt_internal_event_handler, []),
Result. Result.
@ -55,15 +52,9 @@ emit_connection_info_all(Nodes, Items, Ref, AggregatorPid) ->
%% remaining nodes, we send back 'finished' so that the CLI does not time out. %% remaining nodes, we send back 'finished' so that the CLI does not time out.
[AggregatorPid ! {Ref, finished} || _ <- lists:seq(1, length(Nodes) - 1)]; [AggregatorPid ! {Ref, finished} || _ <- lists:seq(1, length(Nodes) - 1)];
false -> false ->
Pids = [ Pids = [spawn_link(Node, ?MODULE, emit_connection_info_local,
spawn_link( [Items, Ref, AggregatorPid])
Node, || Node <- Nodes],
?MODULE,
emit_connection_info_local,
[Items, Ref, AggregatorPid]
)
|| Node <- Nodes
],
rabbit_control_misc:await_emitters_termination(Pids) rabbit_control_misc:await_emitters_termination(Pids)
end. end.
@ -74,23 +65,17 @@ emit_connection_info_local(Items, Ref, AggregatorPid) ->
emit_connection_info(Items, Ref, AggregatorPid, Pids) -> emit_connection_info(Items, Ref, AggregatorPid, Pids) ->
rabbit_control_misc:emitting_map_with_exit_handler( rabbit_control_misc:emitting_map_with_exit_handler(
AggregatorPid, AggregatorPid, Ref,
Ref, fun(Pid) ->
fun(Pid) -> rabbit_mqtt_reader:info(Pid, Items)
rabbit_mqtt_reader:info(Pid, Items) end, Pids).
end,
Pids
).
-spec close_local_client_connections(string() | binary()) -> {'ok', non_neg_integer()}. -spec close_local_client_connections(string() | binary()) -> {'ok', non_neg_integer()}.
close_local_client_connections(Reason) -> close_local_client_connections(Reason) ->
Pids = local_connection_pids(), Pids = local_connection_pids(),
lists:foreach( lists:foreach(fun(Pid) ->
fun(Pid) -> rabbit_mqtt_reader:close_connection(Pid, Reason)
rabbit_mqtt_reader:close_connection(Pid, Reason) end, Pids),
end,
Pids
),
{ok, length(Pids)}. {ok, length(Pids)}.
-spec local_connection_pids() -> [pid()]. -spec local_connection_pids() -> [pid()].
@ -101,12 +86,9 @@ local_connection_pids() ->
lists:filter(fun(Pid) -> node(Pid) =:= node() end, AllPids); lists:filter(fun(Pid) -> node(Pid) =:= node() end, AllPids);
false -> false ->
PgScope = persistent_term:get(?PG_SCOPE), PgScope = persistent_term:get(?PG_SCOPE),
lists:flatmap( lists:flatmap(fun(Group) ->
fun(Group) -> pg:get_local_members(PgScope, Group)
pg:get_local_members(PgScope, Group) end, pg:which_groups(PgScope))
end,
pg:which_groups(PgScope)
)
end. end.
init_global_counters() -> init_global_counters() ->

View File

@ -9,13 +9,8 @@
-include("mqtt_machine.hrl"). -include("mqtt_machine.hrl").
-export([ -export([register/2, register/3, unregister/2,
register/2, register/3, list/0, list_pids/0, leave/1]).
unregister/2,
list/0,
list_pids/0,
leave/1
]).
%%---------------------------------------------------------------------------- %%----------------------------------------------------------------------------
-spec register(term(), pid()) -> {ok, reference()} | {error, term()}. -spec register(term(), pid()) -> {ok, reference()} | {error, term()}.
@ -26,7 +21,7 @@ register(ClientId, Pid) ->
case ra:members(NodeId) of case ra:members(NodeId) of
{ok, _, Leader} -> {ok, _, Leader} ->
register(Leader, ClientId, Pid); register(Leader, ClientId, Pid);
_ = Error -> _ = Error ->
Error Error
end; end;
Leader -> Leader ->
@ -65,31 +60,25 @@ list(QF) ->
undefined -> undefined ->
NodeIds = mqtt_node:all_node_ids(), NodeIds = mqtt_node:all_node_ids(),
case ra:leader_query(NodeIds, QF) of case ra:leader_query(NodeIds, QF) of
{ok, {_, Result}, _} -> {ok, {_, Result}, _} -> Result;
Result; {timeout, _} ->
{timeout, _} -> rabbit_log:debug("~ts:list/1 leader query timed out",
rabbit_log:debug( [?MODULE]),
"~ts:list/1 leader query timed out",
[?MODULE]
),
[] []
end; end;
Leader -> Leader ->
case ra:leader_query(Leader, QF) of case ra:leader_query(Leader, QF) of
{ok, {_, Result}, _} -> {ok, {_, Result}, _} -> Result;
Result;
{error, _} -> {error, _} ->
[]; [];
{timeout, _} -> {timeout, _} ->
rabbit_log:debug( rabbit_log:debug("~ts:list/1 leader query timed out",
"~ts:list/1 leader query timed out", [?MODULE]),
[?MODULE]
),
[] []
end end
end. end.
-spec leave(binary()) -> ok | timeout | nodedown. -spec leave(binary()) -> ok | timeout | nodedown.
leave(NodeBin) -> leave(NodeBin) ->
Node = binary_to_atom(NodeBin, utf8), Node = binary_to_atom(NodeBin, utf8),
ServerId = mqtt_node:server_id(), ServerId = mqtt_node:server_id(),

View File

@ -10,15 +10,13 @@
-include("rabbit_mqtt_packet.hrl"). -include("rabbit_mqtt_packet.hrl").
-compile({no_auto_import, [size/1]}). -compile({no_auto_import, [size/1]}).
-export([ -export([init/0,
init/0, insert/3,
insert/3, confirm/3,
confirm/3, reject/2,
reject/2, remove_queue/2,
remove_queue/2, size/1,
size/1, contains/2]).
contains/2
]).
%% As done in OTP's sets module: %% As done in OTP's sets module:
%% Empty list is cheaper to serialize than atom. %% Empty list is cheaper to serialize than atom.
@ -41,32 +39,26 @@ contains(PktId, State) ->
maps:is_key(PktId, State). maps:is_key(PktId, State).
-spec insert(packet_id(), [queue_name()], state()) -> state(). -spec insert(packet_id(), [queue_name()], state()) -> state().
insert(PktId, QNames, State) when insert(PktId, QNames, State)
is_integer(PktId) andalso when is_integer(PktId) andalso
PktId > 0 andalso PktId > 0 andalso
not is_map_key(PktId, State) not is_map_key(PktId, State) ->
->
QMap = maps:from_keys(QNames, ?VALUE), QMap = maps:from_keys(QNames, ?VALUE),
maps:put(PktId, QMap, State). maps:put(PktId, QMap, State).
-spec confirm([packet_id()], queue_name(), state()) -> -spec confirm([packet_id()], queue_name(), state()) ->
{[packet_id()], state()}. {[packet_id()], state()}.
confirm(PktIds, QName, State0) -> confirm(PktIds, QName, State0) ->
{L0, State} = lists:foldl( {L0, State} = lists:foldl(fun(PktId, Acc) ->
fun(PktId, Acc) -> confirm_one(PktId, QName, Acc)
confirm_one(PktId, QName, Acc) end, {[], State0}, PktIds),
end,
{[], State0},
PktIds
),
L = lists:reverse(L0), L = lists:reverse(L0),
{L, State}. {L, State}.
-spec reject(packet_id(), state()) -> -spec reject(packet_id(), state()) ->
{ok, state()} | {error, not_found}. {ok, state()} | {error, not_found}.
reject(PktId, State0) when reject(PktId, State0)
is_integer(PktId) when is_integer(PktId) ->
->
case maps:take(PktId, State0) of case maps:take(PktId, State0) of
{_, State} -> {_, State} ->
{ok, State}; {ok, State};
@ -79,31 +71,24 @@ reject(PktId, State0) when
{[packet_id()], state()}. {[packet_id()], state()}.
remove_queue(QName, State) -> remove_queue(QName, State) ->
PktIds = maps:fold( PktIds = maps:fold(
fun fun(PktId, QMap, PktIds)
(PktId, QMap, PktIds) when when is_map_key(QName, QMap) ->
is_map_key(QName, QMap) [PktId | PktIds];
-> (_, _, PktIds) ->
[PktId | PktIds]; PktIds
(_, _, PktIds) -> end, [], State),
PktIds
end,
[],
State
),
confirm(lists:sort(PktIds), QName, State). confirm(lists:sort(PktIds), QName, State).
%% INTERNAL %% INTERNAL
confirm_one(PktId, QName, {PktIds, State0}) when confirm_one(PktId, QName, {PktIds, State0})
is_integer(PktId) when is_integer(PktId) ->
->
case maps:take(PktId, State0) of case maps:take(PktId, State0) of
{QMap0, State1} when {QMap0, State1}
is_map_key(QName, QMap0) andalso when is_map_key(QName, QMap0)
map_size(QMap0) =:= 1 andalso map_size(QMap0) =:= 1 ->
->
%% last queue confirm %% last queue confirm
{[PktId | PktIds], State1}; {[PktId| PktIds], State1};
{QMap0, State1} -> {QMap0, State1} ->
QMap = maps:remove(QName, QMap0), QMap = maps:remove(QName, QMap0),
State = maps:put(PktId, QMap, State1), State = maps:put(PktId, QMap, State1),

View File

@ -12,19 +12,17 @@
-export([track_client_id_in_ra/0]). -export([track_client_id_in_ra/0]).
-rabbit_feature_flag( -rabbit_feature_flag(
{?QUEUE_TYPE_QOS_0, #{ {?QUEUE_TYPE_QOS_0,
desc => "Support pseudo queue type for MQTT QoS 0 subscribers omitting a queue process", #{desc => "Support pseudo queue type for MQTT QoS 0 subscribers omitting a queue process",
stability => stable stability => stable
}} }}).
).
-rabbit_feature_flag( -rabbit_feature_flag(
{delete_ra_cluster_mqtt_node, #{ {delete_ra_cluster_mqtt_node,
desc => "Delete Ra cluster 'mqtt_node' since MQTT client IDs are tracked locally", #{desc => "Delete Ra cluster 'mqtt_node' since MQTT client IDs are tracked locally",
stability => stable, stability => stable,
callbacks => #{enable => {mqtt_node, delete}} callbacks => #{enable => {mqtt_node, delete}}
}} }}).
).
-spec track_client_id_in_ra() -> boolean(). -spec track_client_id_in_ra() -> boolean().
track_client_id_in_ra() -> track_client_id_in_ra() ->

View File

@ -28,9 +28,7 @@ handle_event({event, vhost_deleted, Info, _, _}, ?STATE) ->
{ok, ?STATE}; {ok, ?STATE};
handle_event({event, maintenance_connections_closed, _Info, _, _}, ?STATE) -> handle_event({event, maintenance_connections_closed, _Info, _, _}, ?STATE) ->
%% we should close our connections %% we should close our connections
{ok, NConnections} = rabbit_mqtt:close_local_client_connections( {ok, NConnections} = rabbit_mqtt:close_local_client_connections("node is being put into maintenance mode"),
"node is being put into maintenance mode"
),
rabbit_log:warning("Closed ~b local MQTT client connections", [NConnections]), rabbit_log:warning("Closed ~b local MQTT client connections", [NConnections]),
{ok, ?STATE}; {ok, ?STATE};
handle_event(_Event, ?STATE) -> handle_event(_Event, ?STATE) ->

View File

@ -1,26 +1,23 @@
-module(rabbit_mqtt_keepalive). -module(rabbit_mqtt_keepalive).
-export([ -export([init/0,
init/0, start/2,
start/2, handle/2,
handle/2, start_timer/1,
start_timer/1, cancel_timer/1,
cancel_timer/1, interval_secs/1]).
interval_secs/1
]).
-export_type([state/0]). -export_type([state/0]).
-record(state, { -record(state, {
%% Keep Alive value as sent in the CONNECT packet. %% Keep Alive value as sent in the CONNECT packet.
interval_secs :: pos_integer(), interval_secs :: pos_integer(),
timer :: reference(), timer :: reference(),
socket :: inet:socket(), socket :: inet:socket(),
recv_oct :: non_neg_integer(), recv_oct :: non_neg_integer(),
received :: boolean() received :: boolean()}).
}).
-opaque state() :: disabled | #state{}. -opaque(state() :: disabled | #state{}).
-spec init() -> state(). -spec init() -> state().
init() -> init() ->
@ -29,9 +26,8 @@ init() ->
-spec start(IntervalSeconds :: non_neg_integer(), inet:socket()) -> ok. -spec start(IntervalSeconds :: non_neg_integer(), inet:socket()) -> ok.
start(0, _Sock) -> start(0, _Sock) ->
ok; ok;
start(Seconds, Sock) when start(Seconds, Sock)
is_integer(Seconds) andalso Seconds > 0 when is_integer(Seconds) andalso Seconds > 0 ->
->
self() ! {keepalive, {init, Seconds, Sock}}, self() ! {keepalive, {init, Seconds, Sock}},
ok. ok.
@ -40,28 +36,20 @@ start(Seconds, Sock) when
handle({init, IntervalSecs, Sock}, _State) -> handle({init, IntervalSecs, Sock}, _State) ->
case rabbit_net:getstat(Sock, [recv_oct]) of case rabbit_net:getstat(Sock, [recv_oct]) of
{ok, [{recv_oct, RecvOct}]} -> {ok, [{recv_oct, RecvOct}]} ->
{ok, #state{ {ok, #state{interval_secs = IntervalSecs,
interval_secs = IntervalSecs, timer = start_timer0(IntervalSecs),
timer = start_timer0(IntervalSecs), socket = Sock,
socket = Sock, recv_oct = RecvOct,
recv_oct = RecvOct, received = true}};
received = true
}};
{error, _} = Err -> {error, _} = Err ->
Err Err
end; end;
handle( handle(check, State = #state{socket = Sock,
check, recv_oct = SameRecvOct,
State = #state{ received = ReceivedPreviously}) ->
socket = Sock,
recv_oct = SameRecvOct,
received = ReceivedPreviously
}
) ->
case rabbit_net:getstat(Sock, [recv_oct]) of case rabbit_net:getstat(Sock, [recv_oct]) of
{ok, [{recv_oct, SameRecvOct}]} when {ok, [{recv_oct, SameRecvOct}]}
ReceivedPreviously when ReceivedPreviously ->
->
%% Did not receive from socket for the 1st time. %% Did not receive from socket for the 1st time.
{ok, start_timer(State#state{received = false})}; {ok, start_timer(State#state{received = false})};
{ok, [{recv_oct, SameRecvOct}]} -> {ok, [{recv_oct, SameRecvOct}]} ->
@ -69,11 +57,8 @@ handle(
{error, timeout}; {error, timeout};
{ok, [{recv_oct, NewRecvOct}]} -> {ok, [{recv_oct, NewRecvOct}]} ->
%% Received from socket. %% Received from socket.
{ok, {ok, start_timer(State#state{recv_oct = NewRecvOct,
start_timer(State#state{ received = true})};
recv_oct = NewRecvOct,
received = true
})};
{error, _} = Err -> {error, _} = Err ->
Err Err
end. end.
@ -89,13 +74,10 @@ start_timer0(KeepAliveSeconds) ->
erlang:send_after(timer_ms(KeepAliveSeconds), self(), {keepalive, check}). erlang:send_after(timer_ms(KeepAliveSeconds), self(), {keepalive, check}).
-spec cancel_timer(state()) -> state(). -spec cancel_timer(state()) -> state().
cancel_timer(#state{timer = Ref} = State) when cancel_timer(#state{timer = Ref} = State)
is_reference(Ref) when is_reference(Ref) ->
-> ok = erlang:cancel_timer(Ref, [{async, true},
ok = erlang:cancel_timer(Ref, [ {info, false}]),
{async, true},
{info, false}
]),
State; State;
cancel_timer(disabled) -> cancel_timer(disabled) ->
disabled. disabled.

View File

@ -24,148 +24,119 @@
initial_state() -> none. initial_state() -> none.
-spec parse(binary(), state()) -> -spec parse(binary(), state()) ->
{more, state()} {more, state()} |
| {ok, mqtt_packet(), binary()} {ok, mqtt_packet(), binary()} |
| {error, any()}. {error, any()}.
parse(<<>>, none) -> parse(<<>>, none) ->
{more, fun(Bin) -> parse(Bin, none) end}; {more, fun(Bin) -> parse(Bin, none) end};
parse(<<MessageType:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, none) -> parse(<<MessageType:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, none) ->
parse_remaining_len(Rest, #mqtt_packet_fixed{ parse_remaining_len(Rest, #mqtt_packet_fixed{ type = MessageType,
type = MessageType, dup = bool(Dup),
dup = bool(Dup), qos = QoS,
qos = QoS, retain = bool(Retain) });
retain = bool(Retain) parse(Bin, Cont) -> Cont(Bin).
});
parse(Bin, Cont) ->
Cont(Bin).
parse_remaining_len(<<>>, Fixed) -> parse_remaining_len(<<>>, Fixed) ->
{more, fun(Bin) -> parse_remaining_len(Bin, Fixed) end}; {more, fun(Bin) -> parse_remaining_len(Bin, Fixed) end};
parse_remaining_len(Rest, Fixed) -> parse_remaining_len(Rest, Fixed) ->
parse_remaining_len(Rest, Fixed, 1, 0). parse_remaining_len(Rest, Fixed, 1, 0).
parse_remaining_len(_Bin, _Fixed, _Multiplier, Length) when parse_remaining_len(_Bin, _Fixed, _Multiplier, Length)
Length > ?MAX_LEN when Length > ?MAX_LEN ->
->
{error, invalid_mqtt_packet_len}; {error, invalid_mqtt_packet_len};
parse_remaining_len(<<>>, Fixed, Multiplier, Length) -> parse_remaining_len(<<>>, Fixed, Multiplier, Length) ->
{more, fun(Bin) -> parse_remaining_len(Bin, Fixed, Multiplier, Length) end}; {more, fun(Bin) -> parse_remaining_len(Bin, Fixed, Multiplier, Length) end};
parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) -> parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
parse_remaining_len(Rest, Fixed, Multiplier * ?HIGHBIT, Value + Len * Multiplier); parse_remaining_len(Rest, Fixed, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) -> parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
parse_packet(Rest, Fixed, Value + Len * Multiplier). parse_packet(Rest, Fixed, Value + Len * Multiplier).
parse_packet( parse_packet(Bin, #mqtt_packet_fixed{ type = Type,
Bin, qos = Qos } = Fixed, Length)
#mqtt_packet_fixed{ when Length =< ?MAX_LEN ->
type = Type,
qos = Qos
} = Fixed,
Length
) when
Length =< ?MAX_LEN
->
case {Type, Bin} of case {Type, Bin} of
{?CONNECT, <<PacketBin:Length/binary, Rest/binary>>} -> {?CONNECT, <<PacketBin:Length/binary, Rest/binary>>} ->
{ProtoName, Rest1} = parse_utf(PacketBin), {ProtoName, Rest1} = parse_utf(PacketBin),
<<ProtoVersion:8, Rest2/binary>> = Rest1, <<ProtoVersion : 8, Rest2/binary>> = Rest1,
<<UsernameFlag:1, PasswordFlag:1, WillRetain:1, WillQos:2, WillFlag:1, CleanSession:1, <<UsernameFlag : 1,
_Reserved:1, KeepAlive:16/big, Rest3/binary>> = Rest2, PasswordFlag : 1,
{ClientId, Rest4} = parse_utf(Rest3), WillRetain : 1,
WillQos : 2,
WillFlag : 1,
CleanSession : 1,
_Reserved : 1,
KeepAlive : 16/big,
Rest3/binary>> = Rest2,
{ClientId, Rest4} = parse_utf(Rest3),
{WillTopic, Rest5} = parse_utf(Rest4, WillFlag), {WillTopic, Rest5} = parse_utf(Rest4, WillFlag),
{WillMsg, Rest6} = parse_msg(Rest5, WillFlag), {WillMsg, Rest6} = parse_msg(Rest5, WillFlag),
{UserName, Rest7} = parse_utf(Rest6, UsernameFlag), {UserName, Rest7} = parse_utf(Rest6, UsernameFlag),
{PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag), {PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag),
case protocol_name_approved(ProtoVersion, ProtoName) of case protocol_name_approved(ProtoVersion, ProtoName) of
true -> true ->
wrap( wrap(Fixed,
Fixed, #mqtt_packet_connect{
#mqtt_packet_connect{ proto_ver = ProtoVersion,
proto_ver = ProtoVersion, will_retain = bool(WillRetain),
will_retain = bool(WillRetain), will_qos = WillQos,
will_qos = WillQos, will_flag = bool(WillFlag),
will_flag = bool(WillFlag), clean_sess = bool(CleanSession),
clean_sess = bool(CleanSession), keep_alive = KeepAlive,
keep_alive = KeepAlive, client_id = ClientId,
client_id = ClientId, will_topic = WillTopic,
will_topic = WillTopic, will_msg = WillMsg,
will_msg = WillMsg, username = UserName,
username = UserName, password = PasssWord}, Rest);
password = PasssWord false ->
},
Rest
);
false ->
{error, protocol_header_corrupt} {error, protocol_header_corrupt}
end; end;
{?PUBLISH, <<PacketBin:Length/binary, Rest/binary>>} -> {?PUBLISH, <<PacketBin:Length/binary, Rest/binary>>} ->
{TopicName, Rest1} = parse_utf(PacketBin), {TopicName, Rest1} = parse_utf(PacketBin),
{PacketId, Payload} = {PacketId, Payload} = case Qos of
case Qos of 0 -> {undefined, Rest1};
0 -> _ -> <<M:16/big, R/binary>> = Rest1,
{undefined, Rest1}; {M, R}
_ -> end,
<<M:16/big, R/binary>> = Rest1, wrap(Fixed, #mqtt_packet_publish { topic_name = TopicName,
{M, R} packet_id = PacketId },
end, Payload, Rest);
wrap(
Fixed,
#mqtt_packet_publish{
topic_name = TopicName,
packet_id = PacketId
},
Payload,
Rest
);
{?PUBACK, <<PacketBin:Length/binary, Rest/binary>>} -> {?PUBACK, <<PacketBin:Length/binary, Rest/binary>>} ->
<<PacketId:16/big>> = PacketBin, <<PacketId:16/big>> = PacketBin,
wrap(Fixed, #mqtt_packet_publish{packet_id = PacketId}, Rest); wrap(Fixed, #mqtt_packet_publish { packet_id = PacketId }, Rest);
{Subs, <<PacketBin:Length/binary, Rest/binary>>} when {Subs, <<PacketBin:Length/binary, Rest/binary>>}
Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE when Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE ->
->
1 = Qos, 1 = Qos,
<<PacketId:16/big, Rest1/binary>> = PacketBin, <<PacketId:16/big, Rest1/binary>> = PacketBin,
Topics = parse_topics(Subs, Rest1, []), Topics = parse_topics(Subs, Rest1, []),
wrap( wrap(Fixed, #mqtt_packet_subscribe { packet_id = PacketId,
Fixed, topic_table = Topics }, Rest);
#mqtt_packet_subscribe{ {Minimal, Rest}
packet_id = PacketId, when Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ ->
topic_table = Topics
},
Rest
);
{Minimal, Rest} when
Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ
->
Length = 0, Length = 0,
wrap(Fixed, Rest); wrap(Fixed, Rest);
{_, TooShortBin} when {_, TooShortBin}
byte_size(TooShortBin) < Length when byte_size(TooShortBin) < Length ->
->
{more, fun(BinMore) -> {more, fun(BinMore) ->
parse_packet( parse_packet(<<TooShortBin/binary, BinMore/binary>>,
<<TooShortBin/binary, BinMore/binary>>, Fixed, Length)
Fixed, end}
Length
)
end}
end. end.
parse_topics(_, <<>>, Topics) -> parse_topics(_, <<>>, Topics) ->
Topics; Topics;
parse_topics(?SUBSCRIBE = Sub, Bin, Topics) -> parse_topics(?SUBSCRIBE = Sub, Bin, Topics) ->
{Name, <<_:6, QoS:2, Rest/binary>>} = parse_utf(Bin), {Name, <<_:6, QoS:2, Rest/binary>>} = parse_utf(Bin),
parse_topics(Sub, Rest, [#mqtt_topic{name = Name, qos = QoS} | Topics]); parse_topics(Sub, Rest, [#mqtt_topic { name = Name, qos = QoS } | Topics]);
parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) -> parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) ->
{Name, <<Rest/binary>>} = parse_utf(Bin), {Name, <<Rest/binary>>} = parse_utf(Bin),
parse_topics(Sub, Rest, [#mqtt_topic{name = Name} | Topics]). parse_topics(Sub, Rest, [#mqtt_topic { name = Name } | Topics]).
wrap(Fixed, Variable, Payload, Rest) -> wrap(Fixed, Variable, Payload, Rest) ->
{ok, #mqtt_packet{variable = Variable, fixed = Fixed, payload = Payload}, Rest}. {ok, #mqtt_packet { variable = Variable, fixed = Fixed, payload = Payload }, Rest}.
wrap(Fixed, Variable, Rest) -> wrap(Fixed, Variable, Rest) ->
{ok, #mqtt_packet{variable = Variable, fixed = Fixed}, Rest}. {ok, #mqtt_packet { variable = Variable, fixed = Fixed }, Rest}.
wrap(Fixed, Rest) -> wrap(Fixed, Rest) ->
{ok, #mqtt_packet{fixed = Fixed}, Rest}. {ok, #mqtt_packet { fixed = Fixed }, Rest}.
parse_utf(Bin, 0) -> parse_utf(Bin, 0) ->
{undefined, Bin}; {undefined, Bin};
@ -187,109 +158,72 @@ bool(1) -> true.
-spec serialise(#mqtt_packet{}, ?MQTT_PROTO_V3 | ?MQTT_PROTO_V4) -> -spec serialise(#mqtt_packet{}, ?MQTT_PROTO_V3 | ?MQTT_PROTO_V4) ->
iodata(). iodata().
serialise( serialise(#mqtt_packet{fixed = Fixed,
#mqtt_packet{ variable = Variable,
fixed = Fixed, payload = Payload}, Vsn) ->
variable = Variable,
payload = Payload
},
Vsn
) ->
serialise_variable(Fixed, Variable, serialise_payload(Payload), Vsn). serialise_variable(Fixed, Variable, serialise_payload(Payload), Vsn).
serialise_payload(undefined) -> serialise_payload(undefined) ->
<<>>; <<>>;
serialise_payload(P) when serialise_payload(P)
is_binary(P) orelse is_list(P) when is_binary(P) orelse is_list(P) ->
->
P. P.
serialise_variable( serialise_variable(#mqtt_packet_fixed { type = ?CONNACK } = Fixed,
#mqtt_packet_fixed{type = ?CONNACK} = Fixed, #mqtt_packet_connack { session_present = SessionPresent,
#mqtt_packet_connack{ return_code = ReturnCode },
session_present = SessionPresent, <<>> = PayloadBin, _Vsn) ->
return_code = ReturnCode
},
<<>> = PayloadBin,
_Vsn
) ->
VariableBin = <<?RESERVED:7, (opt(SessionPresent)):1, ReturnCode:8>>, VariableBin = <<?RESERVED:7, (opt(SessionPresent)):1, ReturnCode:8>>,
serialise_fixed(Fixed, VariableBin, PayloadBin); serialise_fixed(Fixed, VariableBin, PayloadBin);
serialise_variable(
#mqtt_packet_fixed{type = SubAck} = Fixed, serialise_variable(#mqtt_packet_fixed { type = SubAck } = Fixed,
#mqtt_packet_suback{ #mqtt_packet_suback { packet_id = PacketId,
packet_id = PacketId, qos_table = Qos },
qos_table = Qos <<>> = _PayloadBin, Vsn)
}, when SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK ->
<<>> = _PayloadBin,
Vsn
) when
SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK
->
VariableBin = <<PacketId:16/big>>, VariableBin = <<PacketId:16/big>>,
QosBin = QosBin = case Vsn of
case Vsn of ?MQTT_PROTO_V3 ->
?MQTT_PROTO_V3 -> << <<?RESERVED:6, Q:2>> || Q <- Qos >>;
<<<<?RESERVED:6, Q:2>> || Q <- Qos>>; ?MQTT_PROTO_V4 ->
?MQTT_PROTO_V4 -> %% Allow error code (0x80) in the MQTT SUBACK message.
%% Allow error code (0x80) in the MQTT SUBACK message. << <<Q:8>> || Q <- Qos >>
<<<<Q:8>> || Q <- Qos>> end,
end,
serialise_fixed(Fixed, VariableBin, QosBin); serialise_fixed(Fixed, VariableBin, QosBin);
serialise_variable(
#mqtt_packet_fixed{ serialise_variable(#mqtt_packet_fixed { type = ?PUBLISH,
type = ?PUBLISH, qos = Qos } = Fixed,
qos = Qos #mqtt_packet_publish { topic_name = TopicName,
} = Fixed, packet_id = PacketId },
#mqtt_packet_publish{ Payload, _Vsn) ->
topic_name = TopicName,
packet_id = PacketId
},
Payload,
_Vsn
) ->
TopicBin = serialise_utf(TopicName), TopicBin = serialise_utf(TopicName),
PacketIdBin = PacketIdBin = case Qos of
case Qos of 0 -> <<>>;
0 -> <<>>; 1 -> <<PacketId:16/big>>
1 -> <<PacketId:16/big>> end,
end,
serialise_fixed(Fixed, <<TopicBin/binary, PacketIdBin/binary>>, Payload); serialise_fixed(Fixed, <<TopicBin/binary, PacketIdBin/binary>>, Payload);
serialise_variable(
#mqtt_packet_fixed{type = ?PUBACK} = Fixed, serialise_variable(#mqtt_packet_fixed { type = ?PUBACK } = Fixed,
#mqtt_packet_publish{packet_id = PacketId}, #mqtt_packet_publish { packet_id = PacketId },
PayloadBin, PayloadBin, _Vsn) ->
_Vsn
) ->
PacketIdBin = <<PacketId:16/big>>, PacketIdBin = <<PacketId:16/big>>,
serialise_fixed(Fixed, PacketIdBin, PayloadBin); serialise_fixed(Fixed, PacketIdBin, PayloadBin);
serialise_variable(
#mqtt_packet_fixed{} = Fixed, serialise_variable(#mqtt_packet_fixed {} = Fixed,
undefined, undefined,
<<>> = _PayloadBin, <<>> = _PayloadBin, _Vsn) ->
_Vsn
) ->
serialise_fixed(Fixed, <<>>, <<>>). serialise_fixed(Fixed, <<>>, <<>>).
serialise_fixed( serialise_fixed(#mqtt_packet_fixed{ type = Type,
#mqtt_packet_fixed{ dup = Dup,
type = Type, qos = Qos,
dup = Dup, retain = Retain }, VariableBin, Payload)
qos = Qos, when is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT ->
retain = Retain
},
VariableBin,
Payload
) when
is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT
->
Len = size(VariableBin) + iolist_size(Payload), Len = size(VariableBin) + iolist_size(Payload),
true = (Len =< ?MAX_LEN), true = (Len =< ?MAX_LEN),
LenBin = serialise_len(Len), LenBin = serialise_len(Len),
[ [<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1,
<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1, LenBin/binary, VariableBin/binary>>, LenBin/binary, VariableBin/binary>>, Payload].
Payload
].
serialise_utf(String) -> serialise_utf(String) ->
StringBin = unicode:characters_to_binary(String), StringBin = unicode:characters_to_binary(String),
@ -302,9 +236,9 @@ serialise_len(N) when N =< ?LOWBITS ->
serialise_len(N) -> serialise_len(N) ->
<<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>. <<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>.
opt(undefined) -> ?RESERVED; opt(undefined) -> ?RESERVED;
opt(false) -> 0; opt(false) -> 0;
opt(true) -> 1; opt(true) -> 1;
opt(X) when is_integer(X) -> X. opt(X) when is_integer(X) -> X.
protocol_name_approved(Ver, Name) -> protocol_name_approved(Ver, Name) ->

File diff suppressed because it is too large Load Diff

View File

@ -24,50 +24,42 @@
%% Stateless rabbit_queue_type callbacks. %% Stateless rabbit_queue_type callbacks.
-export([ -export([
is_stateful/0, is_stateful/0,
declare/2, declare/2,
delete/4, delete/4,
deliver/2, deliver/2,
is_enabled/0, is_enabled/0,
is_compatible/3, is_compatible/3,
is_recoverable/1, is_recoverable/1,
recover/2, recover/2,
purge/1, purge/1,
policy_changed/1, policy_changed/1,
info/2, info/2,
stat/1, stat/1,
capabilities/0, capabilities/0,
notify_decorators/1 notify_decorators/1
]). ]).
%% Stateful rabbit_queue_type callbacks are unsupported by this queue type. %% Stateful rabbit_queue_type callbacks are unsupported by this queue type.
-define(STATEFUL_CALLBACKS, [ -define(STATEFUL_CALLBACKS,
init/1, [
close/1, init/1,
update/2, close/1,
consume/3, update/2,
cancel/5, consume/3,
handle_event/3, cancel/5,
settle/5, handle_event/3,
credit/5, settle/5,
dequeue/5, credit/5,
state_info/1 dequeue/5,
]). state_info/1
]).
-export(?STATEFUL_CALLBACKS). -export(?STATEFUL_CALLBACKS).
-dialyzer({nowarn_function, ?STATEFUL_CALLBACKS}). -dialyzer({nowarn_function, ?STATEFUL_CALLBACKS}).
-define(UNSUPPORTED(Args), erlang:error(unsupported, Args)). -define(UNSUPPORTED(Args), erlang:error(unsupported, Args)).
-define(INFO_KEYS, [ -define(INFO_KEYS, [type, name, durable, auto_delete, arguments,
type, pid, owner_pid, state, messages]).
name,
durable,
auto_delete,
arguments,
pid,
owner_pid,
state,
messages
]).
-spec is_stateful() -> -spec is_stateful() ->
boolean(). boolean().
@ -75,8 +67,8 @@ is_stateful() ->
false. false.
-spec declare(amqqueue:amqqueue(), node()) -> -spec declare(amqqueue:amqqueue(), node()) ->
{'new' | 'existing' | 'owner_died', amqqueue:amqqueue()} {'new' | 'existing' | 'owner_died', amqqueue:amqqueue()} |
| {'absent', amqqueue:amqqueue(), rabbit_amqqueue:absent_reason()}. {'absent', amqqueue:amqqueue(), rabbit_amqqueue:absent_reason()}.
declare(Q0, _Node) -> declare(Q0, _Node) ->
%% The queue gets persisted such that routing to this %% The queue gets persisted such that routing to this
%% queue (via the topic exchange) works as usual. %% queue (via the topic exchange) works as usual.
@ -84,29 +76,23 @@ declare(Q0, _Node) ->
{created, Q} -> {created, Q} ->
Opts = amqqueue:get_options(Q), Opts = amqqueue:get_options(Q),
ActingUser = maps:get(user, Opts, ?UNKNOWN_USER), ActingUser = maps:get(user, Opts, ?UNKNOWN_USER),
rabbit_event:notify( rabbit_event:notify(queue_created,
queue_created, [{name, amqqueue:get_name(Q0)},
[ {durable, true},
{name, amqqueue:get_name(Q0)}, {auto_delete, false},
{durable, true}, {exclusive, true},
{auto_delete, false}, {type, amqqueue:get_type(Q0)},
{exclusive, true}, {arguments, amqqueue:get_arguments(Q0)},
{type, amqqueue:get_type(Q0)}, {user_who_performed_action, ActingUser}]),
{arguments, amqqueue:get_arguments(Q0)},
{user_who_performed_action, ActingUser}
]
),
{new, Q}; {new, Q};
Other -> Other ->
Other Other
end. end.
-spec delete( -spec delete(amqqueue:amqqueue(),
amqqueue:amqqueue(), boolean(),
boolean(), boolean(),
boolean(), rabbit_types:username()) ->
rabbit_types:username()
) ->
rabbit_types:ok(non_neg_integer()). rabbit_types:ok(non_neg_integer()).
delete(Q, _IfUnused, _IfEmpty, ActingUser) -> delete(Q, _IfUnused, _IfEmpty, ActingUser) ->
QName = amqqueue:get_name(Q), QName = amqqueue:get_name(Q),
@ -116,41 +102,35 @@ delete(Q, _IfUnused, _IfEmpty, ActingUser) ->
-spec deliver([{amqqueue:amqqueue(), stateless}], Delivery :: term()) -> -spec deliver([{amqqueue:amqqueue(), stateless}], Delivery :: term()) ->
{[], rabbit_queue_type:actions()}. {[], rabbit_queue_type:actions()}.
deliver(Qs, #delivery{ deliver(Qs, #delivery{message = BasicMessage,
message = BasicMessage, confirm = Confirm,
confirm = Confirm, msg_seq_no = SeqNo}) ->
msg_seq_no = SeqNo Msg = {queue_event, ?MODULE,
}) -> {?MODULE, _QPid = none, _QMsgId = none, _Redelivered = false, BasicMessage}},
Msg =
{queue_event, ?MODULE,
{?MODULE, _QPid = none, _QMsgId = none, _Redelivered = false, BasicMessage}},
{Pids, Actions} = {Pids, Actions} =
case Confirm of case Confirm of
false -> false ->
Pids0 = lists:map(fun({Q, stateless}) -> amqqueue:get_pid(Q) end, Qs), Pids0 = lists:map(fun({Q, stateless}) -> amqqueue:get_pid(Q) end, Qs),
{Pids0, []}; {Pids0, []};
true -> true ->
%% We confirm the message directly here in the queue client. %% We confirm the message directly here in the queue client.
%% Alternatively, we could have the target MQTT connection process confirm the message. %% Alternatively, we could have the target MQTT connection process confirm the message.
%% However, given that this message might be lost anyway between target MQTT connection %% However, given that this message might be lost anyway between target MQTT connection
%% process and MQTT subscriber, and we know that the MQTT subscriber wants to receive %% process and MQTT subscriber, and we know that the MQTT subscriber wants to receive
%% this message at most once, we confirm here directly. %% this message at most once, we confirm here directly.
%% Benefits: %% Benefits:
%% 1. We do not block sending the confirmation back to the publishing client just because a single %% 1. We do not block sending the confirmation back to the publishing client just because a single
%% (at-most-once) target queue out of potentially many (e.g. million) queues might be unavailable. %% (at-most-once) target queue out of potentially many (e.g. million) queues might be unavailable.
%% 2. Memory usage in this (publishing) process is kept lower because the target queue name can be %% 2. Memory usage in this (publishing) process is kept lower because the target queue name can be
%% directly removed from rabbit_mqtt_confirms and rabbit_confirms. %% directly removed from rabbit_mqtt_confirms and rabbit_confirms.
%% 3. Reduced network traffic across RabbitMQ nodes. %% 3. Reduced network traffic across RabbitMQ nodes.
%% 4. Lower latency of sending publisher confirmation back to the publishing client. %% 4. Lower latency of sending publisher confirmation back to the publishing client.
SeqNos = [SeqNo], SeqNos = [SeqNo],
lists:mapfoldl( lists:mapfoldl(fun({Q, stateless}, Actions) ->
fun({Q, stateless}, Actions) -> {amqqueue:get_pid(Q),
{amqqueue:get_pid(Q), [{settled, amqqueue:get_name(Q), SeqNos} | Actions]} [{settled, amqqueue:get_name(Q), SeqNos} | Actions]}
end, end, [], Qs)
[], end,
Qs
)
end,
delegate:invoke_no_result(Pids, {gen_server, cast, [Msg]}), delegate:invoke_no_result(Pids, {gen_server, cast, [Msg]}),
{[], Actions}. {[], Actions}.
@ -172,8 +152,8 @@ is_recoverable(Q) ->
Pid = amqqueue:get_pid(Q), Pid = amqqueue:get_pid(Q),
OwnerPid = amqqueue:get_exclusive_owner(Q), OwnerPid = amqqueue:get_exclusive_owner(Q),
node() =:= node(Pid) andalso node() =:= node(Pid) andalso
Pid =:= OwnerPid andalso Pid =:= OwnerPid andalso
not is_process_alive(Pid). not is_process_alive(Pid).
%% We (mis)use the recover callback to clean up our exclusive queues %% We (mis)use the recover callback to clean up our exclusive queues
%% which otherwise do not get cleaned up after a node crash. %% which otherwise do not get cleaned up after a node crash.
@ -181,24 +161,20 @@ is_recoverable(Q) ->
{Recovered :: [amqqueue:amqqueue()], Failed :: [amqqueue:amqqueue()]}. {Recovered :: [amqqueue:amqqueue()], Failed :: [amqqueue:amqqueue()]}.
recover(_VHost, Queues) -> recover(_VHost, Queues) ->
lists:foreach( lists:foreach(
fun(Q) -> fun(Q) ->
%% sanity check %% sanity check
true = is_recoverable(Q), true = is_recoverable(Q),
QName = amqqueue:get_name(Q), QName = amqqueue:get_name(Q),
log_delete(QName, amqqueue:get_exclusive_owner(Q)), log_delete(QName, amqqueue:get_exclusive_owner(Q)),
rabbit_amqqueue:internal_delete(QName, ?INTERNAL_USER) rabbit_amqqueue:internal_delete(QName, ?INTERNAL_USER)
end, end, Queues),
Queues
),
%% We mark the queue recovery as failed because these queues are not really %% We mark the queue recovery as failed because these queues are not really
%% recovered, but deleted. %% recovered, but deleted.
{[], Queues}. {[], Queues}.
log_delete(QName, ConPid) -> log_delete(QName, ConPid) ->
rabbit_log_queue:debug( rabbit_log_queue:debug("Deleting ~s of type ~s because its declaring connection ~tp was closed",
"Deleting ~s of type ~s because its declaring connection ~tp was closed", [rabbit_misc:rs(QName), ?MODULE, ConPid]).
[rabbit_misc:rs(QName), ?MODULE, ConPid]
).
-spec purge(amqqueue:amqqueue()) -> -spec purge(amqqueue:amqqueue()) ->
{ok, non_neg_integer()}. {ok, non_neg_integer()}.
@ -227,13 +203,11 @@ capabilities() ->
-spec info(amqqueue:amqqueue(), all_keys | rabbit_types:info_keys()) -> -spec info(amqqueue:amqqueue(), all_keys | rabbit_types:info_keys()) ->
rabbit_types:infos(). rabbit_types:infos().
info(Q, all_keys) when info(Q, all_keys)
?is_amqqueue(Q) when ?is_amqqueue(Q) ->
->
info(Q, ?INFO_KEYS); info(Q, ?INFO_KEYS);
info(Q, Items) when info(Q, Items)
?is_amqqueue(Q) when ?is_amqqueue(Q) ->
->
[{Item, i(Item, Q)} || Item <- Items]. [{Item, i(Item, Q)} || Item <- Items].
i(type, _) -> i(type, _) ->
@ -275,26 +249,26 @@ init(A1) ->
close(A1) -> close(A1) ->
?UNSUPPORTED([A1]). ?UNSUPPORTED([A1]).
update(A1, A2) -> update(A1,A2) ->
?UNSUPPORTED([A1, A2]). ?UNSUPPORTED([A1,A2]).
consume(A1, A2, A3) -> consume(A1,A2,A3) ->
?UNSUPPORTED([A1, A2, A3]). ?UNSUPPORTED([A1,A2,A3]).
cancel(A1, A2, A3, A4, A5) -> cancel(A1,A2,A3,A4,A5) ->
?UNSUPPORTED([A1, A2, A3, A4, A5]). ?UNSUPPORTED([A1,A2,A3,A4,A5]).
handle_event(A1, A2, A3) -> handle_event(A1,A2,A3) ->
?UNSUPPORTED([A1, A2, A3]). ?UNSUPPORTED([A1,A2,A3]).
settle(A1, A2, A3, A4, A5) -> settle(A1,A2,A3,A4,A5) ->
?UNSUPPORTED([A1, A2, A3, A4, A5]). ?UNSUPPORTED([A1,A2,A3,A4,A5]).
credit(A1, A2, A3, A4, A5) -> credit(A1,A2,A3,A4,A5) ->
?UNSUPPORTED([A1, A2, A3, A4, A5]). ?UNSUPPORTED([A1,A2,A3,A4,A5]).
dequeue(A1, A2, A3, A4, A5) -> dequeue(A1,A2,A3,A4,A5) ->
?UNSUPPORTED([A1, A2, A3, A4, A5]). ?UNSUPPORTED([A1,A2,A3,A4,A5]).
state_info(A1) -> state_info(A1) ->
?UNSUPPORTED([A1]). ?UNSUPPORTED([A1]).

View File

@ -14,20 +14,11 @@
-include_lib("rabbit_common/include/logging.hrl"). -include_lib("rabbit_common/include/logging.hrl").
-export([start_link/3]). -export([start_link/3]).
-export([ -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
init/1, code_change/3, terminate/2, format_status/1]).
handle_call/3,
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2,
format_status/1
]).
-export([ -export([conserve_resources/3,
conserve_resources/3, close_connection/2]).
close_connection/2
]).
-export([info/2]). -export([info/2]).
@ -38,22 +29,22 @@
-define(HIBERNATE_AFTER, 1000). -define(HIBERNATE_AFTER, 1000).
-define(PROTO_FAMILY, 'MQTT'). -define(PROTO_FAMILY, 'MQTT').
-record(state, { -record(state,
socket :: rabbit_net:socket(), {socket :: rabbit_net:socket(),
proxy_socket :: option({rabbit_proxy_socket, any(), any()}), proxy_socket :: option({rabbit_proxy_socket, any(), any()}),
await_recv :: boolean(), await_recv :: boolean(),
deferred_recv :: option(binary()), deferred_recv :: option(binary()),
parse_state :: rabbit_mqtt_packet:state(), parse_state :: rabbit_mqtt_packet:state(),
proc_state :: rabbit_mqtt_processor:state(), proc_state :: rabbit_mqtt_processor:state(),
connection_state :: running | blocked, connection_state :: running | blocked,
conserve :: boolean(), conserve :: boolean(),
stats_timer :: option(rabbit_event:state()), stats_timer :: option(rabbit_event:state()),
keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(), keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
conn_name :: binary(), conn_name :: binary(),
received_connect_packet :: boolean() received_connect_packet :: boolean()
}). }).
-type state() :: #state{}. -type(state() :: #state{}).
%%---------------------------------------------------------------------------- %%----------------------------------------------------------------------------
@ -61,11 +52,9 @@ start_link(Ref, _Transport, []) ->
Pid = proc_lib:spawn_link(?MODULE, init, [Ref]), Pid = proc_lib:spawn_link(?MODULE, init, [Ref]),
{ok, Pid}. {ok, Pid}.
-spec conserve_resources( -spec conserve_resources(pid(),
pid(), rabbit_alarm:resource_alarm_source(),
rabbit_alarm:resource_alarm_source(), rabbit_alarm:resource_alert()) -> ok.
rabbit_alarm:resource_alert()
) -> ok.
conserve_resources(Pid, _, {_, Conserve, _}) -> conserve_resources(Pid, _, {_, Conserve, _}) ->
Pid ! {conserve_resources, Conserve}, Pid ! {conserve_resources, Conserve},
ok. ok.
@ -84,10 +73,8 @@ close_connection(Pid, Reason) ->
init(Ref) -> init(Ref) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [mqtt]}), logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [mqtt]}),
{ok, Sock} = rabbit_networking:handshake( {ok, Sock} = rabbit_networking:handshake(Ref,
Ref, application:get_env(?APP_NAME, proxy_protocol, false)),
application:get_env(?APP_NAME, proxy_protocol, false)
),
RealSocket = rabbit_net:unwrap_socket(Sock), RealSocket = rabbit_net:unwrap_socket(Sock),
case rabbit_net:connection_string(Sock, inbound) of case rabbit_net:connection_string(Sock, inbound) of
{ok, ConnStr} -> {ok, ConnStr} ->
@ -97,17 +84,15 @@ init(Ref) ->
LoginTimeout = application:get_env(?APP_NAME, login_timeout, 10_000), LoginTimeout = application:get_env(?APP_NAME, login_timeout, 10_000),
erlang:send_after(LoginTimeout, self(), login_timeout), erlang:send_after(LoginTimeout, self(), login_timeout),
ProcessorState = rabbit_mqtt_processor:initial_state(RealSocket, ConnName), ProcessorState = rabbit_mqtt_processor:initial_state(RealSocket, ConnName),
State0 = #state{ State0 = #state{socket = RealSocket,
socket = RealSocket, proxy_socket = rabbit_net:maybe_get_proxy_socket(Sock),
proxy_socket = rabbit_net:maybe_get_proxy_socket(Sock), conn_name = ConnName,
conn_name = ConnName, await_recv = false,
await_recv = false, connection_state = running,
connection_state = running, received_connect_packet = false,
received_connect_packet = false, conserve = false,
conserve = false, parse_state = rabbit_mqtt_packet:initial_state(),
parse_state = rabbit_mqtt_packet:initial_state(), proc_state = ProcessorState},
proc_state = ProcessorState
},
State1 = control_throttle(State0), State1 = control_throttle(State0),
State = rabbit_event:init_stats_timer(State1, #state.stats_timer), State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
gen_server:enter_loop(?MODULE, [], State); gen_server:enter_loop(?MODULE, [], State);
@ -123,67 +108,51 @@ init(Ref) ->
handle_call({info, InfoItems}, _From, State) -> handle_call({info, InfoItems}, _From, State) ->
{reply, infos(InfoItems, State), State, ?HIBERNATE_AFTER}; {reply, infos(InfoItems, State), State, ?HIBERNATE_AFTER};
handle_call(Msg, From, State) -> handle_call(Msg, From, State) ->
{stop, {mqtt_unexpected_call, Msg, From}, State}. {stop, {mqtt_unexpected_call, Msg, From}, State}.
handle_cast( handle_cast(duplicate_id,
duplicate_id, State = #state{ proc_state = PState,
State = #state{ conn_name = ConnName }) ->
proc_state = PState, ?LOG_WARNING("MQTT disconnecting client ~tp with duplicate id '~ts'",
conn_name = ConnName [ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
}
) ->
?LOG_WARNING(
"MQTT disconnecting client ~tp with duplicate id '~ts'",
[ConnName, rabbit_mqtt_processor:info(client_id, PState)]
),
{stop, {shutdown, duplicate_id}, State}; {stop, {shutdown, duplicate_id}, State};
handle_cast(
decommission_node, handle_cast(decommission_node,
State = #state{ State = #state{ proc_state = PState,
proc_state = PState, conn_name = ConnName }) ->
conn_name = ConnName ?LOG_WARNING("MQTT disconnecting client ~tp with client ID '~ts' as its node is about"
} " to be decommissioned",
) -> [ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
?LOG_WARNING(
"MQTT disconnecting client ~tp with client ID '~ts' as its node is about"
" to be decommissioned",
[ConnName, rabbit_mqtt_processor:info(client_id, PState)]
),
{stop, {shutdown, decommission_node}, State}; {stop, {shutdown, decommission_node}, State};
handle_cast(
{close_connection, Reason}, handle_cast({close_connection, Reason},
State = #state{conn_name = ConnName, proc_state = PState} State = #state{conn_name = ConnName, proc_state = PState}) ->
) -> ?LOG_WARNING("MQTT disconnecting client ~tp with client ID '~ts', reason: ~ts",
?LOG_WARNING( [ConnName, rabbit_mqtt_processor:info(client_id, PState), Reason]),
"MQTT disconnecting client ~tp with client ID '~ts', reason: ~ts",
[ConnName, rabbit_mqtt_processor:info(client_id, PState), Reason]
),
{stop, {shutdown, server_initiated_close}, State}; {stop, {shutdown, server_initiated_close}, State};
handle_cast(
QueueEvent = {queue_event, _, _}, handle_cast(QueueEvent = {queue_event, _, _},
State = #state{proc_state = PState0} State = #state{proc_state = PState0}) ->
) ->
case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
{ok, PState} -> {ok, PState} ->
maybe_process_deferred_recv(control_throttle(pstate(State, PState))); maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
{error, Reason, PState} -> {error, Reason, PState} ->
{stop, Reason, pstate(State, PState)} {stop, Reason, pstate(State, PState)}
end; end;
handle_cast({force_event_refresh, Ref}, State0) -> handle_cast({force_event_refresh, Ref}, State0) ->
Infos = infos(?CREATION_EVENT_KEYS, State0), Infos = infos(?CREATION_EVENT_KEYS, State0),
rabbit_event:notify(connection_created, Infos, Ref), rabbit_event:notify(connection_created, Infos, Ref),
State = rabbit_event:init_stats_timer(State0, #state.stats_timer), State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
{noreply, State, ?HIBERNATE_AFTER}; {noreply, State, ?HIBERNATE_AFTER};
handle_cast(
refresh_config, handle_cast(refresh_config, State = #state{proc_state = PState0,
State = #state{ conn_name = ConnName}) ->
proc_state = PState0,
conn_name = ConnName
}
) ->
PState = rabbit_mqtt_processor:update_trace(ConnName, PState0), PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
{noreply, pstate(State, PState), ?HIBERNATE_AFTER}; {noreply, pstate(State, PState), ?HIBERNATE_AFTER};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
{stop, {mqtt_unexpected_cast, Msg}, State}. {stop, {mqtt_unexpected_cast, Msg}, State}.
@ -192,53 +161,49 @@ handle_info(connection_created, State) ->
rabbit_core_metrics:connection_created(self(), Infos), rabbit_core_metrics:connection_created(self(), Infos),
rabbit_event:notify(connection_created, Infos), rabbit_event:notify(connection_created, Infos),
{noreply, State, ?HIBERNATE_AFTER}; {noreply, State, ?HIBERNATE_AFTER};
handle_info(timeout, State) -> handle_info(timeout, State) ->
rabbit_mqtt_processor:handle_pre_hibernate(), rabbit_mqtt_processor:handle_pre_hibernate(),
{noreply, State, hibernate}; {noreply, State, hibernate};
handle_info({'EXIT', _Conn, Reason}, State) -> handle_info({'EXIT', _Conn, Reason}, State) ->
{stop, {connection_died, Reason}, State}; {stop, {connection_died, Reason}, State};
handle_info(
{Tag, Sock, Data}, handle_info({Tag, Sock, Data},
State = #state{socket = Sock, connection_state = blocked} State = #state{ socket = Sock, connection_state = blocked })
) when when Tag =:= tcp; Tag =:= ssl ->
Tag =:= tcp; Tag =:= ssl {noreply, State#state{ deferred_recv = Data }, ?HIBERNATE_AFTER};
->
{noreply, State#state{deferred_recv = Data}, ?HIBERNATE_AFTER}; handle_info({Tag, Sock, Data},
handle_info( State = #state{ socket = Sock, connection_state = running })
{Tag, Sock, Data}, when Tag =:= tcp; Tag =:= ssl ->
State = #state{socket = Sock, connection_state = running}
) when
Tag =:= tcp; Tag =:= ssl
->
process_received_bytes( process_received_bytes(
Data, control_throttle(State#state{await_recv = false}) Data, control_throttle(State #state{ await_recv = false }));
);
handle_info({Tag, Sock}, State = #state{socket = Sock}) when handle_info({Tag, Sock}, State = #state{socket = Sock})
Tag =:= tcp_closed; Tag =:= ssl_closed when Tag =:= tcp_closed; Tag =:= ssl_closed ->
->
network_error(closed, State); network_error(closed, State);
handle_info({Tag, Sock, Reason}, State = #state{socket = Sock}) when
Tag =:= tcp_error; Tag =:= ssl_error handle_info({Tag, Sock, Reason}, State = #state{socket = Sock})
-> when Tag =:= tcp_error; Tag =:= ssl_error ->
network_error(Reason, State); network_error(Reason, State);
handle_info({inet_reply, Sock, ok}, State = #state{socket = Sock}) -> handle_info({inet_reply, Sock, ok}, State = #state{socket = Sock}) ->
{noreply, State, ?HIBERNATE_AFTER}; {noreply, State, ?HIBERNATE_AFTER};
handle_info({inet_reply, Sock, {error, Reason}}, State = #state{socket = Sock}) -> handle_info({inet_reply, Sock, {error, Reason}}, State = #state{socket = Sock}) ->
network_error(Reason, State); network_error(Reason, State);
handle_info({conserve_resources, Conserve}, State) -> handle_info({conserve_resources, Conserve}, State) ->
maybe_process_deferred_recv( maybe_process_deferred_recv(
control_throttle(State#state{conserve = Conserve}) control_throttle(State #state{ conserve = Conserve }));
);
handle_info({bump_credit, Msg}, State) -> handle_info({bump_credit, Msg}, State) ->
credit_flow:handle_bump_msg(Msg), credit_flow:handle_bump_msg(Msg),
maybe_process_deferred_recv(control_throttle(State)); maybe_process_deferred_recv(control_throttle(State));
handle_info(
{keepalive, Req}, handle_info({keepalive, Req}, State = #state{keepalive = KState0,
State = #state{ conn_name = ConnName}) ->
keepalive = KState0,
conn_name = ConnName
}
) ->
case rabbit_mqtt_keepalive:handle(Req, KState0) of case rabbit_mqtt_keepalive:handle(Req, KState0) of
{ok, KState} -> {ok, KState} ->
{noreply, State#state{keepalive = KState}, ?HIBERNATE_AFTER}; {noreply, State#state{keepalive = KState}, ?HIBERNATE_AFTER};
@ -248,6 +213,7 @@ handle_info(
{error, Reason} -> {error, Reason} ->
{stop, Reason, State} {stop, Reason, State}
end; end;
handle_info(login_timeout, State = #state{received_connect_packet = true}) -> handle_info(login_timeout, State = #state{received_connect_packet = true}) ->
{noreply, State, ?HIBERNATE_AFTER}; {noreply, State, ?HIBERNATE_AFTER};
handle_info(login_timeout, State = #state{conn_name = ConnName}) -> handle_info(login_timeout, State = #state{conn_name = ConnName}) ->
@ -258,47 +224,43 @@ handle_info(login_timeout, State = #state{conn_name = ConnName}) ->
%% and we don't want to skip closing the connection in that case. %% and we don't want to skip closing the connection in that case.
?LOG_ERROR("closing MQTT connection ~tp (login timeout)", [ConnName]), ?LOG_ERROR("closing MQTT connection ~tp (login timeout)", [ConnName]),
{stop, {shutdown, login_timeout}, State}; {stop, {shutdown, login_timeout}, State};
handle_info(emit_stats, State) -> handle_info(emit_stats, State) ->
{noreply, emit_stats(State), ?HIBERNATE_AFTER}; {noreply, emit_stats(State), ?HIBERNATE_AFTER};
handle_info(
{ra_event, _From, Evt}, handle_info({ra_event, _From, Evt},
#state{proc_state = PState0} = State #state{proc_state = PState0} = State) ->
) ->
%% handle applied event to ensure registration command actually got applied %% handle applied event to ensure registration command actually got applied
%% handle not_leader notification in case we send the command to a non-leader %% handle not_leader notification in case we send the command to a non-leader
PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0), PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
{noreply, pstate(State, PState), ?HIBERNATE_AFTER}; {noreply, pstate(State, PState), ?HIBERNATE_AFTER};
handle_info(
{{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt, handle_info({{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
#state{proc_state = PState0} = State #state{proc_state = PState0} = State) ->
) ->
case rabbit_mqtt_processor:handle_down(Evt, PState0) of case rabbit_mqtt_processor:handle_down(Evt, PState0) of
{ok, PState} -> {ok, PState} ->
maybe_process_deferred_recv(control_throttle(pstate(State, PState))); maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
{error, Reason} -> {error, Reason} ->
{stop, {shutdown, Reason, State}} {stop, {shutdown, Reason, State}}
end; end;
handle_info({'DOWN', _MRef, process, QPid, _Reason}, State) -> handle_info({'DOWN', _MRef, process, QPid, _Reason}, State) ->
rabbit_amqqueue_common:notify_sent_queue_down(QPid), rabbit_amqqueue_common:notify_sent_queue_down(QPid),
{noreply, State, ?HIBERNATE_AFTER}; {noreply, State, ?HIBERNATE_AFTER};
handle_info({shutdown, Explanation} = Reason, State = #state{conn_name = ConnName}) -> handle_info({shutdown, Explanation} = Reason, State = #state{conn_name = ConnName}) ->
%% rabbitmq_management plugin requests to close connection. %% rabbitmq_management plugin requests to close connection.
?LOG_INFO("MQTT closing connection ~tp: ~p", [ConnName, Explanation]), ?LOG_INFO("MQTT closing connection ~tp: ~p", [ConnName, Explanation]),
{stop, Reason, State}; {stop, Reason, State};
handle_info(Msg, State) -> handle_info(Msg, State) ->
{stop, {mqtt_unexpected_msg, Msg}, State}. {stop, {mqtt_unexpected_msg, Msg}, State}.
terminate(Reason, State = #state{}) -> terminate(Reason, State = #state{}) ->
terminate(Reason, {true, State}); terminate(Reason, {true, State});
terminate( terminate(Reason, {SendWill, State = #state{conn_name = ConnName,
Reason, keepalive = KState0,
{SendWill, proc_state = PState}}) ->
State = #state{
conn_name = ConnName,
keepalive = KState0,
proc_state = PState
}}
) ->
KState = rabbit_mqtt_keepalive:cancel_timer(KState0), KState = rabbit_mqtt_keepalive:cancel_timer(KState0),
maybe_emit_stats(State#state{keepalive = KState}), maybe_emit_stats(State#state{keepalive = KState}),
_ = rabbit_mqtt_processor:terminate(SendWill, ConnName, ?PROTO_FAMILY, PState), _ = rabbit_mqtt_processor:terminate(SendWill, ConnName, ?PROTO_FAMILY, PState),
@ -306,25 +268,36 @@ terminate(
log_terminate({network_error, {ssl_upgrade_error, closed}, ConnName}, _State) -> log_terminate({network_error, {ssl_upgrade_error, closed}, ConnName}, _State) ->
?LOG_ERROR("MQTT detected TLS upgrade error on ~s: connection closed", [ConnName]); ?LOG_ERROR("MQTT detected TLS upgrade error on ~s: connection closed", [ConnName]);
log_terminate(
{network_error, {ssl_upgrade_error, {tls_alert, "handshake failure"}}, ConnName}, _State log_terminate({network_error,
) -> {ssl_upgrade_error,
{tls_alert, "handshake failure"}}, ConnName}, _State) ->
log_tls_alert(handshake_failure, ConnName); log_tls_alert(handshake_failure, ConnName);
log_terminate({network_error, {ssl_upgrade_error, {tls_alert, "unknown ca"}}, ConnName}, _State) -> log_terminate({network_error,
{ssl_upgrade_error,
{tls_alert, "unknown ca"}}, ConnName}, _State) ->
log_tls_alert(unknown_ca, ConnName); log_tls_alert(unknown_ca, ConnName);
log_terminate({network_error, {ssl_upgrade_error, {tls_alert, {Err, _}}}, ConnName}, _State) -> log_terminate({network_error,
{ssl_upgrade_error,
{tls_alert, {Err, _}}}, ConnName}, _State) ->
log_tls_alert(Err, ConnName); log_tls_alert(Err, ConnName);
log_terminate({network_error, {ssl_upgrade_error, {tls_alert, Alert}}, ConnName}, _State) -> log_terminate({network_error,
{ssl_upgrade_error,
{tls_alert, Alert}}, ConnName}, _State) ->
log_tls_alert(Alert, ConnName); log_tls_alert(Alert, ConnName);
log_terminate({network_error, {ssl_upgrade_error, Reason}, ConnName}, _State) -> log_terminate({network_error, {ssl_upgrade_error, Reason}, ConnName}, _State) ->
?LOG_ERROR("MQTT detected TLS upgrade error on ~s: ~p", [ConnName, Reason]); ?LOG_ERROR("MQTT detected TLS upgrade error on ~s: ~p", [ConnName, Reason]);
log_terminate({network_error, Reason, ConnName}, _State) -> log_terminate({network_error, Reason, ConnName}, _State) ->
?LOG_ERROR("MQTT detected network error on ~s: ~p", [ConnName, Reason]); ?LOG_ERROR("MQTT detected network error on ~s: ~p", [ConnName, Reason]);
log_terminate({network_error, Reason}, _State) -> log_terminate({network_error, Reason}, _State) ->
?LOG_ERROR("MQTT detected network error: ~p", [Reason]); ?LOG_ERROR("MQTT detected network error: ~p", [Reason]);
log_terminate(normal, #state{conn_name = ConnName}) ->
log_terminate(normal, #state{conn_name = ConnName}) ->
?LOG_INFO("closing MQTT connection ~p (~s)", [self(), ConnName]), ?LOG_INFO("closing MQTT connection ~p (~s)", [self(), ConnName]),
ok; ok;
log_terminate(_Reason, _State) -> log_terminate(_Reason, _State) ->
ok. ok.
@ -336,80 +309,54 @@ code_change(_OldVsn, State, _Extra) ->
log_tls_alert(handshake_failure, ConnName) -> log_tls_alert(handshake_failure, ConnName) ->
?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: handshake failure", [ConnName]); ?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: handshake failure", [ConnName]);
log_tls_alert(unknown_ca, ConnName) -> log_tls_alert(unknown_ca, ConnName) ->
?LOG_ERROR( ?LOG_ERROR("MQTT detected TLS certificate verification error on ~ts: alert 'unknown CA'",
"MQTT detected TLS certificate verification error on ~ts: alert 'unknown CA'", [ConnName]);
[ConnName]
);
log_tls_alert(Alert, ConnName) -> log_tls_alert(Alert, ConnName) ->
?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: alert ~ts", [ConnName, Alert]). ?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: alert ~ts", [ConnName, Alert]).
process_received_bytes( process_received_bytes(<<>>, State = #state{received_connect_packet = false,
<<>>, proc_state = PState,
State = #state{ conn_name = ConnName}) ->
received_connect_packet = false, ?LOG_INFO("Accepted MQTT connection ~p (~s, client ID: ~s)",
proc_state = PState, [self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
conn_name = ConnName
}
) ->
?LOG_INFO(
"Accepted MQTT connection ~p (~s, client ID: ~s)",
[self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]
),
{noreply, ensure_stats_timer(State#state{received_connect_packet = true}), ?HIBERNATE_AFTER}; {noreply, ensure_stats_timer(State#state{received_connect_packet = true}), ?HIBERNATE_AFTER};
process_received_bytes(<<>>, State) -> process_received_bytes(<<>>, State) ->
{noreply, ensure_stats_timer(State), ?HIBERNATE_AFTER}; {noreply, ensure_stats_timer(State), ?HIBERNATE_AFTER};
process_received_bytes( process_received_bytes(Bytes,
Bytes, State = #state{ parse_state = ParseState,
State = #state{ proc_state = ProcState,
parse_state = ParseState, conn_name = ConnName }) ->
proc_state = ProcState,
conn_name = ConnName
}
) ->
case parse(Bytes, ParseState) of case parse(Bytes, ParseState) of
{more, ParseState1} -> {more, ParseState1} ->
{noreply, ensure_stats_timer(State#state{parse_state = ParseState1}), ?HIBERNATE_AFTER}; {noreply,
ensure_stats_timer( State #state{ parse_state = ParseState1 }),
?HIBERNATE_AFTER};
{ok, Packet, Rest} -> {ok, Packet, Rest} ->
case rabbit_mqtt_processor:process_packet(Packet, ProcState) of case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
{ok, ProcState1} -> {ok, ProcState1} ->
process_received_bytes( process_received_bytes(
Rest, Rest,
State#state{ State #state{parse_state = rabbit_mqtt_packet:initial_state(),
parse_state = rabbit_mqtt_packet:initial_state(), proc_state = ProcState1});
proc_state = ProcState1
}
);
%% PUBLISH and more %% PUBLISH and more
{error, unauthorized = Reason, ProcState1} -> {error, unauthorized = Reason, ProcState1} ->
?LOG_ERROR("MQTT connection ~ts is closing due to an authorization failure", [ ?LOG_ERROR("MQTT connection ~ts is closing due to an authorization failure", [ConnName]),
ConnName
]),
{stop, {shutdown, Reason}, pstate(State, ProcState1)}; {stop, {shutdown, Reason}, pstate(State, ProcState1)};
%% CONNECT packets only %% CONNECT packets only
{error, unauthenticated = Reason, ProcState1} -> {error, unauthenticated = Reason, ProcState1} ->
?LOG_ERROR("MQTT connection ~ts is closing due to an authentication failure", [ ?LOG_ERROR("MQTT connection ~ts is closing due to an authentication failure", [ConnName]),
ConnName
]),
{stop, {shutdown, Reason}, pstate(State, ProcState1)}; {stop, {shutdown, Reason}, pstate(State, ProcState1)};
%% CONNECT packets only %% CONNECT packets only
{error, invalid_client_id = Reason, ProcState1} -> {error, invalid_client_id = Reason, ProcState1} ->
?LOG_ERROR("MQTT cannot accept connection ~ts: client uses an invalid ID", [ ?LOG_ERROR("MQTT cannot accept connection ~ts: client uses an invalid ID", [ConnName]),
ConnName
]),
{stop, {shutdown, Reason}, pstate(State, ProcState1)}; {stop, {shutdown, Reason}, pstate(State, ProcState1)};
%% CONNECT packets only %% CONNECT packets only
{error, unsupported_protocol_version = Reason, ProcState1} -> {error, unsupported_protocol_version = Reason, ProcState1} ->
?LOG_ERROR( ?LOG_ERROR("MQTT cannot accept connection ~ts: incompatible protocol version", [ConnName]),
"MQTT cannot accept connection ~ts: incompatible protocol version", [
ConnName
]
),
{stop, {shutdown, Reason}, pstate(State, ProcState1)}; {stop, {shutdown, Reason}, pstate(State, ProcState1)};
{error, unavailable = Reason, ProcState1} -> {error, unavailable = Reason, ProcState1} ->
?LOG_ERROR( ?LOG_ERROR("MQTT cannot accept connection ~ts due to an internal error or unavailable component",
"MQTT cannot accept connection ~ts due to an internal error or unavailable component", [ConnName]),
[ConnName]
),
{stop, {shutdown, Reason}, pstate(State, ProcState1)}; {stop, {shutdown, Reason}, pstate(State, ProcState1)};
{error, Reason, ProcState1} -> {error, Reason, ProcState1} ->
?LOG_ERROR("MQTT protocol error on connection ~ts: ~tp", [ConnName, Reason]), ?LOG_ERROR("MQTT protocol error on connection ~ts: ~tp", [ConnName, Reason]),
@ -418,11 +365,9 @@ process_received_bytes(
{stop, normal, {_SendWill = false, pstate(State, ProcState1)}} {stop, normal, {_SendWill = false, pstate(State, ProcState1)}}
end; end;
{error, {cannot_parse, Reason, Stacktrace}} -> {error, {cannot_parse, Reason, Stacktrace}} ->
?LOG_ERROR( ?LOG_ERROR("MQTT cannot parse a packet on connection '~ts', reason: ~tp, "
"MQTT cannot parse a packet on connection '~ts', reason: ~tp, " "stacktrace: ~tp, payload (first 100 bytes): ~tp",
"stacktrace: ~tp, payload (first 100 bytes): ~tp", [ConnName, Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Bytes, 100)]),
[ConnName, Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Bytes, 100)]
),
{stop, {shutdown, Reason}, State}; {stop, {shutdown, Reason}, State};
{error, Error} -> {error, Error} ->
?LOG_ERROR("MQTT detected a framing error on connection ~ts: ~tp", [ConnName, Error]), ?LOG_ERROR("MQTT detected a framing error on connection ~ts: ~tp", [ConnName, Error]),
@ -430,8 +375,8 @@ process_received_bytes(
end. end.
-spec pstate(state(), rabbit_mqtt_processor:state()) -> state(). -spec pstate(state(), rabbit_mqtt_processor:state()) -> state().
pstate(State = #state{}, PState) -> pstate(State = #state {}, PState) ->
State#state{proc_state = PState}. State #state{ proc_state = PState }.
%%---------------------------------------------------------------------------- %%----------------------------------------------------------------------------
parse(Bytes, ParseState) -> parse(Bytes, ParseState) ->
@ -442,13 +387,9 @@ parse(Bytes, ParseState) ->
{error, {cannot_parse, Reason, Stacktrace}} {error, {cannot_parse, Reason, Stacktrace}}
end. end.
network_error( network_error(closed,
closed, State = #state{conn_name = ConnName,
State = #state{ received_connect_packet = Connected}) ->
conn_name = ConnName,
received_connect_packet = Connected
}
) ->
Fmt = "MQTT connection ~p will terminate because peer closed TCP connection", Fmt = "MQTT connection ~p will terminate because peer closed TCP connection",
Args = [ConnName], Args = [ConnName],
case Connected of case Connected of
@ -456,77 +397,62 @@ network_error(
false -> ?LOG_DEBUG(Fmt, Args) false -> ?LOG_DEBUG(Fmt, Args)
end, end,
{stop, {shutdown, conn_closed}, State}; {stop, {shutdown, conn_closed}, State};
network_error(
Reason, network_error(Reason,
State = #state{conn_name = ConnName} State = #state{conn_name = ConnName}) ->
) ->
?LOG_INFO("MQTT detected network error for ~p: ~p", [ConnName, Reason]), ?LOG_INFO("MQTT detected network error for ~p: ~p", [ConnName, Reason]),
{stop, {shutdown, conn_closed}, State}. {stop, {shutdown, conn_closed}, State}.
run_socket(State = #state{connection_state = blocked}) -> run_socket(State = #state{ connection_state = blocked }) ->
State; State;
run_socket(State = #state{deferred_recv = Data}) when Data =/= undefined -> run_socket(State = #state{ deferred_recv = Data }) when Data =/= undefined ->
State; State;
run_socket(State = #state{await_recv = true}) -> run_socket(State = #state{ await_recv = true }) ->
State; State;
run_socket(State = #state{socket = Sock}) -> run_socket(State = #state{ socket = Sock }) ->
ok = rabbit_net:setopts(Sock, [{active, once}]), ok = rabbit_net:setopts(Sock, [{active, once}]),
State#state{await_recv = true}. State#state{ await_recv = true }.
control_throttle( control_throttle(State = #state{connection_state = ConnState,
State = #state{ conserve = Conserve,
connection_state = ConnState, received_connect_packet = Connected,
conserve = Conserve, proc_state = PState,
received_connect_packet = Connected, keepalive = KState
proc_state = PState, }) ->
keepalive = KState
}
) ->
Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState), Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
case {ConnState, Throttle} of case {ConnState, Throttle} of
{running, true} -> {running, true} ->
State#state{ State#state{connection_state = blocked,
connection_state = blocked, keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)};
keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)
};
{blocked, false} -> {blocked, false} ->
run_socket(State#state{ run_socket(State#state{connection_state = running,
connection_state = running, keepalive = rabbit_mqtt_keepalive:start_timer(KState)});
keepalive = rabbit_mqtt_keepalive:start_timer(KState)
});
{_, _} -> {_, _} ->
run_socket(State) run_socket(State)
end. end.
maybe_process_deferred_recv(State = #state{deferred_recv = undefined}) -> maybe_process_deferred_recv(State = #state{ deferred_recv = undefined }) ->
{noreply, State, ?HIBERNATE_AFTER}; {noreply, State, ?HIBERNATE_AFTER};
maybe_process_deferred_recv(State = #state{deferred_recv = Data, socket = Sock}) -> maybe_process_deferred_recv(State = #state{ deferred_recv = Data, socket = Sock }) ->
handle_info( handle_info({tcp, Sock, Data},
{tcp, Sock, Data}, State#state{ deferred_recv = undefined }).
State#state{deferred_recv = undefined}
).
maybe_emit_stats(#state{stats_timer = undefined}) -> maybe_emit_stats(#state{stats_timer = undefined}) ->
ok; ok;
maybe_emit_stats(State) -> maybe_emit_stats(State) ->
rabbit_event:if_enabled( rabbit_event:if_enabled(State, #state.stats_timer,
State, fun() -> emit_stats(State) end).
#state.stats_timer,
fun() -> emit_stats(State) end
).
emit_stats(State = #state{received_connect_packet = false}) -> emit_stats(State=#state{received_connect_packet = false}) ->
%% Avoid emitting stats on terminate when the connection has not yet been %% Avoid emitting stats on terminate when the connection has not yet been
%% established, as this causes orphan entries on the stats database %% established, as this causes orphan entries on the stats database
State1 = rabbit_event:reset_stats_timer(State, #state.stats_timer), State1 = rabbit_event:reset_stats_timer(State, #state.stats_timer),
ensure_stats_timer(State1); ensure_stats_timer(State1);
emit_stats(State) -> emit_stats(State) ->
[ [{_, Pid},
{_, Pid}, {_, RecvOct},
{_, RecvOct}, {_, SendOct},
{_, SendOct}, {_, Reductions}] = infos(?SIMPLE_METRICS, State),
{_, Reductions}
] = infos(?SIMPLE_METRICS, State),
Infos = infos(?OTHER_METRICS, State), Infos = infos(?OTHER_METRICS, State),
rabbit_core_metrics:connection_stats(Pid, Infos), rabbit_core_metrics:connection_stats(Pid, Infos),
rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions), rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
@ -539,13 +465,12 @@ ensure_stats_timer(State = #state{}) ->
infos(Items, State) -> infos(Items, State) ->
[{Item, i(Item, State)} || Item <- Items]. [{Item, i(Item, State)} || Item <- Items].
i(SockStat, #state{socket = Sock}) when i(SockStat, #state{socket = Sock})
SockStat =:= recv_oct; when SockStat =:= recv_oct;
SockStat =:= recv_cnt; SockStat =:= recv_cnt;
SockStat =:= send_oct; SockStat =:= send_oct;
SockStat =:= send_cnt; SockStat =:= send_cnt;
SockStat =:= send_pend SockStat =:= send_pend ->
->
case rabbit_net:getstat(Sock, [SockStat]) of case rabbit_net:getstat(Sock, [SockStat]) of
{ok, [{_, N}]} when is_number(N) -> {ok, [{_, N}]} when is_number(N) ->
N; N;
@ -569,19 +494,17 @@ i(connection_state, #state{connection_state = Val}) ->
Val; Val;
i(pid, _) -> i(pid, _) ->
self(); self();
i(SSL, #state{socket = Sock, proxy_socket = ProxySock}) when i(SSL, #state{socket = Sock, proxy_socket = ProxySock})
SSL =:= ssl; when SSL =:= ssl;
SSL =:= ssl_protocol; SSL =:= ssl_protocol;
SSL =:= ssl_key_exchange; SSL =:= ssl_key_exchange;
SSL =:= ssl_cipher; SSL =:= ssl_cipher;
SSL =:= ssl_hash SSL =:= ssl_hash ->
->
rabbit_ssl:info(SSL, {Sock, ProxySock}); rabbit_ssl:info(SSL, {Sock, ProxySock});
i(Cert, #state{socket = Sock}) when i(Cert, #state{socket = Sock})
Cert =:= peer_cert_issuer; when Cert =:= peer_cert_issuer;
Cert =:= peer_cert_subject; Cert =:= peer_cert_subject;
Cert =:= peer_cert_validity Cert =:= peer_cert_validity ->
->
rabbit_ssl:cert_info(Cert, Sock); rabbit_ssl:cert_info(Cert, Sock);
i(timeout, #state{keepalive = KState}) -> i(timeout, #state{keepalive = KState}) ->
rabbit_mqtt_keepalive:interval_secs(KState); rabbit_mqtt_keepalive:interval_secs(KState);
@ -591,48 +514,40 @@ i(Key, #state{proc_state = ProcState}) ->
rabbit_mqtt_processor:info(Key, ProcState). rabbit_mqtt_processor:info(Key, ProcState).
-spec format_status(Status) -> Status when -spec format_status(Status) -> Status when
Status :: #{ Status :: #{state => term(),
state => term(), message => term(),
message => term(), reason => term(),
reason => term(), log => [sys:system_event()]}.
log => [sys:system_event()]
}.
format_status(Status) -> format_status(Status) ->
maps:map( maps:map(
fun fun(state, State) ->
(state, State) -> format_state(State);
format_state(State); (_, Value) ->
(_, Value) -> Value
Value end, Status).
end,
Status
).
-spec format_state(state()) -> map(). -spec format_state(state()) -> map().
format_state(#state{ format_state(#state{socket = Socket,
socket = Socket, proxy_socket = ProxySock,
proxy_socket = ProxySock, await_recv = AwaitRecv,
await_recv = AwaitRecv, deferred_recv = DeferredRecv,
deferred_recv = DeferredRecv, parse_state = _,
parse_state = _, proc_state = PState,
proc_state = PState, connection_state = ConnectionState,
connection_state = ConnectionState, conserve = Conserve,
conserve = Conserve, stats_timer = StatsTimer,
stats_timer = StatsTimer, keepalive = Keepalive,
keepalive = Keepalive, conn_name = ConnName,
conn_name = ConnName, received_connect_packet = ReceivedConnectPacket
received_connect_packet = ReceivedConnectPacket }) ->
}) -> #{socket => Socket,
#{ proxy_socket => ProxySock,
socket => Socket, await_recv => AwaitRecv,
proxy_socket => ProxySock, deferred_recv => DeferredRecv =/= undefined,
await_recv => AwaitRecv, proc_state => rabbit_mqtt_processor:format_status(PState),
deferred_recv => DeferredRecv =/= undefined, connection_state => ConnectionState,
proc_state => rabbit_mqtt_processor:format_status(PState), conserve => Conserve,
connection_state => ConnectionState, stats_timer => StatsTimer,
conserve => Conserve, keepalive => Keepalive,
stats_timer => StatsTimer, conn_name => ConnName,
keepalive => Keepalive, received_connect_packet => ReceivedConnectPacket}.
conn_name => ConnName,
received_connect_packet => ReceivedConnectPacket
}.

View File

@ -13,57 +13,53 @@
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]). -export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
-record(store_state, { -record(store_state, {
%% DETS table name %% DETS table name
table table
}). }).
-type store_state() :: #store_state{}. -type store_state() :: #store_state{}.
-spec new(file:name_all(), rabbit_types:vhost()) -> store_state(). -spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
new(Dir, VHost) -> new(Dir, VHost) ->
Tid = open_table(Dir, VHost), Tid = open_table(Dir, VHost),
#store_state{table = Tid}. #store_state{table = Tid}.
-spec recover(file:name_all(), rabbit_types:vhost()) -> -spec recover(file:name_all(), rabbit_types:vhost()) ->
{error, uninitialized} | {ok, store_state()}. {error, uninitialized} | {ok, store_state()}.
recover(Dir, VHost) -> recover(Dir, VHost) ->
case open_table(Dir, VHost) of case open_table(Dir, VHost) of
{error, _} -> {error, uninitialized}; {error, _} -> {error, uninitialized};
{ok, Tid} -> {ok, #store_state{table = Tid}} {ok, Tid} -> {ok, #store_state{table = Tid}}
end. end.
-spec insert(binary(), mqtt_msg(), store_state()) -> ok. -spec insert(binary(), mqtt_msg(), store_state()) -> ok.
insert(Topic, Msg, #store_state{table = T}) -> insert(Topic, Msg, #store_state{table = T}) ->
ok = dets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}). ok = dets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}).
-spec lookup(binary(), store_state()) -> retained_message() | not_found. -spec lookup(binary(), store_state()) -> retained_message() | not_found.
lookup(Topic, #store_state{table = T}) -> lookup(Topic, #store_state{table = T}) ->
case dets:lookup(T, Topic) of case dets:lookup(T, Topic) of
[] -> not_found; [] -> not_found;
[Entry] -> Entry [Entry] -> Entry
end. end.
-spec delete(binary(), store_state()) -> ok. -spec delete(binary(), store_state()) -> ok.
delete(Topic, #store_state{table = T}) -> delete(Topic, #store_state{table = T}) ->
ok = dets:delete(T, Topic). ok = dets:delete(T, Topic).
-spec terminate(store_state()) -> ok. -spec terminate(store_state()) -> ok.
terminate(#store_state{table = T}) -> terminate(#store_state{table = T}) ->
ok = dets:close(T). ok = dets:close(T).
open_table(Dir, VHost) -> open_table(Dir, VHost) ->
dets:open_file( dets:open_file(rabbit_mqtt_util:vhost_name_to_table_name(VHost),
rabbit_mqtt_util:vhost_name_to_table_name(VHost), table_options(rabbit_mqtt_util:path_for(Dir, VHost, ".dets"))).
table_options(rabbit_mqtt_util:path_for(Dir, VHost, ".dets"))
).
table_options(Path) -> table_options(Path) ->
[ [{type, set},
{type, set}, {keypos, #retained_message.topic},
{keypos, #retained_message.topic}, {file, Path},
{file, Path}, {ram_file, true},
{ram_file, true}, {repair, true},
{repair, true}, {auto_save, rabbit_misc:get_env(rabbit_mqtt, retained_message_store_dets_sync_interval, 2000)}
{auto_save,
rabbit_misc:get_env(rabbit_mqtt, retained_message_store_dets_sync_interval, 2000)}
]. ].

View File

@ -13,51 +13,49 @@
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]). -export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
-record(store_state, { -record(store_state, {
%% ETS table ID %% ETS table ID
table, table,
%% where the table is stored on disk %% where the table is stored on disk
filename filename
}). }).
-type store_state() :: #store_state{}. -type store_state() :: #store_state{}.
-spec new(file:name_all(), rabbit_types:vhost()) -> store_state(). -spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
new(Dir, VHost) -> new(Dir, VHost) ->
Path = rabbit_mqtt_util:path_for(Dir, VHost), Path = rabbit_mqtt_util:path_for(Dir, VHost),
TableName = rabbit_mqtt_util:vhost_name_to_table_name(VHost), TableName = rabbit_mqtt_util:vhost_name_to_table_name(VHost),
_ = file:delete(Path), _ = file:delete(Path),
Tid = ets:new(TableName, [set, public, {keypos, #retained_message.topic}]), Tid = ets:new(TableName, [set, public, {keypos, #retained_message.topic}]),
#store_state{table = Tid, filename = Path}. #store_state{table = Tid, filename = Path}.
-spec recover(file:name_all(), rabbit_types:vhost()) -> -spec recover(file:name_all(), rabbit_types:vhost()) ->
{error, uninitialized} | {ok, store_state()}. {error, uninitialized} | {ok, store_state()}.
recover(Dir, VHost) -> recover(Dir, VHost) ->
Path = rabbit_mqtt_util:path_for(Dir, VHost), Path = rabbit_mqtt_util:path_for(Dir, VHost),
case ets:file2tab(Path) of case ets:file2tab(Path) of
{ok, Tid} -> {ok, Tid} -> _ = file:delete(Path),
_ = file:delete(Path), {ok, #store_state{table = Tid, filename = Path}};
{ok, #store_state{table = Tid, filename = Path}}; {error, _} -> {error, uninitialized}
{error, _} -> end.
{error, uninitialized}
end.
-spec insert(binary(), mqtt_msg(), store_state()) -> ok. -spec insert(binary(), mqtt_msg(), store_state()) -> ok.
insert(Topic, Msg, #store_state{table = T}) -> insert(Topic, Msg, #store_state{table = T}) ->
true = ets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}), true = ets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}),
ok. ok.
-spec lookup(binary(), store_state()) -> retained_message() | not_found. -spec lookup(binary(), store_state()) -> retained_message() | not_found.
lookup(Topic, #store_state{table = T}) -> lookup(Topic, #store_state{table = T}) ->
case ets:lookup(T, Topic) of case ets:lookup(T, Topic) of
[] -> not_found; [] -> not_found;
[Entry] -> Entry [Entry] -> Entry
end. end.
-spec delete(binary(), store_state()) -> ok. -spec delete(binary(), store_state()) -> ok.
delete(Topic, #store_state{table = T}) -> delete(Topic, #store_state{table = T}) ->
true = ets:delete(T, Topic), true = ets:delete(T, Topic),
ok. ok.
-spec terminate(store_state()) -> ok. -spec terminate(store_state()) -> ok.
terminate(#store_state{table = T, filename = Path}) -> terminate(#store_state{table = T, filename = Path}) ->
ok = ets:tab2file(T, Path, [{extended_info, [object_count]}]). ok = ets:tab2file(T, Path, [{extended_info, [object_count]}]).

View File

@ -12,19 +12,19 @@
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]). -export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
new(_Dir, _VHost) -> new(_Dir, _VHost) ->
ok. ok.
recover(_Dir, _VHost) -> recover(_Dir, _VHost) ->
{ok, ok}. {ok, ok}.
insert(_Topic, _Msg, _State) -> insert(_Topic, _Msg, _State) ->
ok. ok.
lookup(_Topic, _State) -> lookup(_Topic, _State) ->
not_found. not_found.
delete(_Topic, _State) -> delete(_Topic, _State) ->
ok. ok.
terminate(_State) -> terminate(_State) ->
ok. ok.

View File

@ -12,23 +12,15 @@
-behaviour(gen_server). -behaviour(gen_server).
-export([ -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
init/1, terminate/2, start_link/2]).
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
start_link/2
]).
-export([retain/3, fetch/2, clear/2, store_module/0]). -export([retain/3, fetch/2, clear/2, store_module/0]).
-define(TIMEOUT, 30_000). -define(TIMEOUT, 30_000).
-record(retainer_state, { -record(retainer_state, {store_mod,
store_mod, store}).
store
}).
%%---------------------------------------------------------------------------- %%----------------------------------------------------------------------------
@ -54,19 +46,12 @@ clear(Pid, Topic) ->
init([StoreMod, VHost]) -> init([StoreMod, VHost]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
State = State = case StoreMod:recover(store_dir(), VHost) of
case StoreMod:recover(store_dir(), VHost) of {ok, Store} -> #retainer_state{store = Store,
{ok, Store} -> store_mod = StoreMod};
#retainer_state{ {error, _} -> #retainer_state{store = StoreMod:new(store_dir(), VHost),
store = Store, store_mod = StoreMod}
store_mod = StoreMod end,
};
{error, _} ->
#retainer_state{
store = StoreMod:new(store_dir(), VHost),
store_mod = StoreMod
}
end,
{ok, State}. {ok, State}.
-spec store_module() -> undefined | module(). -spec store_module() -> undefined | module().
@ -78,33 +63,26 @@ store_module() ->
%%---------------------------------------------------------------------------- %%----------------------------------------------------------------------------
handle_cast( handle_cast({retain, Topic, Msg},
{retain, Topic, Msg}, State = #retainer_state{store = Store, store_mod = Mod}) ->
State = #retainer_state{store = Store, store_mod = Mod}
) ->
ok = Mod:insert(Topic, Msg, Store), ok = Mod:insert(Topic, Msg, Store),
{noreply, State}; {noreply, State};
handle_cast( handle_cast({clear, Topic},
{clear, Topic}, State = #retainer_state{store = Store, store_mod = Mod}) ->
State = #retainer_state{store = Store, store_mod = Mod}
) ->
ok = Mod:delete(Topic, Store), ok = Mod:delete(Topic, Store),
{noreply, State}. {noreply, State}.
handle_call( handle_call({fetch, Topic}, _From,
{fetch, Topic}, State = #retainer_state{store = Store, store_mod = Mod}) ->
_From, Reply = case Mod:lookup(Topic, Store) of
State = #retainer_state{store = Store, store_mod = Mod} #retained_message{mqtt_msg = Msg} -> Msg;
) -> not_found -> undefined
Reply = end,
case Mod:lookup(Topic, Store) of
#retained_message{mqtt_msg = Msg} -> Msg;
not_found -> undefined
end,
{reply, Reply, State}. {reply, Reply, State}.
handle_info(stop, State) -> handle_info(stop, State) ->
{stop, normal, State}; {stop, normal, State};
handle_info(Info, State) -> handle_info(Info, State) ->
{stop, {unknown_info, Info}, State}. {stop, {unknown_info, Info}, State}.

View File

@ -8,13 +8,8 @@
-module(rabbit_mqtt_retainer_sup). -module(rabbit_mqtt_retainer_sup).
-behaviour(supervisor). -behaviour(supervisor).
-export([ -export([start_link/1, init/1, start_child/2,start_child/1, child_for_vhost/1,
start_link/1, delete_child/1]).
init/1,
start_child/2, start_child/1,
child_for_vhost/1,
delete_child/1
]).
-spec start_child(binary()) -> supervisor:startchild_ret(). -spec start_child(binary()) -> supervisor:startchild_ret().
-spec start_child(term(), binary()) -> supervisor:startchild_ret(). -spec start_child(term(), binary()) -> supervisor:startchild_ret().
@ -24,13 +19,13 @@ start_link(SupName) ->
-spec child_for_vhost(rabbit_types:vhost()) -> pid(). -spec child_for_vhost(rabbit_types:vhost()) -> pid().
child_for_vhost(VHost) when is_binary(VHost) -> child_for_vhost(VHost) when is_binary(VHost) ->
case rabbit_mqtt_retainer_sup:start_child(VHost) of case rabbit_mqtt_retainer_sup:start_child(VHost) of
{ok, Pid} -> Pid; {ok, Pid} -> Pid;
{error, {already_started, Pid}} -> Pid {error, {already_started, Pid}} -> Pid
end. end.
start_child(VHost) when is_binary(VHost) -> start_child(VHost) when is_binary(VHost) ->
start_child(rabbit_mqtt_retainer:store_module(), VHost). start_child(rabbit_mqtt_retainer:store_module(), VHost).
start_child(RetainStoreMod, VHost) -> start_child(RetainStoreMod, VHost) ->
supervisor:start_child( supervisor:start_child(
@ -46,20 +41,18 @@ start_child(RetainStoreMod, VHost) ->
). ).
delete_child(VHost) -> delete_child(VHost) ->
Id = vhost_to_atom(VHost), Id = vhost_to_atom(VHost),
ok = supervisor:terminate_child(?MODULE, Id), ok = supervisor:terminate_child(?MODULE, Id),
ok = supervisor:delete_child(?MODULE, Id). ok = supervisor:delete_child(?MODULE, Id).
init([]) -> init([]) ->
Mod = rabbit_mqtt_retainer:store_module(), Mod = rabbit_mqtt_retainer:store_module(),
rabbit_log:info( rabbit_log:info("MQTT retained message store: ~tp",
"MQTT retained message store: ~tp", [Mod]),
[Mod] {ok, {
), #{strategy => one_for_one, intensity => 5, period => 5},
{ok, { child_specs(Mod, rabbit_vhost:list_names())
#{strategy => one_for_one, intensity => 5, period => 5}, }}.
child_specs(Mod, rabbit_vhost:list_names())
}}.
child_specs(Mod, VHosts) -> child_specs(Mod, VHosts) ->
%% see start_child/2 %% see start_child/2

View File

@ -23,61 +23,52 @@ init([{Listeners, SslListeners0}]) ->
NumTcpAcceptors = application:get_env(?APP_NAME, num_tcp_acceptors, 10), NumTcpAcceptors = application:get_env(?APP_NAME, num_tcp_acceptors, 10),
ConcurrentConnsSups = application:get_env(?APP_NAME, num_conns_sups, 1), ConcurrentConnsSups = application:get_env(?APP_NAME, num_conns_sups, 1),
{ok, SocketOpts} = application:get_env(?APP_NAME, tcp_listen_options), {ok, SocketOpts} = application:get_env(?APP_NAME, tcp_listen_options),
{SslOpts, NumSslAcceptors, SslListeners} = {SslOpts, NumSslAcceptors, SslListeners}
case SslListeners0 of = case SslListeners0 of
[] -> [] -> {none, 0, []};
{none, 0, []}; _ -> {rabbit_networking:ensure_ssl(),
_ -> application:get_env(?APP_NAME, num_ssl_acceptors, 10),
{ case rabbit_networking:poodle_check('MQTT') of
rabbit_networking:ensure_ssl(), ok -> SslListeners0;
application:get_env(?APP_NAME, num_ssl_acceptors, 10), danger -> []
case rabbit_networking:poodle_check('MQTT') of end}
ok -> SslListeners0; end,
danger -> []
end
}
end,
%% Use separate process group scope per RabbitMQ node. This achieves a local-only %% Use separate process group scope per RabbitMQ node. This achieves a local-only
%% process group which requires less memory with millions of connections. %% process group which requires less memory with millions of connections.
PgScope = list_to_atom(io_lib:format("~s_~s", [?PG_SCOPE, node()])), PgScope = list_to_atom(io_lib:format("~s_~s", [?PG_SCOPE, node()])),
persistent_term:put(?PG_SCOPE, PgScope), persistent_term:put(?PG_SCOPE, PgScope),
{ok, {ok,
{ {#{strategy => one_for_all,
#{ intensity => 10,
strategy => one_for_all, period => 10},
intensity => 10, [
period => 10 #{id => PgScope,
}, start => {pg, start_link, [PgScope]},
[ restart => transient,
#{ shutdown => ?WORKER_WAIT,
id => PgScope, type => worker,
start => {pg, start_link, [PgScope]}, modules => [pg]
restart => transient, },
shutdown => ?WORKER_WAIT, #{
type => worker, id => rabbit_mqtt_retainer_sup,
modules => [pg] start => {rabbit_mqtt_retainer_sup, start_link,
}, [{local, rabbit_mqtt_retainer_sup}]},
#{ restart => transient,
id => rabbit_mqtt_retainer_sup, shutdown => ?SUPERVISOR_WAIT,
start => type => supervisor,
{rabbit_mqtt_retainer_sup, start_link, [{local, rabbit_mqtt_retainer_sup}]}, modules => [rabbit_mqtt_retainer_sup]
restart => transient, }
shutdown => ?SUPERVISOR_WAIT, | listener_specs(
type => supervisor, fun tcp_listener_spec/1,
modules => [rabbit_mqtt_retainer_sup] [SocketOpts, NumTcpAcceptors, ConcurrentConnsSups],
} Listeners
| listener_specs( ) ++
fun tcp_listener_spec/1, listener_specs(
[SocketOpts, NumTcpAcceptors, ConcurrentConnsSups], fun ssl_listener_spec/1,
Listeners [SocketOpts, SslOpts, NumSslAcceptors, ConcurrentConnsSups],
) ++ SslListeners
listener_specs( )
fun ssl_listener_spec/1, ]}}.
[SocketOpts, SslOpts, NumSslAcceptors, ConcurrentConnsSups],
SslListeners
)
]
}}.
-spec stop_listeners() -> ok. -spec stop_listeners() -> ok.
stop_listeners() -> stop_listeners() ->
@ -98,33 +89,33 @@ listener_specs(Fun, Args, Listeners) ->
tcp_listener_spec([Address, SocketOpts, NumAcceptors, ConcurrentConnsSups]) -> tcp_listener_spec([Address, SocketOpts, NumAcceptors, ConcurrentConnsSups]) ->
rabbit_networking:tcp_listener_spec( rabbit_networking:tcp_listener_spec(
rabbit_mqtt_listener_sup, rabbit_mqtt_listener_sup,
Address, Address,
SocketOpts, SocketOpts,
transport(?TCP_PROTOCOL), transport(?TCP_PROTOCOL),
rabbit_mqtt_reader, rabbit_mqtt_reader,
[], [],
mqtt, mqtt,
NumAcceptors, NumAcceptors,
ConcurrentConnsSups, ConcurrentConnsSups,
worker, worker,
"MQTT TCP listener" "MQTT TCP listener"
). ).
ssl_listener_spec([Address, SocketOpts, SslOpts, NumAcceptors, ConcurrentConnsSups]) -> ssl_listener_spec([Address, SocketOpts, SslOpts, NumAcceptors, ConcurrentConnsSups]) ->
rabbit_networking:tcp_listener_spec( rabbit_networking:tcp_listener_spec(
rabbit_mqtt_listener_sup, rabbit_mqtt_listener_sup,
Address, Address,
SocketOpts ++ SslOpts, SocketOpts ++ SslOpts,
transport(?TLS_PROTOCOL), transport(?TLS_PROTOCOL),
rabbit_mqtt_reader, rabbit_mqtt_reader,
[], [],
'mqtt/ssl', 'mqtt/ssl',
NumAcceptors, NumAcceptors,
ConcurrentConnsSups, ConcurrentConnsSups,
worker, worker,
"MQTT TLS listener" "MQTT TLS listener"
). ).
transport(?TCP_PROTOCOL) -> transport(?TCP_PROTOCOL) ->
ranch_tcp; ranch_tcp;

View File

@ -11,21 +11,20 @@
-include("rabbit_mqtt.hrl"). -include("rabbit_mqtt.hrl").
-include("rabbit_mqtt_packet.hrl"). -include("rabbit_mqtt_packet.hrl").
-export([ -export([queue_name_bin/2,
queue_name_bin/2, qos_from_queue_name/2,
qos_from_queue_name/2, env/1,
env/1, table_lookup/2,
table_lookup/2, path_for/2,
path_for/2, path_for/3,
path_for/3, vhost_name_to_table_name/1,
vhost_name_to_table_name/1, register_clientid/2,
register_clientid/2, remove_duplicate_clientid_connections/2,
remove_duplicate_clientid_connections/2, init_sparkplug/0,
init_sparkplug/0, mqtt_to_amqp/1,
mqtt_to_amqp/1, amqp_to_mqtt/1,
amqp_to_mqtt/1, truncate_binary/2
truncate_binary/2 ]).
]).
-define(MAX_TOPIC_TRANSLATION_CACHE_SIZE, 12). -define(MAX_TOPIC_TRANSLATION_CACHE_SIZE, 12).
-define(SPARKPLUG_MP_MQTT_TO_AMQP, sparkplug_mp_mqtt_to_amqp). -define(SPARKPLUG_MP_MQTT_TO_AMQP, sparkplug_mp_mqtt_to_amqp).
@ -73,8 +72,7 @@ init_sparkplug() ->
-spec mqtt_to_amqp(binary()) -> binary(). -spec mqtt_to_amqp(binary()) -> binary().
mqtt_to_amqp(Topic) -> mqtt_to_amqp(Topic) ->
T = T = case persistent_term:get(?SPARKPLUG_MP_MQTT_TO_AMQP, no_sparkplug) of
case persistent_term:get(?SPARKPLUG_MP_MQTT_TO_AMQP, no_sparkplug) of
no_sparkplug -> no_sparkplug ->
Topic; Topic;
M2A_SpRe -> M2A_SpRe ->
@ -104,13 +102,12 @@ amqp_to_mqtt(Topic) ->
end. end.
cached(CacheName, Fun, Arg) -> cached(CacheName, Fun, Arg) ->
Cache = Cache = case get(CacheName) of
case get(CacheName) of undefined ->
undefined -> [];
[]; Other ->
Other -> Other
Other end,
end,
case lists:keyfind(Arg, 1, Cache) of case lists:keyfind(Arg, 1, Cache) of
{_, V} -> {_, V} ->
V; V;
@ -145,11 +142,11 @@ env(Key) ->
coerce_env_value(default_pass, Val) -> rabbit_data_coercion:to_binary(Val); coerce_env_value(default_pass, Val) -> rabbit_data_coercion:to_binary(Val);
coerce_env_value(default_user, Val) -> rabbit_data_coercion:to_binary(Val); coerce_env_value(default_user, Val) -> rabbit_data_coercion:to_binary(Val);
coerce_env_value(exchange, Val) -> rabbit_data_coercion:to_binary(Val); coerce_env_value(exchange, Val) -> rabbit_data_coercion:to_binary(Val);
coerce_env_value(vhost, Val) -> rabbit_data_coercion:to_binary(Val); coerce_env_value(vhost, Val) -> rabbit_data_coercion:to_binary(Val);
coerce_env_value(_, Val) -> Val. coerce_env_value(_, Val) -> Val.
-spec table_lookup(rabbit_framing:amqp_table() | undefined, binary()) -> -spec table_lookup(rabbit_framing:amqp_table() | undefined, binary()) ->
tuple() | undefined. tuple() | undefined.
table_lookup(undefined, _Key) -> table_lookup(undefined, _Key) ->
undefined; undefined;
@ -164,11 +161,11 @@ vhost_name_to_dir_name(VHost, Suffix) ->
-spec path_for(file:name_all(), rabbit_types:vhost()) -> file:filename_all(). -spec path_for(file:name_all(), rabbit_types:vhost()) -> file:filename_all().
path_for(Dir, VHost) -> path_for(Dir, VHost) ->
filename:join(Dir, vhost_name_to_dir_name(VHost)). filename:join(Dir, vhost_name_to_dir_name(VHost)).
-spec path_for(file:name_all(), rabbit_types:vhost(), string()) -> file:filename_all(). -spec path_for(file:name_all(), rabbit_types:vhost(), string()) -> file:filename_all().
path_for(Dir, VHost, Suffix) -> path_for(Dir, VHost, Suffix) ->
filename:join(Dir, vhost_name_to_dir_name(VHost, Suffix)). filename:join(Dir, vhost_name_to_dir_name(VHost, Suffix)).
-spec vhost_name_to_table_name(rabbit_types:vhost()) -> -spec vhost_name_to_table_name(rabbit_types:vhost()) ->
atom(). atom().
@ -177,9 +174,8 @@ vhost_name_to_table_name(VHost) ->
list_to_atom("rabbit_mqtt_retained_" ++ rabbit_misc:format("~36.16.0b", [Num])). list_to_atom("rabbit_mqtt_retained_" ++ rabbit_misc:format("~36.16.0b", [Num])).
-spec register_clientid(rabbit_types:vhost(), binary()) -> ok. -spec register_clientid(rabbit_types:vhost(), binary()) -> ok.
register_clientid(Vhost, ClientId) when register_clientid(Vhost, ClientId)
is_binary(Vhost), is_binary(ClientId) when is_binary(Vhost), is_binary(ClientId) ->
->
PgGroup = {Vhost, ClientId}, PgGroup = {Vhost, ClientId},
ok = pg:join(persistent_term:get(?PG_SCOPE), PgGroup, self()), ok = pg:join(persistent_term:get(?PG_SCOPE), PgGroup, self()),
case rabbit_mqtt_ff:track_client_id_in_ra() of case rabbit_mqtt_ff:track_client_id_in_ra() of
@ -187,12 +183,10 @@ register_clientid(Vhost, ClientId) when
%% Ra node takes care of removing duplicate client ID connections. %% Ra node takes care of removing duplicate client ID connections.
ok; ok;
false -> false ->
ok = erpc:multicast( ok = erpc:multicast([node() | nodes()],
[node() | nodes()], ?MODULE,
?MODULE, remove_duplicate_clientid_connections,
remove_duplicate_clientid_connections, [PgGroup, self()])
[PgGroup, self()]
)
end. end.
-spec remove_duplicate_clientid_connections({rabbit_types:vhost(), binary()}, pid()) -> ok. -spec remove_duplicate_clientid_connections({rabbit_types:vhost(), binary()}, pid()) -> ok.
@ -200,24 +194,18 @@ remove_duplicate_clientid_connections(PgGroup, PidToKeep) ->
try persistent_term:get(?PG_SCOPE) of try persistent_term:get(?PG_SCOPE) of
PgScope -> PgScope ->
Pids = pg:get_local_members(PgScope, PgGroup), Pids = pg:get_local_members(PgScope, PgGroup),
lists:foreach( lists:foreach(fun(Pid) ->
fun(Pid) -> gen_server:cast(Pid, duplicate_id)
gen_server:cast(Pid, duplicate_id) end, Pids -- [PidToKeep])
end, catch _:badarg ->
Pids -- [PidToKeep] %% MQTT supervision tree on this node not fully started
) ok
catch
_:badarg ->
%% MQTT supervision tree on this node not fully started
ok
end. end.
-spec truncate_binary(binary(), non_neg_integer()) -> binary(). -spec truncate_binary(binary(), non_neg_integer()) -> binary().
truncate_binary(Bin, Size) when truncate_binary(Bin, Size)
is_binary(Bin) andalso byte_size(Bin) =< Size when is_binary(Bin) andalso byte_size(Bin) =< Size ->
->
Bin; Bin;
truncate_binary(Bin, Size) when truncate_binary(Bin, Size)
is_binary(Bin) when is_binary(Bin) ->
->
binary:part(Bin, 0, Size). binary:part(Bin, 0, Size).

File diff suppressed because it is too large Load Diff

View File

@ -9,42 +9,36 @@
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-import(util, [ -import(util, [expect_publishes/3,
expect_publishes/3, connect/3,
connect/3, connect/4,
connect/4, await_exit/1]).
await_exit/1
]).
-import( -import(rabbit_ct_broker_helpers,
rabbit_ct_broker_helpers, [setup_steps/0,
[ teardown_steps/0,
setup_steps/0, get_node_config/3,
teardown_steps/0, rabbitmqctl/3,
get_node_config/3, rpc/4,
rabbitmqctl/3, stop_node/2
rpc/4, ]).
stop_node/2
]
).
-define(OPTS, [ -define(OPTS, [{connect_timeout, 1},
{connect_timeout, 1}, {ack_timeout, 1}]).
{ack_timeout, 1}
]).
all() -> all() ->
[ [
{group, cluster_size_5} {group, cluster_size_5}
]. ].
groups() -> groups() ->
[ [
{cluster_size_5, [], [ {cluster_size_5, [],
connection_id_tracking, [
connection_id_tracking_on_nodedown, connection_id_tracking,
connection_id_tracking_with_decommissioned_node connection_id_tracking_on_nodedown,
]} connection_id_tracking_with_decommissioned_node
]}
]. ].
suite() -> suite() ->
@ -56,12 +50,11 @@ suite() ->
merge_app_env(Config) -> merge_app_env(Config) ->
rabbit_ct_helpers:merge_app_env( rabbit_ct_helpers:merge_app_env(
Config, Config,
{rabbit, [ {rabbit, [
{collect_statistics, basic}, {collect_statistics, basic},
{collect_statistics_interval, 100} {collect_statistics_interval, 100}
]} ]}).
).
init_per_suite(Config) -> init_per_suite(Config) ->
rabbit_ct_helpers:log_environment(), rabbit_ct_helpers:log_environment(),
@ -72,8 +65,7 @@ end_per_suite(Config) ->
init_per_group(cluster_size_5, Config) -> init_per_group(cluster_size_5, Config) ->
rabbit_ct_helpers:set_config( rabbit_ct_helpers:set_config(
Config, [{rmq_nodes_count, 5}] Config, [{rmq_nodes_count, 5}]).
).
end_per_group(_, Config) -> end_per_group(_, Config) ->
Config. Config.
@ -83,25 +75,19 @@ init_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:log_environment(), rabbit_ct_helpers:log_environment(),
Config1 = rabbit_ct_helpers:set_config(Config, [ Config1 = rabbit_ct_helpers:set_config(Config, [
{rmq_nodename_suffix, Testcase}, {rmq_nodename_suffix, Testcase},
{rmq_extra_tcp_ports, [ {rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
tcp_port_mqtt_extra, tcp_port_mqtt_tls_extra]},
tcp_port_mqtt_tls_extra
]},
{rmq_nodes_clustered, true} {rmq_nodes_clustered, true}
]), ]),
rabbit_ct_helpers:run_setup_steps( rabbit_ct_helpers:run_setup_steps(Config1,
Config1, [ fun merge_app_env/1 ] ++
[fun merge_app_env/1] ++ setup_steps() ++
setup_steps() ++ rabbit_ct_client_helpers:setup_steps()).
rabbit_ct_client_helpers:setup_steps()
).
end_per_testcase(Testcase, Config) -> end_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:run_teardown_steps( rabbit_ct_helpers:run_teardown_steps(Config,
Config, rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_client_helpers:teardown_steps() ++ teardown_steps()),
teardown_steps()
),
rabbit_ct_helpers:testcase_finished(Config, Testcase). rabbit_ct_helpers:testcase_finished(Config, Testcase).
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
@ -179,15 +165,14 @@ connection_id_tracking_with_decommissioned_node(Config) ->
%% Helpers %% Helpers
%% %%
assert_connection_count(_Config, 0, _, NumElements) -> assert_connection_count(_Config, 0, _, NumElements) ->
ct:fail("failed to match connection count ~b", [NumElements]); ct:fail("failed to match connection count ~b", [NumElements]);
assert_connection_count(Config, Retries, NodeId, NumElements) -> assert_connection_count(Config, Retries, NodeId, NumElements) ->
case util:all_connection_pids(Config) of case util:all_connection_pids(Config) of
Pids when Pids
length(Pids) =:= NumElements when length(Pids) =:= NumElements ->
->
ok; ok;
_ -> _ ->
timer:sleep(500), timer:sleep(500),
assert_connection_count(Config, Retries - 1, NodeId, NumElements) assert_connection_count(Config, Retries-1, NodeId, NumElements)
end. end.

View File

@ -4,6 +4,7 @@
%% %%
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved. %% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
-module(command_SUITE). -module(command_SUITE).
-compile([export_all, nowarn_export_all]). -compile([export_all, nowarn_export_all]).
@ -16,45 +17,39 @@
all() -> all() ->
[ [
{group, non_parallel_tests} {group, non_parallel_tests}
]. ].
groups() -> groups() ->
[ [
{non_parallel_tests, [], [ {non_parallel_tests, [], [
merge_defaults, merge_defaults,
run run
]} ]}
]. ].
suite() -> suite() ->
[ [
{timetrap, {minutes, 3}} {timetrap, {minutes, 3}}
]. ].
init_per_suite(Config) -> init_per_suite(Config) ->
rabbit_ct_helpers:log_environment(), rabbit_ct_helpers:log_environment(),
Config1 = rabbit_ct_helpers:set_config(Config, [ Config1 = rabbit_ct_helpers:set_config(Config, [
{rmq_nodename_suffix, ?MODULE}, {rmq_nodename_suffix, ?MODULE},
{rmq_extra_tcp_ports, [ {rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
tcp_port_mqtt_extra, tcp_port_mqtt_tls_extra]},
tcp_port_mqtt_tls_extra
]},
{rmq_nodes_clustered, true}, {rmq_nodes_clustered, true},
{rmq_nodes_count, 3} {rmq_nodes_count, 3}
]), ]),
rabbit_ct_helpers:run_setup_steps( rabbit_ct_helpers:run_setup_steps(Config1,
Config1, rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_broker_helpers:setup_steps() ++ rabbit_ct_client_helpers:setup_steps()).
rabbit_ct_client_helpers:setup_steps()
).
end_per_suite(Config) -> end_per_suite(Config) ->
rabbit_ct_helpers:run_teardown_steps( rabbit_ct_helpers:run_teardown_steps(Config,
Config, rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_client_helpers:teardown_steps() ++ rabbit_ct_broker_helpers:teardown_steps()).
rabbit_ct_broker_helpers:teardown_steps()
).
init_per_group(_, Config) -> init_per_group(_, Config) ->
Config. Config.
@ -78,6 +73,7 @@ merge_defaults(_Config) ->
{[<<"other_key">>], #{verbose := false}} = {[<<"other_key">>], #{verbose := false}} =
?COMMAND:merge_defaults([<<"other_key">>], #{verbose => false}). ?COMMAND:merge_defaults([<<"other_key">>], #{verbose => false}).
run(Config) -> run(Config) ->
Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
Opts = #{node => Node, timeout => 10_000, verbose => false}, Opts = #{node => Node, timeout => 10_000, verbose => false},
@ -95,27 +91,18 @@ run(Config) ->
C2 = connect(<<"simpleClient1">>, Config, [{ack_timeout, 1}]), C2 = connect(<<"simpleClient1">>, Config, [{ack_timeout, 1}]),
timer:sleep(200), timer:sleep(200),
[ [[{client_id, <<"simpleClient">>}, {user, <<"guest">>}],
[{client_id, <<"simpleClient">>}, {user, <<"guest">>}], [{client_id, <<"simpleClient1">>}, {user, <<"guest">>}]] =
[{client_id, <<"simpleClient1">>}, {user, <<"guest">>}]
] =
lists:sort( lists:sort(
'Elixir.Enum':to_list( 'Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>, <<"user">>],
?COMMAND:run( Opts))),
[<<"client_id">>, <<"user">>],
Opts
)
)
),
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp), Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
start_amqp_connection(network, Node, Port), start_amqp_connection(network, Node, Port),
%% There are still just two MQTT connections %% There are still just two MQTT connections
[ [[{client_id, <<"simpleClient">>}],
[{client_id, <<"simpleClient">>}], [{client_id, <<"simpleClient1">>}]] =
[{client_id, <<"simpleClient1">>}]
] =
lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts))), lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts))),
start_amqp_connection(direct, Node, Port), start_amqp_connection(direct, Node, Port),
@ -123,19 +110,14 @@ run(Config) ->
%% Still two MQTT connections %% Still two MQTT connections
?assertEqual( ?assertEqual(
[ [[{client_id, <<"simpleClient">>}],
[{client_id, <<"simpleClient">>}], [{client_id, <<"simpleClient1">>}]],
[{client_id, <<"simpleClient1">>}] lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts)))),
],
lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts)))
),
%% Verbose returns all keys %% Verbose returns all keys
AllKeys = lists:map(fun(I) -> atom_to_binary(I) end, ?INFO_ITEMS), AllKeys = lists:map(fun(I) -> atom_to_binary(I) end, ?INFO_ITEMS),
[AllInfos1Con1, _AllInfos1Con2] = 'Elixir.Enum':to_list(?COMMAND:run(AllKeys, Opts)), [AllInfos1Con1, _AllInfos1Con2] = 'Elixir.Enum':to_list(?COMMAND:run(AllKeys, Opts)),
[AllInfos2Con1, _AllInfos2Con2] = 'Elixir.Enum':to_list( [AllInfos2Con1, _AllInfos2Con2] = 'Elixir.Enum':to_list(?COMMAND:run([], Opts#{verbose => true})),
?COMMAND:run([], Opts#{verbose => true})
),
%% Keys are INFO_ITEMS %% Keys are INFO_ITEMS
InfoItemsSorted = lists:sort(?INFO_ITEMS), InfoItemsSorted = lists:sort(?INFO_ITEMS),

View File

@ -5,10 +5,8 @@
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved. %% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
-module(config_SUITE). -module(config_SUITE).
-compile([ -compile([export_all,
export_all, nowarn_export_all]).
nowarn_export_all
]).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
@ -16,16 +14,17 @@
all() -> all() ->
[ [
{group, mnesia} {group, mnesia}
]. ].
groups() -> groups() ->
[ [
{mnesia, [shuffle], [ {mnesia, [shuffle],
rabbitmq_default, [
environment_set, rabbitmq_default,
flag_set environment_set,
]} flag_set
]}
]. ].
suite() -> suite() ->
@ -46,12 +45,8 @@ init_per_testcase(rabbitmq_default = Test, Config) ->
init_per_testcase0(Test, Config); init_per_testcase0(Test, Config);
init_per_testcase(environment_set = Test, Config0) -> init_per_testcase(environment_set = Test, Config0) ->
Config = rabbit_ct_helpers:merge_app_env( Config = rabbit_ct_helpers:merge_app_env(
Config0, Config0, {mnesia, [{dump_log_write_threshold, 25000},
{mnesia, [ {dump_log_time_threshold, 60000}]}),
{dump_log_write_threshold, 25000},
{dump_log_time_threshold, 60000}
]}
),
init_per_testcase0(Test, Config); init_per_testcase0(Test, Config);
init_per_testcase(flag_set = Test, Config0) -> init_per_testcase(flag_set = Test, Config0) ->
Config = [{additional_erl_args, "-mnesia dump_log_write_threshold 15000"} | Config0], Config = [{additional_erl_args, "-mnesia dump_log_write_threshold 15000"} | Config0],
@ -60,19 +55,17 @@ init_per_testcase(flag_set = Test, Config0) ->
init_per_testcase0(Testcase, Config0) -> init_per_testcase0(Testcase, Config0) ->
Config1 = rabbit_ct_helpers:set_config(Config0, {rmq_nodename_suffix, Testcase}), Config1 = rabbit_ct_helpers:set_config(Config0, {rmq_nodename_suffix, Testcase}),
Config = rabbit_ct_helpers:run_steps( Config = rabbit_ct_helpers:run_steps(
Config1, Config1,
rabbit_ct_broker_helpers:setup_steps() ++ rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_client_helpers:setup_steps() rabbit_ct_client_helpers:setup_steps()),
),
rabbit_ct_helpers:testcase_started(Config, Testcase). rabbit_ct_helpers:testcase_started(Config, Testcase).
end_per_testcase(Testcase, Config0) -> end_per_testcase(Testcase, Config0) ->
Config = rabbit_ct_helpers:testcase_finished(Config0, Testcase), Config = rabbit_ct_helpers:testcase_finished(Config0, Testcase),
rabbit_ct_helpers:run_teardown_steps( rabbit_ct_helpers:run_teardown_steps(
Config, Config,
rabbit_ct_client_helpers:teardown_steps() ++ rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_broker_helpers:teardown_steps() rabbit_ct_broker_helpers:teardown_steps()).
).
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
%% Testsuite cases %% Testsuite cases
@ -81,33 +74,21 @@ end_per_testcase(Testcase, Config0) ->
%% The MQTT plugin expects Mnesia dump_log_write_threshold to be increased %% The MQTT plugin expects Mnesia dump_log_write_threshold to be increased
%% from 1000 (Mnesia default) to 5000 (RabbitMQ default). %% from 1000 (Mnesia default) to 5000 (RabbitMQ default).
rabbitmq_default(Config) -> rabbitmq_default(Config) ->
?assertEqual( ?assertEqual(5_000,
5_000, rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])),
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold]) ?assertEqual(90_000,
), rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])).
?assertEqual(
90_000,
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
).
%% User configured setting in advanced.config should be respected. %% User configured setting in advanced.config should be respected.
environment_set(Config) -> environment_set(Config) ->
?assertEqual( ?assertEqual(25_000,
25_000, rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])),
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold]) ?assertEqual(60_000,
), rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])).
?assertEqual(
60_000,
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
).
%% User configured setting in RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS should be respected. %% User configured setting in RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS should be respected.
flag_set(Config) -> flag_set(Config) ->
?assertEqual( ?assertEqual(15_000,
15_000, rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])),
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold]) ?assertEqual(90_000,
), rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])).
?assertEqual(
90_000,
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
).

View File

@ -23,6 +23,7 @@ init_per_suite(Config) ->
Config1 = rabbit_ct_helpers:run_setup_steps(Config), Config1 = rabbit_ct_helpers:run_setup_steps(Config),
rabbit_ct_config_schema:init_schemas(rabbitmq_mqtt, Config1). rabbit_ct_config_schema:init_schemas(rabbitmq_mqtt, Config1).
end_per_suite(Config) -> end_per_suite(Config) ->
rabbit_ct_helpers:run_teardown_steps(Config). rabbit_ct_helpers:run_teardown_steps(Config).
@ -30,19 +31,15 @@ init_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:testcase_started(Config, Testcase), rabbit_ct_helpers:testcase_started(Config, Testcase),
Config1 = rabbit_ct_helpers:set_config(Config, [ Config1 = rabbit_ct_helpers:set_config(Config, [
{rmq_nodename_suffix, Testcase} {rmq_nodename_suffix, Testcase}
]), ]),
rabbit_ct_helpers:run_steps( rabbit_ct_helpers:run_steps(Config1,
Config1, rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_broker_helpers:setup_steps() ++ rabbit_ct_client_helpers:setup_steps()).
rabbit_ct_client_helpers:setup_steps()
).
end_per_testcase(Testcase, Config) -> end_per_testcase(Testcase, Config) ->
Config1 = rabbit_ct_helpers:run_steps( Config1 = rabbit_ct_helpers:run_steps(Config,
Config, rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_client_helpers:teardown_steps() ++ rabbit_ct_broker_helpers:teardown_steps()),
rabbit_ct_broker_helpers:teardown_steps()
),
rabbit_ct_helpers:testcase_finished(Config1, Testcase). rabbit_ct_helpers:testcase_finished(Config1, Testcase).
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
@ -50,13 +47,9 @@ end_per_testcase(Testcase, Config) ->
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
run_snippets(Config) -> run_snippets(Config) ->
ok = rabbit_ct_broker_helpers:rpc( ok = rabbit_ct_broker_helpers:rpc(Config, 0,
Config, ?MODULE, run_snippets1, [Config]).
0,
?MODULE,
run_snippets1,
[Config]
).
run_snippets1(Config) -> run_snippets1(Config) ->
rabbit_ct_config_schema:run_snippets(Config). rabbit_ct_config_schema:run_snippets(Config).

View File

@ -15,11 +15,10 @@
init(_) -> init(_) ->
{ok, ?INIT_STATE}. {ok, ?INIT_STATE}.
handle_event(#event{type = T}, State) when handle_event(#event{type = T}, State)
T =:= node_stats orelse when T =:= node_stats orelse
T =:= node_node_stats orelse T =:= node_node_stats orelse
T =:= node_node_deleted T =:= node_node_deleted ->
->
{ok, State}; {ok, State};
handle_event(Event, State) -> handle_event(Event, State) ->
{ok, [Event | State]}. {ok, [Event | State]}.

View File

@ -13,31 +13,27 @@
-import(rabbit_ct_broker_helpers, [rpc/5]). -import(rabbit_ct_broker_helpers, [rpc/5]).
-import(rabbit_ct_helpers, [eventually/1]). -import(rabbit_ct_helpers, [eventually/1]).
-import(util, [ -import(util, [expect_publishes/3,
expect_publishes/3, get_global_counters/4,
get_global_counters/4, connect/2,
connect/2, connect/4]).
connect/4
]).
-define(PROTO_VER, v4). -define(PROTO_VER, v4).
all() -> all() ->
[ [
{group, cluster_size_3} {group, cluster_size_3}
]. ].
groups() -> groups() ->
[ [
{cluster_size_3, [], [ {cluster_size_3, [], [delete_ra_cluster_mqtt_node,
delete_ra_cluster_mqtt_node, rabbit_mqtt_qos0_queue]}
rabbit_mqtt_qos0_queue
]}
]. ].
suite() -> suite() ->
[ [
{timetrap, {minutes, 2}} {timetrap, {minutes, 2}}
]. ].
init_per_suite(Config) -> init_per_suite(Config) ->
@ -48,36 +44,26 @@ end_per_suite(Config) ->
rabbit_ct_helpers:run_teardown_steps(Config). rabbit_ct_helpers:run_teardown_steps(Config).
init_per_group(Group = cluster_size_3, Config0) -> init_per_group(Group = cluster_size_3, Config0) ->
Config1 = rabbit_ct_helpers:set_config(Config0, [ Config1 = rabbit_ct_helpers:set_config(Config0, [{rmq_nodes_count, 3},
{rmq_nodes_count, 3}, {rmq_nodename_suffix, Group}]),
{rmq_nodename_suffix, Group}
]),
Config = rabbit_ct_helpers:merge_app_env( Config = rabbit_ct_helpers:merge_app_env(
Config1, {rabbit, [{forced_feature_flags_on_init, []}]} Config1, {rabbit, [{forced_feature_flags_on_init, []}]}),
), rabbit_ct_helpers:run_steps(Config,
rabbit_ct_helpers:run_steps( rabbit_ct_broker_helpers:setup_steps() ++
Config, rabbit_ct_client_helpers:setup_steps()).
rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_client_helpers:setup_steps()
).
end_per_group(_Group, Config) -> end_per_group(_Group, Config) ->
rabbit_ct_helpers:run_steps( rabbit_ct_helpers:run_steps(Config,
Config, rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_client_helpers:teardown_steps() ++ rabbit_ct_broker_helpers:teardown_steps()).
rabbit_ct_broker_helpers:teardown_steps()
).
init_per_testcase(TestCase, Config) -> init_per_testcase(TestCase, Config) ->
case rabbit_ct_broker_helpers:is_feature_flag_supported(Config, TestCase) of case rabbit_ct_broker_helpers:is_feature_flag_supported(Config, TestCase) of
true -> true ->
Config; Config;
false -> false ->
{skip, {skip, io_lib:format("feature flag ~s is unsupported",
io_lib:format( [TestCase])}
"feature flag ~s is unsupported",
[TestCase]
)}
end. end.
end_per_testcase(_TestCase, Config) -> end_per_testcase(_TestCase, Config) ->
@ -90,27 +76,16 @@ delete_ra_cluster_mqtt_node(Config) ->
%% old client ID tracking works %% old client ID tracking works
?assertEqual(1, length(util:all_connection_pids(Config))), ?assertEqual(1, length(util:all_connection_pids(Config))),
%% Ra processes are alive %% Ra processes are alive
?assert( ?assert(lists:all(fun erlang:is_pid/1,
lists:all( rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node]))),
fun erlang:is_pid/1,
rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])
)
),
?assertEqual( ?assertEqual(ok,
ok, rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)),
rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)
),
%% Ra processes should be gone %% Ra processes should be gone
rabbit_ct_helpers:eventually( rabbit_ct_helpers:eventually(
?_assert( ?_assert(lists:all(fun(Pid) -> Pid =:= undefined end,
lists:all( rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])))),
fun(Pid) -> Pid =:= undefined end,
rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])
)
)
),
%% new client ID tracking works %% new client ID tracking works
?assertEqual(1, length(util:all_connection_pids(Config))), ?assertEqual(1, length(util:all_connection_pids(Config))),
?assert(erlang:is_process_alive(C)), ?assert(erlang:is_process_alive(C)),
@ -124,30 +99,20 @@ rabbit_mqtt_qos0_queue(Config) ->
{ok, _, [0]} = emqtt:subscribe(C1, Topic, qos0), {ok, _, [0]} = emqtt:subscribe(C1, Topic, qos0),
ok = emqtt:publish(C1, Topic, Msg, qos0), ok = emqtt:publish(C1, Topic, Msg, qos0),
ok = expect_publishes(C1, Topic, [Msg]), ok = expect_publishes(C1, Topic, [Msg]),
?assertEqual( ?assertEqual(1,
1, length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))),
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))
),
?assertEqual( ?assertEqual(ok,
ok, rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)),
rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)
),
%% Queue type does not chanage for existing connection. %% Queue type does not chanage for existing connection.
?assertEqual( ?assertEqual(1,
1, length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))),
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))
),
ok = emqtt:publish(C1, Topic, Msg, qos0), ok = emqtt:publish(C1, Topic, Msg, qos0),
ok = expect_publishes(C1, Topic, [Msg]), ok = expect_publishes(C1, Topic, [Msg]),
?assertMatch( ?assertMatch(#{messages_delivered_total := 2,
#{ messages_delivered_consume_auto_ack_total := 2},
messages_delivered_total := 2, get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, rabbit_classic_queue}])),
messages_delivered_consume_auto_ack_total := 2
},
get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, rabbit_classic_queue}])
),
%% Reconnecting with the same client ID will terminate the old connection. %% Reconnecting with the same client ID will terminate the old connection.
true = unlink(C1), true = unlink(C1),
@ -155,22 +120,13 @@ rabbit_mqtt_qos0_queue(Config) ->
{ok, _, [0]} = emqtt:subscribe(C2, Topic, qos0), {ok, _, [0]} = emqtt:subscribe(C2, Topic, qos0),
%% This time, we get the new queue type. %% This time, we get the new queue type.
eventually( eventually(
?_assertEqual( ?_assertEqual(0,
0, length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue])))),
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue])) ?assertEqual(1,
) length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [FeatureFlag]))),
),
?assertEqual(
1,
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [FeatureFlag]))
),
ok = emqtt:publish(C2, Topic, Msg, qos0), ok = emqtt:publish(C2, Topic, Msg, qos0),
ok = expect_publishes(C2, Topic, [Msg]), ok = expect_publishes(C2, Topic, [Msg]),
?assertMatch( ?assertMatch(#{messages_delivered_total := 1,
#{ messages_delivered_consume_auto_ack_total := 1},
messages_delivered_total := 1, get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, FeatureFlag}])),
messages_delivered_consume_auto_ack_total := 1
},
get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, FeatureFlag}])
),
ok = emqtt:disconnect(C2). ok = emqtt:disconnect(C2).

View File

@ -12,25 +12,24 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-define(BASE_CONF_MQTT, -define(BASE_CONF_MQTT,
{rabbitmq_mqtt, [ {rabbitmq_mqtt, [
{ssl_cert_login, true}, {ssl_cert_login, true},
{allow_anonymous, false}, {allow_anonymous, false},
{sparkplug, true}, {sparkplug, true},
{tcp_listeners, []}, {tcp_listeners, []},
{ssl_listeners, []} {ssl_listeners, []}
]} ]}).
).
all() -> all() ->
[ [
{group, non_parallel_tests} {group, non_parallel_tests}
]. ].
groups() -> groups() ->
[ [
{non_parallel_tests, [], [ {non_parallel_tests, [], [
java java
]} ]}
]. ].
suite() -> suite() ->
@ -53,20 +52,16 @@ init_per_suite(Config) ->
{rmq_certspwd, "bunnychow"}, {rmq_certspwd, "bunnychow"},
{rmq_nodes_clustered, true}, {rmq_nodes_clustered, true},
{rmq_nodes_count, 3} {rmq_nodes_count, 3}
]), ]),
rabbit_ct_helpers:run_setup_steps( rabbit_ct_helpers:run_setup_steps(Config1,
Config1, [ fun merge_app_env/1 ] ++
[fun merge_app_env/1] ++ rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_broker_helpers:setup_steps() ++ rabbit_ct_client_helpers:setup_steps()).
rabbit_ct_client_helpers:setup_steps()
).
end_per_suite(Config) -> end_per_suite(Config) ->
rabbit_ct_helpers:run_teardown_steps( rabbit_ct_helpers:run_teardown_steps(Config,
Config, rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_client_helpers:teardown_steps() ++ rabbit_ct_broker_helpers:teardown_steps()).
rabbit_ct_broker_helpers:teardown_steps()
).
init_per_group(_, Config) -> init_per_group(_, Config) ->
Config. Config.
@ -79,38 +74,25 @@ init_per_testcase(Testcase, Config) ->
CertFile = filename:join([CertsDir, "client", "cert.pem"]), CertFile = filename:join([CertsDir, "client", "cert.pem"]),
{ok, CertBin} = file:read_file(CertFile), {ok, CertBin} = file:read_file(CertFile),
[{'Certificate', Cert, not_encrypted}] = public_key:pem_decode(CertBin), [{'Certificate', Cert, not_encrypted}] = public_key:pem_decode(CertBin),
UserBin = rabbit_ct_broker_helpers:rpc( UserBin = rabbit_ct_broker_helpers:rpc(Config, 0,
Config, rabbit_ssl,
0, peer_cert_auth_name,
rabbit_ssl, [Cert]),
peer_cert_auth_name,
[Cert]
),
User = binary_to_list(UserBin), User = binary_to_list(UserBin),
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["add_user", User, ""]), {ok,_} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["add_user", User, ""]),
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, [ {ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["set_permissions", "-p", "/", User, ".*", ".*", ".*"]),
"set_permissions", "-p", "/", User, ".*", ".*", ".*" {ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0,
]), ["set_topic_permissions", "-p", "/", "guest", "amq.topic",
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(
Config,
0,
[
"set_topic_permissions",
"-p",
"/",
"guest",
"amq.topic",
% Write permission % Write permission
"test-topic|test-retained-topic|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+", "test-topic|test-retained-topic|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+",
% Read permission % Read permission
"test-topic|test-retained-topic|last-will|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+" "test-topic|test-retained-topic|last-will|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+"]),
]
),
rabbit_ct_helpers:testcase_started(Config, Testcase). rabbit_ct_helpers:testcase_started(Config, Testcase).
end_per_testcase(Testcase, Config) -> end_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:testcase_finished(Config, Testcase). rabbit_ct_helpers:testcase_finished(Config, Testcase).
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
%% Testsuite cases %% Testsuite cases
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
@ -140,5 +122,5 @@ q(P, [K | Rem]) ->
undefined -> undefined; undefined -> undefined;
V -> q(V, Rem) V -> q(V, Rem)
end; end;
q(P, []) -> q(P, []) -> {ok, P}.
{ok, P}.

View File

@ -12,19 +12,20 @@
all() -> all() ->
[ [
{group, tests} {group, tests}
]. ].
all_tests() -> all_tests() ->
[ [
basics, basics,
machine_upgrade, machine_upgrade,
many_downs many_downs
]. ].
groups() -> groups() ->
[ [
{tests, [], all_tests()} {tests, [], all_tests()}
]. ].
init_per_suite(Config) -> init_per_suite(Config) ->
@ -52,16 +53,13 @@ end_per_testcase(_TestCase, _Config) ->
basics(_Config) -> basics(_Config) ->
S0 = mqtt_machine:init(#{}), S0 = mqtt_machine:init(#{}),
ClientId = <<"id1">>, ClientId = <<"id1">>,
OthPid = spawn(fun() -> ok end), OthPid = spawn(fun () -> ok end),
{S1, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, self()}, S0), {S1, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, self()}, S0),
?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 1, S1), ?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 1, S1),
?assertMatch(#machine_state{pids = Pids} when map_size(Pids) == 1, S1), ?assertMatch(#machine_state{pids = Pids} when map_size(Pids) == 1, S1),
{S2, ok, _} = mqtt_machine:apply(meta(2), {register, ClientId, OthPid}, S1), {S2, ok, _} = mqtt_machine:apply(meta(2), {register, ClientId, OthPid}, S1),
?assertMatch( ?assertMatch(#machine_state{client_ids = #{ClientId := OthPid} = Ids}
#machine_state{client_ids = #{ClientId := OthPid} = Ids} when when map_size(Ids) == 1, S2),
map_size(Ids) == 1,
S2
),
{S3, ok, _} = mqtt_machine:apply(meta(3), {down, OthPid, noproc}, S2), {S3, ok, _} = mqtt_machine:apply(meta(3), {down, OthPid, noproc}, S2),
?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 0, S3), ?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 0, S3),
{S4, ok, _} = mqtt_machine:apply(meta(3), {unregister, ClientId, OthPid}, S2), {S4, ok, _} = mqtt_machine:apply(meta(3), {unregister, ClientId, OthPid}, S2),
@ -76,63 +74,41 @@ machine_upgrade(_Config) ->
{S1, ok, _} = mqtt_machine_v0:apply(meta(1), {register, ClientId, self()}, S0), {S1, ok, _} = mqtt_machine_v0:apply(meta(1), {register, ClientId, self()}, S0),
?assertMatch({machine_state, Ids} when map_size(Ids) == 1, S1), ?assertMatch({machine_state, Ids} when map_size(Ids) == 1, S1),
{S2, ok, _} = mqtt_machine:apply(meta(2), {machine_version, 0, 1}, S1), {S2, ok, _} = mqtt_machine:apply(meta(2), {machine_version, 0, 1}, S1),
?assertMatch( ?assertMatch(#machine_state{client_ids = #{ClientId := Self},
#machine_state{ pids = #{Self := [ClientId]} = Pids}
client_ids = #{ClientId := Self}, when map_size(Pids) == 1, S2),
pids = #{Self := [ClientId]} = Pids
} when
map_size(Pids) == 1,
S2
),
{S3, ok, _} = mqtt_machine:apply(meta(3), {down, self(), noproc}, S2), {S3, ok, _} = mqtt_machine:apply(meta(3), {down, self(), noproc}, S2),
?assertMatch( ?assertMatch(#machine_state{client_ids = Ids,
#machine_state{ pids = Pids}
client_ids = Ids, when map_size(Ids) == 0 andalso map_size(Pids) == 0, S3),
pids = Pids
} when
map_size(Ids) == 0 andalso map_size(Pids) == 0,
S3
),
ok. ok.
many_downs(_Config) -> many_downs(_Config) ->
S0 = mqtt_machine:init(#{}), S0 = mqtt_machine:init(#{}),
Clients = [ Clients = [{list_to_binary(integer_to_list(I)), spawn(fun() -> ok end)}
{list_to_binary(integer_to_list(I)), spawn(fun() -> ok end)} || I <- lists:seq(1, 10000)],
|| I <- lists:seq(1, 10000)
],
S1 = lists:foldl( S1 = lists:foldl(
fun({ClientId, Pid}, Acc0) -> fun ({ClientId, Pid}, Acc0) ->
{Acc, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, Pid}, Acc0), {Acc, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, Pid}, Acc0),
Acc Acc
end, end, S0, Clients),
S0,
Clients
),
_ = lists:foldl( _ = lists:foldl(
fun({_ClientId, Pid}, Acc0) -> fun ({_ClientId, Pid}, Acc0) ->
{Acc, ok, _} = mqtt_machine:apply(meta(1), {down, Pid, noproc}, Acc0), {Acc, ok, _} = mqtt_machine:apply(meta(1), {down, Pid, noproc}, Acc0),
Acc Acc
end, end, S1, Clients),
S1,
Clients
),
_ = lists:foldl( _ = lists:foldl(
fun({ClientId, Pid}, Acc0) -> fun ({ClientId, Pid}, Acc0) ->
{Acc, ok, _} = mqtt_machine:apply(meta(1), {unregister, ClientId, Pid}, Acc0), {Acc, ok, _} = mqtt_machine:apply(meta(1), {unregister, ClientId,
Acc Pid}, Acc0),
end, Acc
S0, end, S0, Clients),
Clients
),
ok. ok.
%% Utility %% Utility
meta(Idx) -> meta(Idx) ->
#{ #{index => Idx,
index => Idx, term => 1,
term => 1, ts => erlang:system_time(millisecond)}.
ts => erlang:system_time(millisecond)
}.

View File

@ -4,6 +4,7 @@
%% %%
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved. %% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
-module(processor_SUITE). -module(processor_SUITE).
-compile([export_all, nowarn_export_all]). -compile([export_all, nowarn_export_all]).
@ -13,17 +14,17 @@
all() -> all() ->
[ [
{group, non_parallel_tests} {group, non_parallel_tests}
]. ].
groups() -> groups() ->
[ [
{non_parallel_tests, [], [ {non_parallel_tests, [], [
ignores_colons_in_username_if_option_set, ignores_colons_in_username_if_option_set,
interprets_colons_in_username_if_option_not_set, interprets_colons_in_username_if_option_not_set,
get_vhosts_from_global_runtime_parameter, get_vhosts_from_global_runtime_parameter,
get_vhost get_vhost
]} ]}
]. ].
suite() -> suite() ->
@ -41,50 +42,35 @@ init_per_testcase(get_vhost, Config) ->
mnesia:start(), mnesia:start(),
mnesia:create_table(rabbit_runtime_parameters, [ mnesia:create_table(rabbit_runtime_parameters, [
{attributes, record_info(fields, runtime_parameters)}, {attributes, record_info(fields, runtime_parameters)},
{record_name, runtime_parameters} {record_name, runtime_parameters}]),
]),
Config; Config;
init_per_testcase(_, Config) -> init_per_testcase(_, Config) -> Config.
Config.
end_per_testcase(get_vhost, Config) -> end_per_testcase(get_vhost, Config) ->
mnesia:stop(), mnesia:stop(),
Config; Config;
end_per_testcase(_, Config) -> end_per_testcase(_, Config) -> Config.
Config.
ignore_colons(B) -> application:set_env(rabbitmq_mqtt, ignore_colons_in_username, B). ignore_colons(B) -> application:set_env(rabbitmq_mqtt, ignore_colons_in_username, B).
ignores_colons_in_username_if_option_set(_Config) -> ignores_colons_in_username_if_option_set(_Config) ->
ignore_colons(true), ignore_colons(true),
?assertEqual( ?assertEqual({rabbit_mqtt_util:env(vhost), <<"a:b:c">>},
{rabbit_mqtt_util:env(vhost), <<"a:b:c">>}, rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)).
rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)
).
interprets_colons_in_username_if_option_not_set(_Config) -> interprets_colons_in_username_if_option_not_set(_Config) ->
ignore_colons(false), ignore_colons(false),
?assertEqual( ?assertEqual({<<"a:b">>, <<"c">>},
{<<"a:b">>, <<"c">>}, rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)).
rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)
).
get_vhosts_from_global_runtime_parameter(_Config) -> get_vhosts_from_global_runtime_parameter(_Config) ->
MappingParameter = [ MappingParameter = [
{<<"O=client,CN=dummy1">>, <<"vhost1">>}, {<<"O=client,CN=dummy1">>, <<"vhost1">>},
{<<"O=client,CN=dummy2">>, <<"vhost2">>} {<<"O=client,CN=dummy2">>, <<"vhost2">>}
], ],
<<"vhost1">> = rabbit_mqtt_processor:get_vhost_from_user_mapping( <<"vhost1">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy1">>, MappingParameter),
<<"O=client,CN=dummy1">>, MappingParameter <<"vhost2">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy2">>, MappingParameter),
), undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy3">>, MappingParameter),
<<"vhost2">> = rabbit_mqtt_processor:get_vhost_from_user_mapping( undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy3">>, not_found).
<<"O=client,CN=dummy2">>, MappingParameter
),
undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(
<<"O=client,CN=dummy3">>, MappingParameter
),
undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(
<<"O=client,CN=dummy3">>, not_found
).
get_vhost(_Config) -> get_vhost(_Config) ->
clear_vhost_global_parameters(), clear_vhost_global_parameters(),
@ -97,35 +83,27 @@ get_vhost(_Config) ->
%% not a certificate user, no cert/vhost mapping, vhost in user %% not a certificate user, no cert/vhost mapping, vhost in user
%% should use vhost in user %% should use vhost in user
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost( {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"somevhost:guest">>, none, 1883),
<<"somevhost:guest">>, none, 1883
),
clear_vhost_global_parameters(), clear_vhost_global_parameters(),
%% certificate user, no cert/vhost mapping %% certificate user, no cert/vhost mapping
%% should use default vhost %% should use default vhost
{_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost( {_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
<<"guest">>, <<"O=client,CN=dummy">>, 1883
),
clear_vhost_global_parameters(), clear_vhost_global_parameters(),
%% certificate user, cert/vhost mapping with global runtime parameter %% certificate user, cert/vhost mapping with global runtime parameter
%% should use mapping %% should use mapping
set_global_parameter(mqtt_default_vhosts, [ set_global_parameter(mqtt_default_vhosts, [
{<<"O=client,CN=dummy">>, <<"somevhost">>}, {<<"O=client,CN=dummy">>, <<"somevhost">>},
{<<"O=client,CN=otheruser">>, <<"othervhost">>} {<<"O=client,CN=otheruser">>, <<"othervhost">>}
]), ]),
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost( {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
<<"guest">>, <<"O=client,CN=dummy">>, 1883
),
clear_vhost_global_parameters(), clear_vhost_global_parameters(),
%% certificate user, cert/vhost mapping with global runtime parameter, but no key for the user %% certificate user, cert/vhost mapping with global runtime parameter, but no key for the user
%% should use default vhost %% should use default vhost
set_global_parameter(mqtt_default_vhosts, [{<<"O=client,CN=otheruser">>, <<"somevhost">>}]), set_global_parameter(mqtt_default_vhosts, [{<<"O=client,CN=otheruser">>, <<"somevhost">>}]),
{_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost( {_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
<<"guest">>, <<"O=client,CN=dummy">>, 1883
),
clear_vhost_global_parameters(), clear_vhost_global_parameters(),
%% not a certificate user, port/vhost mapping %% not a certificate user, port/vhost mapping
@ -143,9 +121,7 @@ get_vhost(_Config) ->
{<<"1883">>, <<"somevhost">>}, {<<"1883">>, <<"somevhost">>},
{<<"1884">>, <<"othervhost">>} {<<"1884">>, <<"othervhost">>}
]), ]),
{_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost( {_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"vhostinusername:guest">>, none, 1883),
<<"vhostinusername:guest">>, none, 1883
),
clear_vhost_global_parameters(), clear_vhost_global_parameters(),
%% not a certificate user, port/vhost mapping, but no mapping for this port %% not a certificate user, port/vhost mapping, but no mapping for this port
@ -162,50 +138,42 @@ get_vhost(_Config) ->
{<<"1883">>, <<"somevhost">>}, {<<"1883">>, <<"somevhost">>},
{<<"1884">>, <<"othervhost">>} {<<"1884">>, <<"othervhost">>}
]), ]),
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost( {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
<<"guest">>, <<"O=client,CN=dummy">>, 1883
),
clear_vhost_global_parameters(), clear_vhost_global_parameters(),
%% certificate user, port/vhost parameter but no mapping, cert/vhost mapping %% certificate user, port/vhost parameter but no mapping, cert/vhost mapping
%% should use cert/vhost mapping %% should use cert/vhost mapping
set_global_parameter(mqtt_default_vhosts, [ set_global_parameter(mqtt_default_vhosts, [
{<<"O=client,CN=dummy">>, <<"somevhost">>}, {<<"O=client,CN=dummy">>, <<"somevhost">>},
{<<"O=client,CN=otheruser">>, <<"othervhost">>} {<<"O=client,CN=otheruser">>, <<"othervhost">>}
]), ]),
set_global_parameter(mqtt_port_to_vhost_mapping, [ set_global_parameter(mqtt_port_to_vhost_mapping, [
{<<"1884">>, <<"othervhost">>} {<<"1884">>, <<"othervhost">>}
]), ]),
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost( {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
<<"guest">>, <<"O=client,CN=dummy">>, 1883
),
clear_vhost_global_parameters(), clear_vhost_global_parameters(),
%% certificate user, port/vhost parameter, cert/vhost parameter %% certificate user, port/vhost parameter, cert/vhost parameter
%% cert/vhost parameter takes precedence %% cert/vhost parameter takes precedence
set_global_parameter(mqtt_default_vhosts, [ set_global_parameter(mqtt_default_vhosts, [
{<<"O=client,CN=dummy">>, <<"cert-somevhost">>}, {<<"O=client,CN=dummy">>, <<"cert-somevhost">>},
{<<"O=client,CN=otheruser">>, <<"othervhost">>} {<<"O=client,CN=otheruser">>, <<"othervhost">>}
]), ]),
set_global_parameter(mqtt_port_to_vhost_mapping, [ set_global_parameter(mqtt_port_to_vhost_mapping, [
{<<"1883">>, <<"port-vhost">>}, {<<"1883">>, <<"port-vhost">>},
{<<"1884">>, <<"othervhost">>} {<<"1884">>, <<"othervhost">>}
]), ]),
{_, {<<"cert-somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost( {_, {<<"cert-somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
<<"guest">>, <<"O=client,CN=dummy">>, 1883
),
clear_vhost_global_parameters(), clear_vhost_global_parameters(),
%% certificate user, no port/vhost or cert/vhost mapping, vhost in username %% certificate user, no port/vhost or cert/vhost mapping, vhost in username
%% should use vhost in username %% should use vhost in username
{_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost( {_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"vhostinusername:guest">>, <<"O=client,CN=dummy">>, 1883),
<<"vhostinusername:guest">>, <<"O=client,CN=dummy">>, 1883
),
%% not a certificate user, port/vhost parameter, cert/vhost parameter %% not a certificate user, port/vhost parameter, cert/vhost parameter
%% port/vhost mapping is used, as cert/vhost should not be used %% port/vhost mapping is used, as cert/vhost should not be used
set_global_parameter(mqtt_default_vhosts, [ set_global_parameter(mqtt_default_vhosts, [
{<<"O=cert">>, <<"cert-somevhost">>}, {<<"O=cert">>, <<"cert-somevhost">>},
{<<"O=client,CN=otheruser">>, <<"othervhost">>} {<<"O=client,CN=otheruser">>, <<"othervhost">>}
]), ]),
set_global_parameter(mqtt_port_to_vhost_mapping, [ set_global_parameter(mqtt_port_to_vhost_mapping, [
@ -217,15 +185,15 @@ get_vhost(_Config) ->
ok. ok.
set_global_parameter(Key, Term) -> set_global_parameter(Key, Term) ->
InsertParameterFun = fun() -> InsertParameterFun = fun () ->
mnesia:write(rabbit_runtime_parameters, #runtime_parameters{key = Key, value = Term}, write) mnesia:write(rabbit_runtime_parameters, #runtime_parameters{key = Key, value = Term}, write)
end, end,
{atomic, ok} = mnesia:transaction(InsertParameterFun). {atomic, ok} = mnesia:transaction(InsertParameterFun).
clear_vhost_global_parameters() -> clear_vhost_global_parameters() ->
DeleteParameterFun = fun() -> DeleteParameterFun = fun () ->
ok = mnesia:delete(rabbit_runtime_parameters, mqtt_default_vhosts, write), ok = mnesia:delete(rabbit_runtime_parameters, mqtt_default_vhosts, write),
ok = mnesia:delete(rabbit_runtime_parameters, mqtt_port_to_vhost_mapping, write) ok = mnesia:delete(rabbit_runtime_parameters, mqtt_port_to_vhost_mapping, write)
end, end,
{atomic, ok} = mnesia:transaction(DeleteParameterFun). {atomic, ok} = mnesia:transaction(DeleteParameterFun).

View File

@ -34,26 +34,21 @@ init_per_suite(Config) ->
{rabbitmq_ct_tls_verify, verify_none} {rabbitmq_ct_tls_verify, verify_none}
]), ]),
MqttConfig = mqtt_config(), MqttConfig = mqtt_config(),
rabbit_ct_helpers:run_setup_steps( rabbit_ct_helpers:run_setup_steps(Config1,
Config1, [ fun(Conf) -> merge_app_env(MqttConfig, Conf) end ] ++
[fun(Conf) -> merge_app_env(MqttConfig, Conf) end] ++
rabbit_ct_broker_helpers:setup_steps() ++ rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_client_helpers:setup_steps() rabbit_ct_client_helpers:setup_steps()).
).
mqtt_config() -> mqtt_config() ->
{rabbitmq_mqtt, [ {rabbitmq_mqtt, [
{proxy_protocol, true}, {proxy_protocol, true},
{ssl_cert_login, true}, {ssl_cert_login, true},
{allow_anonymous, true} {allow_anonymous, true}]}.
]}.
end_per_suite(Config) -> end_per_suite(Config) ->
rabbit_ct_helpers:run_teardown_steps( rabbit_ct_helpers:run_teardown_steps(Config,
Config,
rabbit_ct_client_helpers:teardown_steps() ++ rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_broker_helpers:teardown_steps() rabbit_ct_broker_helpers:teardown_steps()).
).
init_per_group(_, Config) -> Config. init_per_group(_, Config) -> Config.
end_per_group(_, Config) -> Config. end_per_group(_, Config) -> Config.
@ -66,11 +61,8 @@ end_per_testcase(Testcase, Config) ->
proxy_protocol(Config) -> proxy_protocol(Config) ->
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt), Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt),
{ok, Socket} = gen_tcp:connect( {ok, Socket} = gen_tcp:connect({127,0,0,1}, Port,
{127, 0, 0, 1}, [binary, {active, false}, {packet, raw}]),
Port,
[binary, {active, false}, {packet, raw}]
),
ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"), ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
ok = inet:send(Socket, mqtt_3_1_1_connect_packet()), ok = inet:send(Socket, mqtt_3_1_1_connect_packet()),
{ok, _Packet} = gen_tcp:recv(Socket, 0, ?TIMEOUT), {ok, _Packet} = gen_tcp:recv(Socket, 0, ?TIMEOUT),
@ -83,11 +75,8 @@ proxy_protocol(Config) ->
proxy_protocol_tls(Config) -> proxy_protocol_tls(Config) ->
app_utils:start_applications([asn1, crypto, public_key, ssl]), app_utils:start_applications([asn1, crypto, public_key, ssl]),
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt_tls), Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt_tls),
{ok, Socket} = gen_tcp:connect( {ok, Socket} = gen_tcp:connect({127,0,0,1}, Port,
{127, 0, 0, 1}, [binary, {active, false}, {packet, raw}]),
Port,
[binary, {active, false}, {packet, raw}]
),
ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"), ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
{ok, SslSocket} = ssl:connect(Socket, [], ?TIMEOUT), {ok, SslSocket} = ssl:connect(Socket, [], ?TIMEOUT),
ok = ssl:send(SslSocket, mqtt_3_1_1_connect_packet()), ok = ssl:send(SslSocket, mqtt_3_1_1_connect_packet()),
@ -107,5 +96,29 @@ merge_app_env(MqttConfig, Config) ->
rabbit_ct_helpers:merge_app_env(Config, MqttConfig). rabbit_ct_helpers:merge_app_env(Config, MqttConfig).
mqtt_3_1_1_connect_packet() -> mqtt_3_1_1_connect_packet() ->
<<16, 24, 0, 4, 77, 81, 84, 84, 4, 2, 0, 60, 0, 12, 84, 101, 115, 116, 67, 111, 110, 115, 117, <<16,
109, 101, 114>>. 24,
0,
4,
77,
81,
84,
84,
4,
2,
0,
60,
0,
12,
84,
101,
115,
116,
67,
111,
110,
115,
117,
109,
101,
114>>.

View File

@ -13,16 +13,11 @@
-behaviour(rabbit_authn_backend). -behaviour(rabbit_authn_backend).
-behaviour(rabbit_authz_backend). -behaviour(rabbit_authz_backend).
-export([ -export([setup/1,
setup/1, user_login_authentication/2, user_login_authorization/2,
user_login_authentication/2, check_vhost_access/3, check_resource_access/4, check_topic_access/4,
user_login_authorization/2, state_can_expire/0,
check_vhost_access/3, get/1]).
check_resource_access/4,
check_topic_access/4,
state_can_expire/0,
get/1
]).
setup(CallerPid) -> setup(CallerPid) ->
ets:new(?MODULE, [set, public, named_table]), ets:new(?MODULE, [set, public, named_table]),
@ -31,13 +26,12 @@ setup(CallerPid) ->
stop -> ok stop -> ok
end. end.
user_login_authentication(_, AuthProps) -> user_login_authentication(_, AuthProps) ->
ets:insert(?MODULE, {authentication, AuthProps}), ets:insert(?MODULE, {authentication, AuthProps}),
{ok, #auth_user{ {ok, #auth_user{username = <<"dummy">>,
username = <<"dummy">>, tags = [],
tags = [], impl = none}}.
impl = none
}}.
user_login_authorization(_, _) -> user_login_authorization(_, _) ->
io:format("login authorization"), io:format("login authorization"),

View File

@ -5,45 +5,42 @@
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved. %% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
%% %%
-module(reader_SUITE). -module(reader_SUITE).
-compile([ -compile([export_all,
export_all, nowarn_export_all]).
nowarn_export_all
]).
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-import(rabbit_ct_broker_helpers, [rpc/4]). -import(rabbit_ct_broker_helpers, [rpc/4]).
-import(rabbit_ct_helpers, [eventually/3]). -import(rabbit_ct_helpers, [eventually/3]).
-import(util, [ -import(util, [all_connection_pids/1,
all_connection_pids/1, publish_qos1_timeout/4,
publish_qos1_timeout/4, expect_publishes/3,
expect_publishes/3, connect/2,
connect/2, connect/3,
connect/3, await_exit/1]).
await_exit/1
]).
all() -> all() ->
[ [
{group, tests} {group, tests}
]. ].
groups() -> groups() ->
[ [
{tests, [], [ {tests, [],
block_connack_timeout, [
handle_invalid_packets, block_connack_timeout,
login_timeout, handle_invalid_packets,
stats, login_timeout,
quorum_clean_session_false, stats,
quorum_clean_session_true, quorum_clean_session_false,
classic_clean_session_true, quorum_clean_session_true,
classic_clean_session_false, classic_clean_session_true,
non_clean_sess_empty_client_id, classic_clean_session_false,
event_authentication_failure, non_clean_sess_empty_client_id,
rabbit_mqtt_qos0_queue_overflow event_authentication_failure,
]} rabbit_mqtt_qos0_queue_overflow
]}
]. ].
suite() -> suite() ->
@ -54,36 +51,28 @@ suite() ->
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
merge_app_env(Config) -> merge_app_env(Config) ->
rabbit_ct_helpers:merge_app_env( rabbit_ct_helpers:merge_app_env(Config,
Config, {rabbit, [
{rabbit, [ {collect_statistics, basic},
{collect_statistics, basic}, {collect_statistics_interval, 100}
{collect_statistics_interval, 100} ]}).
]}
).
init_per_suite(Config) -> init_per_suite(Config) ->
rabbit_ct_helpers:log_environment(), rabbit_ct_helpers:log_environment(),
Config1 = rabbit_ct_helpers:set_config(Config, [ Config1 = rabbit_ct_helpers:set_config(Config, [
{rmq_nodename_suffix, ?MODULE}, {rmq_nodename_suffix, ?MODULE},
{rmq_extra_tcp_ports, [ {rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
tcp_port_mqtt_extra, tcp_port_mqtt_tls_extra]}
tcp_port_mqtt_tls_extra ]),
]} rabbit_ct_helpers:run_setup_steps(Config1,
]), [ fun merge_app_env/1 ] ++
rabbit_ct_helpers:run_setup_steps( rabbit_ct_broker_helpers:setup_steps() ++
Config1, rabbit_ct_client_helpers:setup_steps()).
[fun merge_app_env/1] ++
rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_client_helpers:setup_steps()
).
end_per_suite(Config) -> end_per_suite(Config) ->
rabbit_ct_helpers:run_teardown_steps( rabbit_ct_helpers:run_teardown_steps(Config,
Config, rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_client_helpers:teardown_steps() ++ rabbit_ct_broker_helpers:teardown_steps()).
rabbit_ct_broker_helpers:teardown_steps()
).
init_per_group(_, Config) -> init_per_group(_, Config) ->
Config. Config.
@ -97,6 +86,7 @@ init_per_testcase(Testcase, Config) ->
end_per_testcase(Testcase, Config) -> end_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:testcase_finished(Config, Testcase). rabbit_ct_helpers:testcase_finished(Config, Testcase).
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
%% Testsuite cases %% Testsuite cases
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
@ -110,13 +100,11 @@ block_connack_timeout(Config) ->
timer:sleep(100), timer:sleep(100),
%% We can still connect via TCP, but CONNECT packet will not be processed on the server. %% We can still connect via TCP, but CONNECT packet will not be processed on the server.
{ok, Client} = emqtt:start_link([ {ok, Client} = emqtt:start_link([{host, "localhost"},
{host, "localhost"}, {port, P},
{port, P}, {clientid, atom_to_binary(?FUNCTION_NAME)},
{clientid, atom_to_binary(?FUNCTION_NAME)}, {proto_ver, v4},
{proto_ver, v4}, {connect_timeout, 1}]),
{connect_timeout, 1}
]),
unlink(Client), unlink(Client),
ClientMRef = monitor(process, Client), ClientMRef = monitor(process, Client),
{error, connack_timeout} = emqtt:connect(Client), {error, connack_timeout} = emqtt:connect(Client),
@ -124,7 +112,7 @@ block_connack_timeout(Config) ->
{'DOWN', ClientMRef, process, Client, connack_timeout} -> {'DOWN', ClientMRef, process, Client, connack_timeout} ->
ok ok
after 200 -> after 200 ->
ct:fail("missing connack_timeout in client") ct:fail("missing connack_timeout in client")
end, end,
Ports = rpc(Config, erlang, ports, []), Ports = rpc(Config, erlang, ports, []),
@ -142,7 +130,7 @@ block_connack_timeout(Config) ->
%% because our client already disconnected. %% because our client already disconnected.
ok ok
after 2000 -> after 2000 ->
ct:fail("missing peername_not_known from server") ct:fail("missing peername_not_known from server")
end, end,
%% Ensure that our client is not registered. %% Ensure that our client is not registered.
?assertEqual([], all_connection_pids(Config)), ?assertEqual([], all_connection_pids(Config)),
@ -182,12 +170,8 @@ stats(Config) ->
[{Pid, Props}] = rpc(Config, ets, lookup, [connection_metrics, Pid]), [{Pid, Props}] = rpc(Config, ets, lookup, [connection_metrics, Pid]),
true = proplists:is_defined(garbage_collection, Props), true = proplists:is_defined(garbage_collection, Props),
%% If the coarse entry is present, stats were successfully emitted %% If the coarse entry is present, stats were successfully emitted
[{Pid, _, _, _, _}] = rpc( [{Pid, _, _, _, _}] = rpc(Config, ets, lookup,
Config, [connection_coarse_metrics, Pid]),
ets,
lookup,
[connection_coarse_metrics, Pid]
),
ok = emqtt:disconnect(C). ok = emqtt:disconnect(C).
get_durable_queue_type(Server, QNameBin) -> get_durable_queue_type(Server, QNameBin) ->
@ -232,41 +216,32 @@ classic_clean_session_true(Config) ->
validate_durable_queue_type(Config, <<"classicCleanSessionTrue">>, true, rabbit_classic_queue). validate_durable_queue_type(Config, <<"classicCleanSessionTrue">>, true, rabbit_classic_queue).
classic_clean_session_false(Config) -> classic_clean_session_false(Config) ->
validate_durable_queue_type( validate_durable_queue_type(Config, <<"classicCleanSessionFalse">>, false, rabbit_classic_queue).
Config, <<"classicCleanSessionFalse">>, false, rabbit_classic_queue
).
%% "If the Client supplies a zero-byte ClientId with CleanSession set to 0, %% "If the Client supplies a zero-byte ClientId with CleanSession set to 0,
%% the Server MUST respond to the CONNECT Packet with a CONNACK return code 0x02 %% the Server MUST respond to the CONNECT Packet with a CONNACK return code 0x02
%% (Identifier rejected) and then close the Network Connection" [MQTT-3.1.3-8]. %% (Identifier rejected) and then close the Network Connection" [MQTT-3.1.3-8].
non_clean_sess_empty_client_id(Config) -> non_clean_sess_empty_client_id(Config) ->
{ok, C} = emqtt:start_link( {ok, C} = emqtt:start_link(
[ [{clientid, <<>>},
{clientid, <<>>}, {clean_start, false},
{clean_start, false}, {proto_ver, v4},
{proto_ver, v4}, {host, "localhost"},
{host, "localhost"}, {port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)}
{port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)} ]),
]
),
process_flag(trap_exit, true), process_flag(trap_exit, true),
?assertMatch( ?assertMatch({error, {client_identifier_not_valid, _}},
{error, {client_identifier_not_valid, _}}, emqtt:connect(C)),
emqtt:connect(C)
),
ok = await_exit(C). ok = await_exit(C).
event_authentication_failure(Config) -> event_authentication_failure(Config) ->
{ok, C} = emqtt:start_link( {ok, C} = emqtt:start_link(
[ [{username, <<"Trudy">>},
{username, <<"Trudy">>}, {password, <<"fake-password">>},
{password, <<"fake-password">>}, {host, "localhost"},
{host, "localhost"}, {port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)},
{port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)}, {clientid, atom_to_binary(?FUNCTION_NAME)},
{clientid, atom_to_binary(?FUNCTION_NAME)}, {proto_ver, v4}]),
{proto_ver, v4}
]
),
true = unlink(C), true = unlink(C),
ok = rabbit_ct_broker_helpers:add_code_path_to_all_nodes(Config, event_recorder), ok = rabbit_ct_broker_helpers:add_code_path_to_all_nodes(Config, event_recorder),
@ -277,13 +252,9 @@ event_authentication_failure(Config) ->
[E, _ConnectionClosedEvent] = util:get_events(Server), [E, _ConnectionClosedEvent] = util:get_events(Server),
util:assert_event_type(user_authentication_failure, E), util:assert_event_type(user_authentication_failure, E),
util:assert_event_prop( util:assert_event_prop([{name, <<"Trudy">>},
[ {connection_type, network}],
{name, <<"Trudy">>}, E),
{connection_type, network}
],
E
),
ok = gen_event:delete_handler({rabbit_event, Server}, event_recorder, []). ok = gen_event:delete_handler({rabbit_event, Server}, event_recorder, []).
@ -295,12 +266,8 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
NumMsgs = 10_000, NumMsgs = 10_000,
%% Provoke TCP back-pressure from client to server by using very small buffers. %% Provoke TCP back-pressure from client to server by using very small buffers.
Opts = [ Opts = [{tcp_opts, [{recbuf, 512},
{tcp_opts, [ {buffer, 512}]}],
{recbuf, 512},
{buffer, 512}
]}
],
Sub = connect(<<"subscriber">>, Config, Opts), Sub = connect(<<"subscriber">>, Config, Opts),
{ok, _, [0]} = emqtt:subscribe(Sub, Topic, qos0), {ok, _, [0]} = emqtt:subscribe(Sub, Topic, qos0),
[ServerConnectionPid] = all_connection_pids(Config), [ServerConnectionPid] = all_connection_pids(Config),
@ -312,12 +279,9 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
%% Let's overflow the receiving server MQTT connection process %% Let's overflow the receiving server MQTT connection process
%% (i.e. the rabbit_mqtt_qos0_queue) by sending many large messages. %% (i.e. the rabbit_mqtt_qos0_queue) by sending many large messages.
Pub = connect(<<"publisher">>, Config), Pub = connect(<<"publisher">>, Config),
lists:foreach( lists:foreach(fun(_) ->
fun(_) -> ok = emqtt:publish(Pub, Topic, Msg, qos0)
ok = emqtt:publish(Pub, Topic, Msg, qos0) end, lists:seq(1, NumMsgs)),
end,
lists:seq(1, NumMsgs)
),
%% Give the server some time to process (either send or drop) the messages. %% Give the server some time to process (either send or drop) the messages.
timer:sleep(2000), timer:sleep(2000),
@ -354,11 +318,9 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
num_received(Topic, Payload, N) -> num_received(Topic, Payload, N) ->
receive receive
{publish, #{ {publish, #{topic := Topic,
topic := Topic, payload := Payload}} ->
payload := Payload
}} ->
num_received(Topic, Payload, N + 1) num_received(Topic, Payload, N + 1)
after 1000 -> after 1000 ->
N N
end. end.

View File

@ -8,31 +8,29 @@
-compile([export_all, nowarn_export_all]). -compile([export_all, nowarn_export_all]).
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-import(util, [ -import(util, [expect_publishes/3,
expect_publishes/3, connect/3]).
connect/3
]).
all() -> all() ->
[ [
{group, dets}, {group, dets},
{group, ets}, {group, ets},
{group, noop} {group, noop}
]. ].
groups() -> groups() ->
[ [
{dets, [], tests()}, {dets, [], tests()},
{ets, [], tests()}, {ets, [], tests()},
{noop, [], [does_not_retain]} {noop, [], [does_not_retain]}
]. ].
tests() -> tests() ->
[ [
coerce_configuration_data, coerce_configuration_data,
should_translate_amqp2mqtt_on_publish, should_translate_amqp2mqtt_on_publish,
should_translate_amqp2mqtt_on_retention, should_translate_amqp2mqtt_on_retention,
should_translate_amqp2mqtt_on_retention_search should_translate_amqp2mqtt_on_retention_search
]. ].
suite() -> suite() ->
@ -51,38 +49,31 @@ end_per_suite(Config) ->
init_per_group(Group, Config0) -> init_per_group(Group, Config0) ->
Config = rabbit_ct_helpers:set_config( Config = rabbit_ct_helpers:set_config(
Config0, Config0,
[ [
{rmq_nodename_suffix, Group}, {rmq_nodename_suffix, Group},
{rmq_extra_tcp_ports, [ {rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
tcp_port_mqtt_extra, tcp_port_mqtt_tls_extra]}
tcp_port_mqtt_tls_extra ]),
]}
]
),
Mod = list_to_atom("rabbit_mqtt_retained_msg_store_" ++ atom_to_list(Group)), Mod = list_to_atom("rabbit_mqtt_retained_msg_store_" ++ atom_to_list(Group)),
Env = [ Env = [{rabbitmq_mqtt, [{retained_message_store, Mod}]},
{rabbitmq_mqtt, [{retained_message_store, Mod}]}, {rabbit, [
{rabbit, [ {default_user, "guest"},
{default_user, "guest"}, {default_pass, "guest"},
{default_pass, "guest"}, {default_vhost, "/"},
{default_vhost, "/"}, {default_permissions, [".*", ".*", ".*"]}
{default_permissions, [".*", ".*", ".*"]} ]}],
]}
],
rabbit_ct_helpers:run_setup_steps( rabbit_ct_helpers:run_setup_steps(
Config, Config,
[fun(Conf) -> rabbit_ct_helpers:merge_app_env(Conf, Env) end] ++ [fun(Conf) -> rabbit_ct_helpers:merge_app_env(Conf, Env) end] ++
rabbit_ct_broker_helpers:setup_steps() ++ rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_client_helpers:setup_steps() rabbit_ct_client_helpers:setup_steps()).
).
end_per_group(_, Config) -> end_per_group(_, Config) ->
rabbit_ct_helpers:run_teardown_steps( rabbit_ct_helpers:run_teardown_steps(
Config, Config,
rabbit_ct_client_helpers:teardown_steps() ++ rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_broker_helpers:teardown_steps() rabbit_ct_broker_helpers:teardown_steps()).
).
init_per_testcase(Testcase, Config) -> init_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:testcase_started(Config, Testcase). rabbit_ct_helpers:testcase_started(Config, Testcase).
@ -90,6 +81,7 @@ init_per_testcase(Testcase, Config) ->
end_per_testcase(Testcase, Config) -> end_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:testcase_finished(Config, Testcase). rabbit_ct_helpers:testcase_finished(Config, Testcase).
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
%% Testsuite cases %% Testsuite cases
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
@ -112,7 +104,7 @@ should_translate_amqp2mqtt_on_publish(Config) ->
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]), C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
%% there's an active consumer %% there's an active consumer
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1), {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]), ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]), ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
ok = emqtt:disconnect(C). ok = emqtt:disconnect(C).
@ -124,7 +116,7 @@ should_translate_amqp2mqtt_on_publish(Config) ->
should_translate_amqp2mqtt_on_retention(Config) -> should_translate_amqp2mqtt_on_retention(Config) ->
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]), C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
%% publish with retain = true before a consumer comes around %% publish with retain = true before a consumer comes around
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]), ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1), {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]), ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
ok = emqtt:disconnect(C). ok = emqtt:disconnect(C).
@ -136,19 +128,19 @@ should_translate_amqp2mqtt_on_retention(Config) ->
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
should_translate_amqp2mqtt_on_retention_search(Config) -> should_translate_amqp2mqtt_on_retention_search(Config) ->
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]), C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]), ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device/Field">>, qos1), {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device/Field">>, qos1),
ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]), ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
ok = emqtt:disconnect(C). ok = emqtt:disconnect(C).
does_not_retain(Config) -> does_not_retain(Config) ->
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]), C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]), ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1), {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
receive receive
Unexpected -> Unexpected ->
ct:fail("Unexpected message: ~p", [Unexpected]) ct:fail("Unexpected message: ~p", [Unexpected])
after 1000 -> after 1000 ->
ok ok
end, end,
ok = emqtt:disconnect(C). ok = emqtt:disconnect(C).

File diff suppressed because it is too large Load Diff

View File

@ -4,58 +4,46 @@
-include_lib("rabbit_common/include/rabbit.hrl"). -include_lib("rabbit_common/include/rabbit.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-export([ -export([all_connection_pids/1,
all_connection_pids/1, publish_qos1_timeout/4,
publish_qos1_timeout/4, sync_publish_result/3,
sync_publish_result/3, get_global_counters/2,
get_global_counters/2, get_global_counters/3,
get_global_counters/3, get_global_counters/4,
get_global_counters/4, expect_publishes/3,
expect_publishes/3, connect/2,
connect/2, connect/3,
connect/3, connect/4,
connect/4, get_events/1,
get_events/1, assert_event_type/2,
assert_event_type/2, assert_event_prop/2,
assert_event_prop/2, await_exit/1,
await_exit/1, await_exit/2
await_exit/2 ]).
]).
all_connection_pids(Config) -> all_connection_pids(Config) ->
Nodes = rabbit_ct_broker_helpers:get_node_configs(Config, nodename), Nodes = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
Result = erpc:multicall(Nodes, rabbit_mqtt, local_connection_pids, [], 5000), Result = erpc:multicall(Nodes, rabbit_mqtt, local_connection_pids, [], 5000),
lists:foldl( lists:foldl(fun({ok, Pids}, Acc) ->
fun Pids ++ Acc;
({ok, Pids}, Acc) -> (_, Acc) ->
Pids ++ Acc; Acc
(_, Acc) -> end, [], Result).
Acc
end,
[],
Result
).
publish_qos1_timeout(Client, Topic, Payload, Timeout) -> publish_qos1_timeout(Client, Topic, Payload, Timeout) ->
Mref = erlang:monitor(process, Client), Mref = erlang:monitor(process, Client),
ok = emqtt:publish_async( ok = emqtt:publish_async(Client, Topic, #{}, Payload, [{qos, 1}], infinity,
Client, {fun ?MODULE:sync_publish_result/3, [self(), Mref]}),
Topic,
#{},
Payload,
[{qos, 1}],
infinity,
{fun ?MODULE:sync_publish_result/3, [self(), Mref]}
),
receive receive
{Mref, Reply} -> {Mref, Reply} ->
erlang:demonitor(Mref, [flush]), erlang:demonitor(Mref, [flush]),
Reply; Reply;
{'DOWN', Mref, process, Client, Reason} -> {'DOWN', Mref, process, Client, Reason} ->
ct:fail("client is down: ~tp", [Reason]) ct:fail("client is down: ~tp", [Reason])
after Timeout -> after
erlang:demonitor(Mref, [flush]), Timeout ->
puback_timeout erlang:demonitor(Mref, [flush]),
puback_timeout
end. end.
sync_publish_result(Caller, Mref, Result) -> sync_publish_result(Caller, Mref, Result) ->
@ -63,27 +51,20 @@ sync_publish_result(Caller, Mref, Result) ->
expect_publishes(_, _, []) -> expect_publishes(_, _, []) ->
ok; ok;
expect_publishes(Client, Topic, [Payload | Rest]) when expect_publishes(Client, Topic, [Payload|Rest])
is_pid(Client) when is_pid(Client) ->
->
receive receive
{publish, #{ {publish, #{client_pid := Client,
client_pid := Client, topic := Topic,
topic := Topic, payload := Payload}} ->
payload := Payload
}} ->
expect_publishes(Client, Topic, Rest); expect_publishes(Client, Topic, Rest);
{publish, #{ {publish, #{client_pid := Client,
client_pid := Client, topic := Topic,
topic := Topic, payload := Other}} ->
payload := Other ct:fail("Received unexpected PUBLISH payload. Expected: ~p Got: ~p",
}} -> [Payload, Other])
ct:fail(
"Received unexpected PUBLISH payload. Expected: ~p Got: ~p",
[Payload, Other]
)
after 3000 -> after 3000 ->
{publish_not_received, Payload} {publish_not_received, Payload}
end. end.
get_global_counters(Config, ProtoVer) -> get_global_counters(Config, ProtoVer) ->
@ -97,14 +78,11 @@ get_global_counters(Config, v3, Node, QType) ->
get_global_counters(Config, v4, Node, QType) -> get_global_counters(Config, v4, Node, QType) ->
get_global_counters(Config, ?MQTT_PROTO_V4, Node, QType); get_global_counters(Config, ?MQTT_PROTO_V4, Node, QType);
get_global_counters(Config, Proto, Node, QType) -> get_global_counters(Config, Proto, Node, QType) ->
maps:get( maps:get([{protocol, Proto}] ++ QType,
[{protocol, Proto}] ++ QType, rabbit_ct_broker_helpers:rpc(Config, Node, rabbit_global_counters, overview, [])).
rabbit_ct_broker_helpers:rpc(Config, Node, rabbit_global_counters, overview, [])
).
get_events(Node) -> get_events(Node) ->
%% events are sent and processed asynchronously timer:sleep(300), %% events are sent and processed asynchronously
timer:sleep(300),
Result = gen_event:call({rabbit_event, Node}, event_recorder, take_state), Result = gen_event:call({rabbit_event, Node}, event_recorder, take_state),
?assert(is_list(Result)), ?assert(is_list(Result)),
Result. Result.
@ -114,26 +92,24 @@ assert_event_type(ExpectedType, #event{type = ActualType}) ->
assert_event_prop(ExpectedProp = {Key, _Value}, #event{props = Props}) -> assert_event_prop(ExpectedProp = {Key, _Value}, #event{props = Props}) ->
?assertEqual(ExpectedProp, lists:keyfind(Key, 1, Props)); ?assertEqual(ExpectedProp, lists:keyfind(Key, 1, Props));
assert_event_prop(ExpectedProps, Event) when assert_event_prop(ExpectedProps, Event)
is_list(ExpectedProps) when is_list(ExpectedProps) ->
-> lists:foreach(fun(P) ->
lists:foreach( assert_event_prop(P, Event)
fun(P) -> end, ExpectedProps).
assert_event_prop(P, Event)
end,
ExpectedProps
).
await_exit(Pid) -> await_exit(Pid) ->
receive receive
{'EXIT', Pid, _} -> ok {'EXIT', Pid, _} -> ok
after 20_000 -> ct:fail({missing_exit, Pid}) after
20_000 -> ct:fail({missing_exit, Pid})
end. end.
await_exit(Pid, Reason) -> await_exit(Pid, Reason) ->
receive receive
{'EXIT', Pid, Reason} -> ok {'EXIT', Pid, Reason} -> ok
after 20_000 -> ct:fail({missing_exit, Pid}) after
20_000 -> ct:fail({missing_exit, Pid})
end. end.
connect(ClientId, Config) -> connect(ClientId, Config) ->
@ -144,27 +120,21 @@ connect(ClientId, Config, AdditionalOpts) ->
connect(ClientId, Config, Node, AdditionalOpts) -> connect(ClientId, Config, Node, AdditionalOpts) ->
{Port, WsOpts, Connect} = {Port, WsOpts, Connect} =
case rabbit_ct_helpers:get_config(Config, websocket, false) of case rabbit_ct_helpers:get_config(Config, websocket, false) of
false -> false ->
{ {rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_mqtt),
rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_mqtt), [],
[], fun emqtt:connect/1};
fun emqtt:connect/1 true ->
}; {rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_web_mqtt),
true -> [{ws_path, "/ws"}],
{ fun emqtt:ws_connect/1}
rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_web_mqtt), end,
[{ws_path, "/ws"}], Options = [{host, "localhost"},
fun emqtt:ws_connect/1 {port, Port},
} {proto_ver, v4},
end, {clientid, rabbit_data_coercion:to_binary(ClientId)}
Options = ] ++ WsOpts ++ AdditionalOpts,
[
{host, "localhost"},
{port, Port},
{proto_ver, v4},
{clientid, rabbit_data_coercion:to_binary(ClientId)}
] ++ WsOpts ++ AdditionalOpts,
{ok, C} = emqtt:start_link(Options), {ok, C} = emqtt:start_link(Options),
{ok, _Properties} = Connect(C), {ok, _Properties} = Connect(C),
C. C.

View File

@ -12,18 +12,19 @@
all() -> all() ->
[ [
{group, tests} {group, tests}
]. ].
groups() -> groups() ->
[ [
{tests, [parallel], [ {tests, [parallel], [
coerce_exchange, coerce_exchange,
coerce_vhost, coerce_vhost,
coerce_default_user, coerce_default_user,
coerce_default_pass, coerce_default_pass,
mqtt_amqp_topic_translation mqtt_amqp_topic_translation
]} ]
}
]. ].
suite() -> suite() ->

View File

@ -49,7 +49,7 @@ init([]) -> {ok, {{one_for_one, 1, 5}, []}}.
-spec list_connections() -> [pid()]. -spec list_connections() -> [pid()].
list_connections() -> list_connections() ->
PlainPids = connection_pids_of_protocol(?TCP_PROTOCOL), PlainPids = connection_pids_of_protocol(?TCP_PROTOCOL),
TLSPids = connection_pids_of_protocol(?TLS_PROTOCOL), TLSPids = connection_pids_of_protocol(?TLS_PROTOCOL),
PlainPids ++ TLSPids. PlainPids ++ TLSPids.
%% %%
@ -58,8 +58,7 @@ list_connections() ->
connection_pids_of_protocol(Protocol) -> connection_pids_of_protocol(Protocol) ->
case rabbit_networking:ranch_ref_of_protocol(Protocol) of case rabbit_networking:ranch_ref_of_protocol(Protocol) of
undefined -> undefined -> [];
[];
AcceptorRef -> AcceptorRef ->
lists:map(fun cowboy_ws_connection_pid/1, ranch:procs(AcceptorRef, connections)) lists:map(fun cowboy_ws_connection_pid/1, ranch:procs(AcceptorRef, connections))
end. end.
@ -71,38 +70,36 @@ cowboy_ws_connection_pid(RanchConnPid) ->
Pid. Pid.
mqtt_init() -> mqtt_init() ->
CowboyOpts0 = maps:from_list(get_env(cowboy_opts, [])), CowboyOpts0 = maps:from_list(get_env(cowboy_opts, [])),
CowboyWsOpts = maps:from_list(get_env(cowboy_ws_opts, [])), CowboyWsOpts = maps:from_list(get_env(cowboy_ws_opts, [])),
Routes = cowboy_router:compile([ Routes = cowboy_router:compile([{'_', [
{'_', [ {get_env(ws_path, "/ws"), rabbit_web_mqtt_handler, [{ws_opts, CowboyWsOpts}]}
{get_env(ws_path, "/ws"), rabbit_web_mqtt_handler, [{ws_opts, CowboyWsOpts}]} ]}]),
]} CowboyOpts = CowboyOpts0#{
]), env => #{dispatch => Routes},
CowboyOpts = CowboyOpts0#{ proxy_header => get_env(proxy_protocol, false),
env => #{dispatch => Routes}, stream_handlers => [rabbit_web_mqtt_stream_handler, cowboy_stream_h]
proxy_header => get_env(proxy_protocol, false), },
stream_handlers => [rabbit_web_mqtt_stream_handler, cowboy_stream_h] case get_env(tcp_config, []) of
}, [] -> ok;
case get_env(tcp_config, []) of TCPConf0 -> start_tcp_listener(TCPConf0, CowboyOpts)
[] -> ok; end,
TCPConf0 -> start_tcp_listener(TCPConf0, CowboyOpts) case get_env(ssl_config, []) of
end, [] -> ok;
case get_env(ssl_config, []) of TLSConf0 -> start_tls_listener(TLSConf0, CowboyOpts)
[] -> ok; end,
TLSConf0 -> start_tls_listener(TLSConf0, CowboyOpts) ok.
end,
ok.
start_tcp_listener(TCPConf0, CowboyOpts) -> start_tcp_listener(TCPConf0, CowboyOpts) ->
{TCPConf, IpStr, Port} = get_tcp_conf(TCPConf0), {TCPConf, IpStr, Port} = get_tcp_conf(TCPConf0),
RanchRef = rabbit_networking:ranch_ref(TCPConf), RanchRef = rabbit_networking:ranch_ref(TCPConf),
RanchTransportOpts = RanchTransportOpts =
#{ #{
socket_opts => TCPConf, socket_opts => TCPConf,
max_connections => get_max_connections(), max_connections => get_max_connections(),
num_acceptors => get_env(num_tcp_acceptors, 10), num_acceptors => get_env(num_tcp_acceptors, 10),
num_conns_sups => get_env(num_conns_sup, 1) num_conns_sups => get_env(num_conns_sup, 1)
}, },
case cowboy:start_clear(RanchRef, RanchTransportOpts, CowboyOpts) of case cowboy:start_clear(RanchRef, RanchTransportOpts, CowboyOpts) of
{ok, _} -> {ok, _} ->
ok; ok;
@ -110,28 +107,25 @@ start_tcp_listener(TCPConf0, CowboyOpts) ->
ok; ok;
{error, ErrTCP} -> {error, ErrTCP} ->
rabbit_log:error( rabbit_log:error(
"Failed to start a WebSocket (HTTP) listener. Error: ~p, listener settings: ~p", "Failed to start a WebSocket (HTTP) listener. Error: ~p, listener settings: ~p",
[ErrTCP, TCPConf] [ErrTCP, TCPConf]),
),
throw(ErrTCP) throw(ErrTCP)
end, end,
listener_started(?TCP_PROTOCOL, TCPConf), listener_started(?TCP_PROTOCOL, TCPConf),
rabbit_log:info( rabbit_log:info("rabbit_web_mqtt: listening for HTTP connections on ~s:~w",
"rabbit_web_mqtt: listening for HTTP connections on ~s:~w", [IpStr, Port]).
[IpStr, Port]
).
start_tls_listener(TLSConf0, CowboyOpts) -> start_tls_listener(TLSConf0, CowboyOpts) ->
_ = rabbit_networking:ensure_ssl(), _ = rabbit_networking:ensure_ssl(),
{TLSConf, TLSIpStr, TLSPort} = get_tls_conf(TLSConf0), {TLSConf, TLSIpStr, TLSPort} = get_tls_conf(TLSConf0),
RanchRef = rabbit_networking:ranch_ref(TLSConf), RanchRef = rabbit_networking:ranch_ref(TLSConf),
RanchTransportOpts = RanchTransportOpts =
#{ #{
socket_opts => TLSConf, socket_opts => TLSConf,
max_connections => get_max_connections(), max_connections => get_max_connections(),
num_acceptors => get_env(num_ssl_acceptors, 10), num_acceptors => get_env(num_ssl_acceptors, 10),
num_conns_sups => get_env(num_conns_sup, 1) num_conns_sups => get_env(num_conns_sup, 1)
}, },
case cowboy:start_tls(RanchRef, RanchTransportOpts, CowboyOpts) of case cowboy:start_tls(RanchRef, RanchTransportOpts, CowboyOpts) of
{ok, _} -> {ok, _} ->
ok; ok;
@ -139,45 +133,34 @@ start_tls_listener(TLSConf0, CowboyOpts) ->
ok; ok;
{error, ErrTLS} -> {error, ErrTLS} ->
rabbit_log:error( rabbit_log:error(
"Failed to start a TLS WebSocket (HTTPS) listener. Error: ~p, listener settings: ~p", "Failed to start a TLS WebSocket (HTTPS) listener. Error: ~p, listener settings: ~p",
[ErrTLS, TLSConf] [ErrTLS, TLSConf]),
),
throw(ErrTLS) throw(ErrTLS)
end, end,
listener_started(?TLS_PROTOCOL, TLSConf), listener_started(?TLS_PROTOCOL, TLSConf),
rabbit_log:info( rabbit_log:info("rabbit_web_mqtt: listening for HTTPS connections on ~s:~w",
"rabbit_web_mqtt: listening for HTTPS connections on ~s:~w", [TLSIpStr, TLSPort]).
[TLSIpStr, TLSPort]
).
listener_started(Protocol, Listener) -> listener_started(Protocol, Listener) ->
Port = rabbit_misc:pget(port, Listener), Port = rabbit_misc:pget(port, Listener),
[ [rabbit_networking:tcp_listener_started(Protocol, Listener,
rabbit_networking:tcp_listener_started( IPAddress, Port)
Protocol, || {IPAddress, _Port, _Family}
Listener, <- rabbit_networking:tcp_listener_addresses(Port)],
IPAddress,
Port
)
|| {IPAddress, _Port, _Family} <-
rabbit_networking:tcp_listener_addresses(Port)
],
ok. ok.
get_tcp_conf(TCPConf0) -> get_tcp_conf(TCPConf0) ->
TCPConf1 = TCPConf1 = case proplists:get_value(port, TCPConf0) of
case proplists:get_value(port, TCPConf0) of undefined -> [{port, 15675}|TCPConf0];
undefined -> [{port, 15675} | TCPConf0]; _ -> TCPConf0
_ -> TCPConf0 end,
end,
get_ip_port(TCPConf1). get_ip_port(TCPConf1).
get_tls_conf(TLSConf0) -> get_tls_conf(TLSConf0) ->
TLSConf1 = TLSConf1 = case proplists:get_value(port, TLSConf0) of
case proplists:get_value(port, TLSConf0) of undefined -> [{port, 15675}|proplists:delete(port, TLSConf0)];
undefined -> [{port, 15675} | proplists:delete(port, TLSConf0)]; _ -> TLSConf0
_ -> TLSConf0 end,
end,
get_ip_port(TLSConf1). get_ip_port(TLSConf1).
get_ip_port(Conf0) -> get_ip_port(Conf0) ->
@ -194,7 +177,7 @@ normalize_ip(Ip) ->
Ip. Ip.
get_max_connections() -> get_max_connections() ->
get_env(max_connections, infinity). get_env(max_connections, infinity).
get_env(Key, Default) -> get_env(Key, Default) ->
rabbit_misc:get_env(rabbitmq_web_mqtt, Key, Default). rabbit_misc:get_env(rabbitmq_web_mqtt, Key, Default).

View File

@ -24,25 +24,23 @@
-export([conserve_resources/3]). -export([conserve_resources/3]).
%% cowboy_sub_protocol %% cowboy_sub_protocol
-export([ -export([upgrade/4,
upgrade/4, upgrade/5,
upgrade/5, takeover/7]).
takeover/7
]).
-type option(T) :: undefined | T. -type option(T) :: undefined | T.
-record(state, { -record(state, {
socket :: {rabbit_proxy_socket, any(), any()} | rabbit_net:socket(), socket :: {rabbit_proxy_socket, any(), any()} | rabbit_net:socket(),
parse_state = rabbit_mqtt_packet:initial_state() :: rabbit_mqtt_packet:state(), parse_state = rabbit_mqtt_packet:initial_state() :: rabbit_mqtt_packet:state(),
proc_state :: undefined | rabbit_mqtt_processor:state(), proc_state :: undefined | rabbit_mqtt_processor:state(),
connection_state = running :: running | blocked, connection_state = running :: running | blocked,
conserve = false :: boolean(), conserve = false :: boolean(),
stats_timer :: option(rabbit_event:state()), stats_timer :: option(rabbit_event:state()),
keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(), keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
conn_name :: option(binary()), conn_name :: option(binary()),
received_connect_packet = false :: boolean() received_connect_packet = false :: boolean()
}). }).
-type state() :: #state{}. -type state() :: #state{}.
@ -60,22 +58,14 @@ upgrade(Req, Env, Handler, HandlerState, Opts) ->
cowboy_websocket:upgrade(Req, Env, Handler, HandlerState, Opts). cowboy_websocket:upgrade(Req, Env, Handler, HandlerState, Opts).
takeover(Parent, Ref, Socket, Transport, Opts, Buffer, {Handler, {HandlerState, PeerAddr}}) -> takeover(Parent, Ref, Socket, Transport, Opts, Buffer, {Handler, {HandlerState, PeerAddr}}) ->
Sock = Sock = case HandlerState#state.socket of
case HandlerState#state.socket of undefined ->
undefined -> Socket;
Socket; ProxyInfo ->
ProxyInfo -> {rabbit_proxy_socket, Socket, ProxyInfo}
{rabbit_proxy_socket, Socket, ProxyInfo} end,
end, cowboy_websocket:takeover(Parent, Ref, Socket, Transport, Opts, Buffer,
cowboy_websocket:takeover( {Handler, {HandlerState#state{socket = Sock}, PeerAddr}}).
Parent,
Ref,
Socket,
Transport,
Opts,
Buffer,
{Handler, {HandlerState#state{socket = Sock}, PeerAddr}}
).
%% cowboy_websocket %% cowboy_websocket
init(Req, Opts) -> init(Req, Opts) ->
@ -85,20 +75,22 @@ init(Req, Opts) ->
Protocol -> Protocol ->
{PeerAddr, _PeerPort} = maps:get(peer, Req), {PeerAddr, _PeerPort} = maps:get(peer, Req),
WsOpts0 = proplists:get_value(ws_opts, Opts, #{}), WsOpts0 = proplists:get_value(ws_opts, Opts, #{}),
WsOpts = maps:merge(#{compress => true}, WsOpts0), WsOpts = maps:merge(#{compress => true}, WsOpts0),
case lists:member(<<"mqtt">>, Protocol) of case lists:member(<<"mqtt">>, Protocol) of
false -> false ->
no_supported_sub_protocol(Protocol, Req); no_supported_sub_protocol(Protocol, Req);
true -> true ->
{?MODULE, {?MODULE,
cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, <<"mqtt">>, Req), cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, <<"mqtt">>, Req),
{#state{socket = maps:get(proxy_header, Req, undefined)}, PeerAddr}, WsOpts} {#state{socket = maps:get(proxy_header, Req, undefined)},
PeerAddr},
WsOpts}
end end
end. end.
-spec websocket_init({state(), PeerAddr :: binary()}) -> -spec websocket_init({state(), PeerAddr :: binary()}) ->
{cowboy_websocket:commands(), state()} {cowboy_websocket:commands(), state()} |
| {cowboy_websocket:commands(), state(), hibernate}. {cowboy_websocket:commands(), state(), hibernate}.
websocket_init({State0 = #state{socket = Sock}, PeerAddr}) -> websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [web_mqtt]}), logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [web_mqtt]}),
ok = file_handle_cache:obtain(), ok = file_handle_cache:obtain(),
@ -108,15 +100,12 @@ websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
?LOG_INFO("Accepting Web MQTT connection ~s", [ConnName]), ?LOG_INFO("Accepting Web MQTT connection ~s", [ConnName]),
_ = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}), _ = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}),
PState = rabbit_mqtt_processor:initial_state( PState = rabbit_mqtt_processor:initial_state(
rabbit_net:unwrap_socket(Sock), rabbit_net:unwrap_socket(Sock),
ConnName, ConnName,
fun send_reply/2, fun send_reply/2,
PeerAddr PeerAddr),
), State1 = State0#state{conn_name = ConnName,
State1 = State0#state{ proc_state = PState},
conn_name = ConnName,
proc_state = PState
},
State = rabbit_event:init_stats_timer(State1, #state.stats_timer), State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
process_flag(trap_exit, true), process_flag(trap_exit, true),
{[], State, hibernate}; {[], State, hibernate};
@ -124,28 +113,24 @@ websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
{[{shutdown_reason, Reason}], State0} {[{shutdown_reason, Reason}], State0}
end. end.
-spec conserve_resources( -spec conserve_resources(pid(),
pid(), rabbit_alarm:resource_alarm_source(),
rabbit_alarm:resource_alarm_source(), rabbit_alarm:resource_alert()) -> ok.
rabbit_alarm:resource_alert()
) -> ok.
conserve_resources(Pid, _, {_, Conserve, _}) -> conserve_resources(Pid, _, {_, Conserve, _}) ->
Pid ! {conserve_resources, Conserve}, Pid ! {conserve_resources, Conserve},
ok. ok.
-spec websocket_handle(ping | pong | {text | binary | ping | pong, binary()}, State) -> -spec websocket_handle(ping | pong | {text | binary | ping | pong, binary()}, State) ->
{cowboy_websocket:commands(), State} {cowboy_websocket:commands(), State} |
| {cowboy_websocket:commands(), State, hibernate}. {cowboy_websocket:commands(), State, hibernate}.
websocket_handle({binary, Data}, State) -> websocket_handle({binary, Data}, State) ->
handle_data(Data, State); handle_data(Data, State);
%% Silently ignore ping and pong frames as Cowboy will automatically reply to ping frames. %% Silently ignore ping and pong frames as Cowboy will automatically reply to ping frames.
websocket_handle({Ping, _}, State) when websocket_handle({Ping, _}, State)
Ping =:= ping orelse Ping =:= pong when Ping =:= ping orelse Ping =:= pong ->
->
{[], State, hibernate}; {[], State, hibernate};
websocket_handle(Ping, State) when websocket_handle(Ping, State)
Ping =:= ping orelse Ping =:= pong when Ping =:= ping orelse Ping =:= pong ->
->
{[], State, hibernate}; {[], State, hibernate};
%% Log and close connection when receiving any other unexpected frames. %% Log and close connection when receiving any other unexpected frames.
websocket_handle(Frame, State) -> websocket_handle(Frame, State) ->
@ -153,8 +138,8 @@ websocket_handle(Frame, State) ->
stop(State, ?CLOSE_UNACCEPTABLE_DATA_TYPE, <<"unexpected WebSocket frame">>). stop(State, ?CLOSE_UNACCEPTABLE_DATA_TYPE, <<"unexpected WebSocket frame">>).
-spec websocket_info(any(), State) -> -spec websocket_info(any(), State) ->
{cowboy_websocket:commands(), State} {cowboy_websocket:commands(), State} |
| {cowboy_websocket:commands(), State, hibernate}. {cowboy_websocket:commands(), State, hibernate}.
websocket_info({conserve_resources, Conserve}, State) -> websocket_info({conserve_resources, Conserve}, State) ->
handle_credits(State#state{conserve = Conserve}); handle_credits(State#state{conserve = Conserve});
websocket_info({bump_credit, Msg}, State) -> websocket_info({bump_credit, Msg}, State) ->
@ -164,66 +149,39 @@ websocket_info({reply, Data}, State) ->
{[{binary, Data}], State, hibernate}; {[{binary, Data}], State, hibernate};
websocket_info({'EXIT', _, _}, State) -> websocket_info({'EXIT', _, _}, State) ->
stop(State); stop(State);
websocket_info( websocket_info({'$gen_cast', QueueEvent = {queue_event, _, _}},
{'$gen_cast', QueueEvent = {queue_event, _, _}}, State = #state{proc_state = PState0}) ->
State = #state{proc_state = PState0}
) ->
case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
{ok, PState} -> {ok, PState} ->
handle_credits(State#state{proc_state = PState}); handle_credits(State#state{proc_state = PState});
{error, Reason, PState} -> {error, Reason, PState} ->
?LOG_ERROR( ?LOG_ERROR("Web MQTT connection ~p failed to handle queue event: ~p",
"Web MQTT connection ~p failed to handle queue event: ~p", [State#state.conn_name, Reason]),
[State#state.conn_name, Reason]
),
stop(State#state{proc_state = PState}) stop(State#state{proc_state = PState})
end; end;
websocket_info( websocket_info({'$gen_cast', duplicate_id}, State = #state{ proc_state = ProcState,
{'$gen_cast', duplicate_id}, conn_name = ConnName }) ->
State = #state{ ?LOG_WARNING("Web MQTT disconnecting a client with duplicate ID '~s' (~p)",
proc_state = ProcState, [rabbit_mqtt_processor:info(client_id, ProcState), ConnName]),
conn_name = ConnName
}
) ->
?LOG_WARNING(
"Web MQTT disconnecting a client with duplicate ID '~s' (~p)",
[rabbit_mqtt_processor:info(client_id, ProcState), ConnName]
),
stop(State); stop(State);
websocket_info( websocket_info({'$gen_cast', {close_connection, Reason}}, State = #state{ proc_state = ProcState,
{'$gen_cast', {close_connection, Reason}}, conn_name = ConnName }) ->
State = #state{ ?LOG_WARNING("Web MQTT disconnecting client with ID '~s' (~p), reason: ~s",
proc_state = ProcState, [rabbit_mqtt_processor:info(client_id, ProcState), ConnName, Reason]),
conn_name = ConnName
}
) ->
?LOG_WARNING(
"Web MQTT disconnecting client with ID '~s' (~p), reason: ~s",
[rabbit_mqtt_processor:info(client_id, ProcState), ConnName, Reason]
),
stop(State); stop(State);
websocket_info({'$gen_cast', {force_event_refresh, Ref}}, State0) -> websocket_info({'$gen_cast', {force_event_refresh, Ref}}, State0) ->
Infos = infos(?CREATION_EVENT_KEYS, State0), Infos = infos(?CREATION_EVENT_KEYS, State0),
rabbit_event:notify(connection_created, Infos, Ref), rabbit_event:notify(connection_created, Infos, Ref),
State = rabbit_event:init_stats_timer(State0, #state.stats_timer), State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
{[], State, hibernate}; {[], State, hibernate};
websocket_info( websocket_info({'$gen_cast', refresh_config},
{'$gen_cast', refresh_config}, State0 = #state{proc_state = PState0,
State0 = #state{ conn_name = ConnName}) ->
proc_state = PState0,
conn_name = ConnName
}
) ->
PState = rabbit_mqtt_processor:update_trace(ConnName, PState0), PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
State = State0#state{proc_state = PState}, State = State0#state{proc_state = PState},
{[], State, hibernate}; {[], State, hibernate};
websocket_info( websocket_info({keepalive, Req}, State = #state{keepalive = KState0,
{keepalive, Req}, conn_name = ConnName}) ->
State = #state{
keepalive = KState0,
conn_name = ConnName
}
) ->
case rabbit_mqtt_keepalive:handle(Req, KState0) of case rabbit_mqtt_keepalive:handle(Req, KState0) of
{ok, KState} -> {ok, KState} ->
{[], State#state{keepalive = KState}, hibernate}; {[], State#state{keepalive = KState}, hibernate};
@ -231,24 +189,18 @@ websocket_info(
?LOG_ERROR("keepalive timeout in Web MQTT connection ~p", [ConnName]), ?LOG_ERROR("keepalive timeout in Web MQTT connection ~p", [ConnName]),
stop(State, ?CLOSE_NORMAL, <<"MQTT keepalive timeout">>); stop(State, ?CLOSE_NORMAL, <<"MQTT keepalive timeout">>);
{error, Reason} -> {error, Reason} ->
?LOG_ERROR( ?LOG_ERROR("keepalive error in Web MQTT connection ~p: ~p",
"keepalive error in Web MQTT connection ~p: ~p", [ConnName, Reason]),
[ConnName, Reason]
),
stop(State) stop(State)
end; end;
websocket_info(emit_stats, State) -> websocket_info(emit_stats, State) ->
{[], emit_stats(State), hibernate}; {[], emit_stats(State), hibernate};
websocket_info( websocket_info({ra_event, _From, Evt},
{ra_event, _From, Evt}, #state{proc_state = PState0} = State) ->
#state{proc_state = PState0} = State
) ->
PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0), PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
{[], State#state{proc_state = PState}, hibernate}; {[], State#state{proc_state = PState}, hibernate};
websocket_info( websocket_info({{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
{{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt, State = #state{proc_state = PState0}) ->
State = #state{proc_state = PState0}
) ->
case rabbit_mqtt_processor:handle_down(Evt, PState0) of case rabbit_mqtt_processor:handle_down(Evt, PState0) of
{ok, PState} -> {ok, PState} ->
handle_credits(State#state{proc_state = PState}); handle_credits(State#state{proc_state = PState});
@ -275,16 +227,10 @@ terminate(_Reason, _Req, #state{proc_state = undefined}) ->
ok; ok;
terminate(Reason, Request, #state{} = State) -> terminate(Reason, Request, #state{} = State) ->
terminate(Reason, Request, {true, State}); terminate(Reason, Request, {true, State});
terminate( terminate(_Reason, _Request,
_Reason, {SendWill, #state{conn_name = ConnName,
_Request, proc_state = PState,
{SendWill, keepalive = KState} = State}) ->
#state{
conn_name = ConnName,
proc_state = PState,
keepalive = KState
} = State}
) ->
?LOG_INFO("Web MQTT closing connection ~ts", [ConnName]), ?LOG_INFO("Web MQTT closing connection ~ts", [ConnName]),
maybe_emit_stats(State), maybe_emit_stats(State),
_ = rabbit_mqtt_keepalive:cancel_timer(KState), _ = rabbit_mqtt_keepalive:cancel_timer(KState),
@ -306,49 +252,29 @@ handle_data(Data, State0 = #state{}) ->
Other Other
end. end.
handle_data1( handle_data1(<<>>, State0 = #state{received_connect_packet = false,
<<>>, proc_state = PState,
State0 = #state{ conn_name = ConnName}) ->
received_connect_packet = false, ?LOG_INFO("Accepted web MQTT connection ~p (~s, client ID: ~s)",
proc_state = PState, [self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
conn_name = ConnName
}
) ->
?LOG_INFO(
"Accepted web MQTT connection ~p (~s, client ID: ~s)",
[self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]
),
State = State0#state{received_connect_packet = true}, State = State0#state{received_connect_packet = true},
{ok, ensure_stats_timer(control_throttle(State)), hibernate}; {ok, ensure_stats_timer(control_throttle(State)), hibernate};
handle_data1(<<>>, State) -> handle_data1(<<>>, State) ->
{ok, ensure_stats_timer(control_throttle(State)), hibernate}; {ok, ensure_stats_timer(control_throttle(State)), hibernate};
handle_data1( handle_data1(Data, State = #state{ parse_state = ParseState,
Data, proc_state = ProcState,
State = #state{ conn_name = ConnName }) ->
parse_state = ParseState,
proc_state = ProcState,
conn_name = ConnName
}
) ->
case parse(Data, ParseState) of case parse(Data, ParseState) of
{more, ParseState1} -> {more, ParseState1} ->
{ok, {ok, ensure_stats_timer(control_throttle(
ensure_stats_timer( State #state{ parse_state = ParseState1 })), hibernate};
control_throttle(
State#state{parse_state = ParseState1}
)
),
hibernate};
{ok, Packet, Rest} -> {ok, Packet, Rest} ->
case rabbit_mqtt_processor:process_packet(Packet, ProcState) of case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
{ok, ProcState1} -> {ok, ProcState1} ->
handle_data1( handle_data1(
Rest, Rest,
State#state{ State#state{parse_state = rabbit_mqtt_packet:initial_state(),
parse_state = rabbit_mqtt_packet:initial_state(), proc_state = ProcState1});
proc_state = ProcState1
}
);
{error, Reason, _} -> {error, Reason, _} ->
stop_mqtt_protocol_error(State, Reason, ConnName); stop_mqtt_protocol_error(State, Reason, ConnName);
{stop, disconnect, ProcState1} -> {stop, disconnect, ProcState1} ->
@ -363,11 +289,9 @@ parse(Data, ParseState) ->
rabbit_mqtt_packet:parse(Data, ParseState) rabbit_mqtt_packet:parse(Data, ParseState)
catch catch
_:Reason:Stacktrace -> _:Reason:Stacktrace ->
?LOG_ERROR( ?LOG_ERROR("Web MQTT cannot parse a packet, reason: ~tp, stacktrace: ~tp, "
"Web MQTT cannot parse a packet, reason: ~tp, stacktrace: ~tp, " "payload (first 100 bytes): ~tp",
"payload (first 100 bytes): ~tp", [Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Data, 100)]),
[Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Data, 100)]
),
{error, cannot_parse} {error, cannot_parse}
end. end.
@ -384,34 +308,26 @@ stop(State, CloseCode, Error0) ->
handle_credits(State0) -> handle_credits(State0) ->
State = #state{connection_state = CS} = control_throttle(State0), State = #state{connection_state = CS} = control_throttle(State0),
Active = Active = case CS of
case CS of running -> true;
running -> true; blocked -> false
blocked -> false end,
end,
{[{active, Active}], State, hibernate}. {[{active, Active}], State, hibernate}.
control_throttle( control_throttle(State = #state{connection_state = ConnState,
State = #state{ conserve = Conserve,
connection_state = ConnState, received_connect_packet = Connected,
conserve = Conserve, proc_state = PState,
received_connect_packet = Connected, keepalive = KState
proc_state = PState, }) ->
keepalive = KState
}
) ->
Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState), Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
case {ConnState, Throttle} of case {ConnState, Throttle} of
{running, true} -> {running, true} ->
State#state{ State#state{connection_state = blocked,
connection_state = blocked, keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)};
keepalive = rabbit_mqtt_keepalive:cancel_timer(KState) {blocked,false} ->
}; State#state{connection_state = running,
{blocked, false} -> keepalive = rabbit_mqtt_keepalive:start_timer(KState)};
State#state{
connection_state = running,
keepalive = rabbit_mqtt_keepalive:start_timer(KState)
};
{_, _} -> {_, _} ->
State State
end. end.
@ -425,23 +341,18 @@ ensure_stats_timer(State) ->
maybe_emit_stats(#state{stats_timer = undefined}) -> maybe_emit_stats(#state{stats_timer = undefined}) ->
ok; ok;
maybe_emit_stats(State) -> maybe_emit_stats(State) ->
rabbit_event:if_enabled( rabbit_event:if_enabled(State, #state.stats_timer,
State, fun() -> emit_stats(State) end).
#state.stats_timer,
fun() -> emit_stats(State) end
).
emit_stats(State = #state{received_connect_packet = false}) -> emit_stats(State=#state{received_connect_packet = false}) ->
%% Avoid emitting stats on terminate when the connection has not yet been %% Avoid emitting stats on terminate when the connection has not yet been
%% established, as this causes orphan entries on the stats database %% established, as this causes orphan entries on the stats database
rabbit_event:reset_stats_timer(State, #state.stats_timer); rabbit_event:reset_stats_timer(State, #state.stats_timer);
emit_stats(State) -> emit_stats(State) ->
[ [{_, Pid},
{_, Pid}, {_, RecvOct},
{_, RecvOct}, {_, SendOct},
{_, SendOct}, {_, Reductions}] = infos(?SIMPLE_METRICS, State),
{_, Reductions}
] = infos(?SIMPLE_METRICS, State),
Infos = infos(?OTHER_METRICS, State), Infos = infos(?OTHER_METRICS, State),
rabbit_core_metrics:connection_stats(Pid, Infos), rabbit_core_metrics:connection_stats(Pid, Infos),
rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions), rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
@ -453,13 +364,12 @@ infos(Items, State) ->
i(pid, _) -> i(pid, _) ->
self(); self();
i(SockStat, #state{socket = Sock}) when i(SockStat, #state{socket = Sock})
SockStat =:= recv_oct; when SockStat =:= recv_oct;
SockStat =:= recv_cnt; SockStat =:= recv_cnt;
SockStat =:= send_oct; SockStat =:= send_oct;
SockStat =:= send_cnt; SockStat =:= send_cnt;
SockStat =:= send_pend SockStat =:= send_pend ->
->
case rabbit_net:getstat(Sock, [SockStat]) of case rabbit_net:getstat(Sock, [SockStat]) of
{ok, [{_, N}]} when is_number(N) -> {ok, [{_, N}]} when is_number(N) ->
N; N;
@ -473,23 +383,22 @@ i(garbage_collection, _) ->
rabbit_misc:get_gc_info(self()); rabbit_misc:get_gc_info(self());
i(protocol, #state{proc_state = PState}) -> i(protocol, #state{proc_state = PState}) ->
{?PROTO_FAMILY, rabbit_mqtt_processor:proto_version_tuple(PState)}; {?PROTO_FAMILY, rabbit_mqtt_processor:proto_version_tuple(PState)};
i(SSL, #state{socket = Sock}) when i(SSL, #state{socket = Sock})
SSL =:= ssl; when SSL =:= ssl;
SSL =:= ssl_protocol; SSL =:= ssl_protocol;
SSL =:= ssl_key_exchange; SSL =:= ssl_key_exchange;
SSL =:= ssl_cipher; SSL =:= ssl_cipher;
SSL =:= ssl_hash SSL =:= ssl_hash ->
-> rabbit_ssl:info(SSL, {rabbit_net:unwrap_socket(Sock),
rabbit_ssl:info(SSL, {rabbit_net:unwrap_socket(Sock), rabbit_net:maybe_get_proxy_socket(Sock)}); rabbit_net:maybe_get_proxy_socket(Sock)});
i(name, S) -> i(name, S) ->
i(conn_name, S); i(conn_name, S);
i(conn_name, #state{conn_name = Val}) -> i(conn_name, #state{conn_name = Val}) ->
Val; Val;
i(Cert, #state{socket = Sock}) when i(Cert, #state{socket = Sock})
Cert =:= peer_cert_issuer; when Cert =:= peer_cert_issuer;
Cert =:= peer_cert_subject; Cert =:= peer_cert_subject;
Cert =:= peer_cert_validity Cert =:= peer_cert_validity ->
->
rabbit_ssl:cert_info(Cert, rabbit_net:unwrap_socket(Sock)); rabbit_ssl:cert_info(Cert, rabbit_net:unwrap_socket(Sock));
i(state, S) -> i(state, S) ->
i(connection_state, S); i(connection_state, S);

View File

@ -14,6 +14,7 @@
-export([terminate/3]). -export([terminate/3]).
-export([early_error/5]). -export([early_error/5]).
-record(state, {next}). -record(state, {next}).
init(StreamID, Req, Opts) -> init(StreamID, Req, Opts) ->

View File

@ -23,6 +23,7 @@ init_per_suite(Config) ->
Config1 = rabbit_ct_helpers:run_setup_steps(Config), Config1 = rabbit_ct_helpers:run_setup_steps(Config),
rabbit_ct_config_schema:init_schemas(rabbitmq_web_mqtt, Config1). rabbit_ct_config_schema:init_schemas(rabbitmq_web_mqtt, Config1).
end_per_suite(Config) -> end_per_suite(Config) ->
rabbit_ct_helpers:run_teardown_steps(Config). rabbit_ct_helpers:run_teardown_steps(Config).
@ -30,19 +31,15 @@ init_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:testcase_started(Config, Testcase), rabbit_ct_helpers:testcase_started(Config, Testcase),
Config1 = rabbit_ct_helpers:set_config(Config, [ Config1 = rabbit_ct_helpers:set_config(Config, [
{rmq_nodename_suffix, Testcase} {rmq_nodename_suffix, Testcase}
]), ]),
rabbit_ct_helpers:run_steps( rabbit_ct_helpers:run_steps(Config1,
Config1, rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_broker_helpers:setup_steps() ++ rabbit_ct_client_helpers:setup_steps()).
rabbit_ct_client_helpers:setup_steps()
).
end_per_testcase(Testcase, Config) -> end_per_testcase(Testcase, Config) ->
Config1 = rabbit_ct_helpers:run_steps( Config1 = rabbit_ct_helpers:run_steps(Config,
Config, rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_client_helpers:teardown_steps() ++ rabbit_ct_broker_helpers:teardown_steps()),
rabbit_ct_broker_helpers:teardown_steps()
),
rabbit_ct_helpers:testcase_finished(Config1, Testcase). rabbit_ct_helpers:testcase_finished(Config1, Testcase).
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
@ -50,13 +47,9 @@ end_per_testcase(Testcase, Config) ->
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
run_snippets(Config) -> run_snippets(Config) ->
ok = rabbit_ct_broker_helpers:rpc( ok = rabbit_ct_broker_helpers:rpc(Config, 0,
Config, ?MODULE, run_snippets1, [Config]).
0,
?MODULE,
run_snippets1,
[Config]
).
run_snippets1(Config) -> run_snippets1(Config) ->
rabbit_ct_config_schema:run_snippets(Config). rabbit_ct_config_schema:run_snippets(Config).

View File

@ -7,6 +7,7 @@
-module(proxy_protocol_SUITE). -module(proxy_protocol_SUITE).
-compile([export_all, nowarn_export_all]). -compile([export_all, nowarn_export_all]).
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
@ -14,24 +15,20 @@
suite() -> suite() ->
[ [
%% If a test hangs, no need to wait for 30 minutes. %% If a test hangs, no need to wait for 30 minutes.
{timetrap, {minutes, 2}} {timetrap, {minutes, 2}}
]. ].
all() -> all() ->
[ [{group, http_tests},
{group, http_tests}, {group, https_tests}].
{group, https_tests}
].
groups() -> groups() ->
Tests = [ Tests = [
proxy_protocol proxy_protocol
], ],
[ [{https_tests, [], Tests},
{https_tests, [], Tests}, {http_tests, [], Tests}].
{http_tests, [], Tests}
].
init_per_suite(Config) -> init_per_suite(Config) ->
rabbit_ct_helpers:log_environment(), rabbit_ct_helpers:log_environment(),
@ -41,29 +38,22 @@ end_per_suite(Config) ->
Config. Config.
init_per_group(Group, Config) -> init_per_group(Group, Config) ->
Protocol = Protocol = case Group of
case Group of http_tests -> "ws";
http_tests -> "ws"; https_tests -> "wss"
https_tests -> "wss" end,
end, Config1 = rabbit_ct_helpers:set_config(Config,
Config1 = rabbit_ct_helpers:set_config( [{rmq_nodename_suffix, ?MODULE},
Config, {protocol, Protocol},
[ {rabbitmq_ct_tls_verify, verify_none},
{rmq_nodename_suffix, ?MODULE}, {rabbitmq_ct_tls_fail_if_no_peer_cert, false}]),
{protocol, Protocol},
{rabbitmq_ct_tls_verify, verify_none},
{rabbitmq_ct_tls_fail_if_no_peer_cert, false}
]
),
rabbit_ct_helpers:run_setup_steps( rabbit_ct_helpers:run_setup_steps(
Config1, Config1,
rabbit_ct_broker_helpers:setup_steps() ++ rabbit_ct_broker_helpers:setup_steps() ++ [
[ fun configure_proxy_protocol/1,
fun configure_proxy_protocol/1, fun configure_ssl/1
fun configure_ssl/1 ]).
]
).
configure_proxy_protocol(Config) -> configure_proxy_protocol(Config) ->
rabbit_ws_test_util:update_app_env(Config, proxy_protocol, true), rabbit_ws_test_util:update_app_env(Config, proxy_protocol, true),
@ -74,16 +64,12 @@ configure_ssl(Config) ->
RabbitAppConfig = proplists:get_value(rabbit, ErlangConfig, []), RabbitAppConfig = proplists:get_value(rabbit, ErlangConfig, []),
RabbitSslConfig = proplists:get_value(ssl_options, RabbitAppConfig, []), RabbitSslConfig = proplists:get_value(ssl_options, RabbitAppConfig, []),
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_web_mqtt_tls), Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_web_mqtt_tls),
rabbit_ws_test_util:update_app_env(Config, ssl_config, [ rabbit_ws_test_util:update_app_env(Config, ssl_config, [{port, Port} | lists:keydelete(port, 1, RabbitSslConfig)]),
{port, Port} | lists:keydelete(port, 1, RabbitSslConfig)
]),
Config. Config.
end_per_group(_Group, Config) -> end_per_group(_Group, Config) ->
rabbit_ct_helpers:run_teardown_steps( rabbit_ct_helpers:run_teardown_steps(Config,
Config, rabbit_ct_broker_helpers:teardown_steps()).
rabbit_ct_broker_helpers:teardown_steps()
).
init_per_testcase(Testcase, Config) -> init_per_testcase(Testcase, Config) ->
rabbit_ct_helpers:testcase_started(Config, Testcase). rabbit_ct_helpers:testcase_started(Config, Testcase).
@ -95,23 +81,13 @@ proxy_protocol(Config) ->
PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config), PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config),
Protocol = ?config(protocol, Config), Protocol = ?config(protocol, Config),
WS = rfc6455_client:new( WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self(),
Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", undefined, ["mqtt"], "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
self(),
undefined,
["mqtt"],
"PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"
),
{ok, _} = rfc6455_client:open(WS), {ok, _} = rfc6455_client:open(WS),
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()), rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
{binary, _P} = rfc6455_client:recv(WS), {binary, _P} = rfc6455_client:recv(WS),
ConnectionName = rabbit_ct_broker_helpers:rpc( ConnectionName = rabbit_ct_broker_helpers:rpc(Config, 0,
Config, ?MODULE, connection_name, []),
0,
?MODULE,
connection_name,
[]
),
match = re:run(ConnectionName, <<"^192.168.1.1:80 -> 192.168.1.2:81$">>, [{capture, none}]), match = re:run(ConnectionName, <<"^192.168.1.1:80 -> 192.168.1.2:81$">>, [{capture, none}]),
{close, _} = rfc6455_client:close(WS), {close, _} = rfc6455_client:close(WS),
ok. ok.

View File

@ -18,13 +18,13 @@ all() ->
groups() -> groups() ->
[ [
{tests, [], [ {tests, [],
no_websocket_subprotocol, [no_websocket_subprotocol
unsupported_websocket_subprotocol, ,unsupported_websocket_subprotocol
unacceptable_data_type, ,unacceptable_data_type
handle_invalid_packets, ,handle_invalid_packets
duplicate_connect ,duplicate_connect
]} ]}
]. ].
suite() -> suite() ->
@ -35,19 +35,15 @@ init_per_suite(Config) ->
Config1 = rabbit_ct_helpers:set_config(Config, [ Config1 = rabbit_ct_helpers:set_config(Config, [
{rmq_nodename_suffix, ?MODULE}, {rmq_nodename_suffix, ?MODULE},
{protocol, "ws"} {protocol, "ws"}
]), ]),
rabbit_ct_helpers:run_setup_steps( rabbit_ct_helpers:run_setup_steps(Config1,
Config1, rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_broker_helpers:setup_steps() ++ rabbit_ct_client_helpers:setup_steps()).
rabbit_ct_client_helpers:setup_steps()
).
end_per_suite(Config) -> end_per_suite(Config) ->
rabbit_ct_helpers:run_teardown_steps( rabbit_ct_helpers:run_teardown_steps(Config,
Config, rabbit_ct_client_helpers:teardown_steps() ++
rabbit_ct_client_helpers:teardown_steps() ++ rabbit_ct_broker_helpers:teardown_steps()).
rabbit_ct_broker_helpers:teardown_steps()
).
init_per_group(_, Config) -> init_per_group(_, Config) ->
Config. Config.
@ -76,9 +72,7 @@ websocket_subprotocol(Config, SubProtocol) ->
PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config), PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config),
WS = rfc6455_client:new("ws://localhost:" ++ PortStr ++ "/ws", self(), undefined, SubProtocol), WS = rfc6455_client:new("ws://localhost:" ++ PortStr ++ "/ws", self(), undefined, SubProtocol),
{_, [{http_response, Res}]} = rfc6455_client:open(WS), {_, [{http_response, Res}]} = rfc6455_client:open(WS),
{'HTTP/1.1', 400, <<"Bad Request">>, _} = cow_http:parse_status_line( {'HTTP/1.1', 400, <<"Bad Request">>, _} = cow_http:parse_status_line(rabbit_data_coercion:to_binary(Res)),
rabbit_data_coercion:to_binary(Res)
),
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()), rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
{close, _} = rfc6455_client:recv(WS, timer:seconds(1)). {close, _} = rfc6455_client:recv(WS, timer:seconds(1)).
@ -116,8 +110,7 @@ duplicate_connect(Config) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()), rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
eventually(?_assertEqual(0, num_mqtt_connections(Config, 0))), eventually(?_assertEqual(0, num_mqtt_connections(Config, 0))),
receive receive {'EXIT', WS, _} -> ok
{'EXIT', WS, _} -> ok
after 500 -> ct:fail("expected web socket to exit") after 500 -> ct:fail("expected web socket to exit")
end. end.

View File

@ -14,10 +14,3 @@
inline_items => {when_under, 4} inline_items => {when_under, 4}
}} }}
]}. ]}.
{project_plugins, [erlfmt]}.
{erlfmt, [
write,
{print_width, 100},
{files, "deps/{rabbitmq_mqtt,rabbitmq_web_mqtt}/{test,src}/*.erl"}
]}.