Revert "Format MQTT code with `erlfmt`"
This commit is contained in:
parent
6806a9a45e
commit
209f23fa2f
|
@ -1 +0,0 @@
|
|||
1de9fcf582def91d1cee6bea457dd24e8a53a431
|
|
@ -10,20 +10,18 @@
|
|||
|
||||
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
|
||||
|
||||
-export([
|
||||
scopes/0,
|
||||
switches/0,
|
||||
aliases/0,
|
||||
usage/0,
|
||||
usage_doc_guides/0,
|
||||
banner/2,
|
||||
validate/2,
|
||||
merge_defaults/2,
|
||||
run/2,
|
||||
output/2,
|
||||
description/0,
|
||||
help_section/0
|
||||
]).
|
||||
-export([scopes/0,
|
||||
switches/0,
|
||||
aliases/0,
|
||||
usage/0,
|
||||
usage_doc_guides/0,
|
||||
banner/2,
|
||||
validate/2,
|
||||
merge_defaults/2,
|
||||
run/2,
|
||||
output/2,
|
||||
description/0,
|
||||
help_section/0]).
|
||||
|
||||
scopes() -> [ctl].
|
||||
switches() -> [].
|
||||
|
@ -50,29 +48,20 @@ usage() ->
|
|||
usage_doc_guides() ->
|
||||
[?MQTT_GUIDE_URL].
|
||||
|
||||
run([Node], #{
|
||||
node := NodeName,
|
||||
timeout := Timeout
|
||||
}) ->
|
||||
run([Node], #{node := NodeName,
|
||||
timeout := Timeout}) ->
|
||||
case rabbit_misc:rpc_call(NodeName, rabbit_mqtt_collector, leave, [Node], Timeout) of
|
||||
{badrpc, _} = Error ->
|
||||
Error;
|
||||
nodedown ->
|
||||
{ok,
|
||||
list_to_binary(
|
||||
io_lib:format(
|
||||
"Node ~ts is down but has been successfully removed"
|
||||
" from the cluster",
|
||||
[Node]
|
||||
)
|
||||
)};
|
||||
{ok, list_to_binary(io_lib:format("Node ~ts is down but has been successfully removed"
|
||||
" from the cluster", [Node]))};
|
||||
Result ->
|
||||
%% 'ok' or 'timeout'
|
||||
Result
|
||||
end.
|
||||
|
||||
banner([Node], _) ->
|
||||
list_to_binary(io_lib:format("Removing node ~ts from the list of MQTT nodes...", [Node])).
|
||||
banner([Node], _) -> list_to_binary(io_lib:format("Removing node ~ts from the list of MQTT nodes...", [Node])).
|
||||
|
||||
output(Result, _Opts) ->
|
||||
'Elixir.RabbitMQ.CLI.DefaultOutput':output(Result).
|
||||
|
|
|
@ -10,22 +10,20 @@
|
|||
|
||||
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
|
||||
|
||||
-export([
|
||||
formatter/0,
|
||||
scopes/0,
|
||||
switches/0,
|
||||
aliases/0,
|
||||
usage/0,
|
||||
usage_additional/0,
|
||||
usage_doc_guides/0,
|
||||
banner/2,
|
||||
validate/2,
|
||||
merge_defaults/2,
|
||||
run/2,
|
||||
output/2,
|
||||
description/0,
|
||||
help_section/0
|
||||
]).
|
||||
-export([formatter/0,
|
||||
scopes/0,
|
||||
switches/0,
|
||||
aliases/0,
|
||||
usage/0,
|
||||
usage_additional/0,
|
||||
usage_doc_guides/0,
|
||||
banner/2,
|
||||
validate/2,
|
||||
merge_defaults/2,
|
||||
run/2,
|
||||
output/2,
|
||||
description/0,
|
||||
help_section/0]).
|
||||
|
||||
formatter() -> 'Elixir.RabbitMQ.CLI.Formatters.Table'.
|
||||
scopes() -> [ctl, diagnostics].
|
||||
|
@ -39,14 +37,10 @@ help_section() ->
|
|||
|
||||
validate(Args, _) ->
|
||||
InfoItems = lists:map(fun atom_to_list/1, ?INFO_ITEMS),
|
||||
case
|
||||
'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':validate_info_keys(
|
||||
Args,
|
||||
InfoItems
|
||||
)
|
||||
of
|
||||
case 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':validate_info_keys(Args,
|
||||
InfoItems) of
|
||||
{ok, _} -> ok;
|
||||
Error -> Error
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
merge_defaults([], Opts) ->
|
||||
|
@ -61,22 +55,19 @@ usage_additional() ->
|
|||
Prefix = <<" must be one of ">>,
|
||||
InfoItems = 'Elixir.Enum':join(lists:usort(?INFO_ITEMS), <<", ">>),
|
||||
[
|
||||
{<<"<column>">>, <<Prefix/binary, InfoItems/binary>>}
|
||||
{<<"<column>">>, <<Prefix/binary, InfoItems/binary>>}
|
||||
].
|
||||
|
||||
usage_doc_guides() ->
|
||||
[?MQTT_GUIDE_URL].
|
||||
|
||||
run(Args, #{
|
||||
node := NodeName,
|
||||
timeout := Timeout,
|
||||
verbose := Verbose
|
||||
}) ->
|
||||
InfoKeys =
|
||||
case Verbose of
|
||||
true -> ?INFO_ITEMS;
|
||||
false -> 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':prepare_info_keys(Args)
|
||||
end,
|
||||
run(Args, #{node := NodeName,
|
||||
timeout := Timeout,
|
||||
verbose := Verbose}) ->
|
||||
InfoKeys = case Verbose of
|
||||
true -> ?INFO_ITEMS;
|
||||
false -> 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':prepare_info_keys(Args)
|
||||
end,
|
||||
|
||||
Nodes = 'Elixir.RabbitMQ.CLI.Core.Helpers':nodes_in_cluster(NodeName),
|
||||
|
||||
|
@ -87,8 +78,7 @@ run(Args, #{
|
|||
[Nodes, InfoKeys],
|
||||
Timeout,
|
||||
InfoKeys,
|
||||
length(Nodes)
|
||||
).
|
||||
length(Nodes)).
|
||||
|
||||
banner(_, _) -> <<"Listing MQTT connections ...">>.
|
||||
|
||||
|
|
|
@ -9,15 +9,13 @@
|
|||
|
||||
-include("mqtt_machine.hrl").
|
||||
|
||||
-export([
|
||||
version/0,
|
||||
which_module/1,
|
||||
init/1,
|
||||
apply/3,
|
||||
state_enter/2,
|
||||
notify_connection/2,
|
||||
overview/1
|
||||
]).
|
||||
-export([version/0,
|
||||
which_module/1,
|
||||
init/1,
|
||||
apply/3,
|
||||
state_enter/2,
|
||||
notify_connection/2,
|
||||
overview/1]).
|
||||
|
||||
-type state() :: #machine_state{}.
|
||||
|
||||
|
@ -26,10 +24,9 @@
|
|||
-type reply() :: {ok, term()} | {error, term()}.
|
||||
-type client_id() :: term().
|
||||
|
||||
-type command() ::
|
||||
{register, client_id(), pid()}
|
||||
| {unregister, client_id(), pid()}
|
||||
| list.
|
||||
-type command() :: {register, client_id(), pid()} |
|
||||
{unregister, client_id(), pid()} |
|
||||
list.
|
||||
version() -> 1.
|
||||
|
||||
which_module(1) -> ?MODULE;
|
||||
|
@ -41,130 +38,93 @@ init(_Conf) ->
|
|||
|
||||
-spec apply(map(), command(), state()) ->
|
||||
{state(), reply(), ra_machine:effects()}.
|
||||
apply(
|
||||
_Meta,
|
||||
{register, ClientId, Pid},
|
||||
#machine_state{
|
||||
client_ids = Ids,
|
||||
pids = Pids0
|
||||
} = State0
|
||||
) ->
|
||||
apply(_Meta, {register, ClientId, Pid},
|
||||
#machine_state{client_ids = Ids,
|
||||
pids = Pids0} = State0) ->
|
||||
{Effects, Ids1, Pids} =
|
||||
case maps:find(ClientId, Ids) of
|
||||
{ok, OldPid} when Pid =/= OldPid ->
|
||||
Effects0 = [
|
||||
{demonitor, process, OldPid},
|
||||
{monitor, process, Pid},
|
||||
{mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]}
|
||||
],
|
||||
Pids2 =
|
||||
case maps:take(OldPid, Pids0) of
|
||||
error ->
|
||||
Pids0;
|
||||
{[ClientId], Pids1} ->
|
||||
Pids1;
|
||||
{ClientIds, Pids1} ->
|
||||
Pids1#{ClientId => lists:delete(ClientId, ClientIds)}
|
||||
end,
|
||||
Pids3 = maps:update_with(
|
||||
Pid,
|
||||
fun(CIds) -> [ClientId | CIds] end,
|
||||
[ClientId],
|
||||
Pids2
|
||||
),
|
||||
Effects0 = [{demonitor, process, OldPid},
|
||||
{monitor, process, Pid},
|
||||
{mod_call, ?MODULE, notify_connection,
|
||||
[OldPid, duplicate_id]}],
|
||||
Pids2 = case maps:take(OldPid, Pids0) of
|
||||
error ->
|
||||
Pids0;
|
||||
{[ClientId], Pids1} ->
|
||||
Pids1;
|
||||
{ClientIds, Pids1} ->
|
||||
Pids1#{ClientId => lists:delete(ClientId, ClientIds)}
|
||||
end,
|
||||
Pids3 = maps:update_with(Pid, fun(CIds) -> [ClientId | CIds] end,
|
||||
[ClientId], Pids2),
|
||||
{Effects0, maps:remove(ClientId, Ids), Pids3};
|
||||
{ok, Pid} ->
|
||||
|
||||
{ok, Pid} ->
|
||||
{[], Ids, Pids0};
|
||||
error ->
|
||||
Pids1 = maps:update_with(
|
||||
Pid,
|
||||
fun(CIds) -> [ClientId | CIds] end,
|
||||
[ClientId],
|
||||
Pids0
|
||||
),
|
||||
Pids1 = maps:update_with(Pid, fun(CIds) -> [ClientId | CIds] end,
|
||||
[ClientId], Pids0),
|
||||
Effects0 = [{monitor, process, Pid}],
|
||||
{Effects0, Ids, Pids1}
|
||||
end,
|
||||
State = State0#machine_state{
|
||||
client_ids = maps:put(ClientId, Pid, Ids1),
|
||||
pids = Pids
|
||||
},
|
||||
State = State0#machine_state{client_ids = maps:put(ClientId, Pid, Ids1),
|
||||
pids = Pids},
|
||||
{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{
|
||||
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,
|
||||
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{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}],
|
||||
%% snapshot only when the map has changed
|
||||
Effects =
|
||||
case State of
|
||||
State0 -> Effects0;
|
||||
_ -> Effects0 ++ snapshot_effects(Meta, State)
|
||||
end,
|
||||
Effects = case State of
|
||||
State0 -> Effects0;
|
||||
_ -> Effects0 ++ snapshot_effects(Meta, State)
|
||||
end,
|
||||
{State, ok, Effects};
|
||||
|
||||
apply(_Meta, {down, DownPid, noconnection}, State) ->
|
||||
%% 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
|
||||
%% actual fate of the connection processes on it
|
||||
Effect = {monitor, node, node(DownPid)},
|
||||
{State, ok, Effect};
|
||||
apply(
|
||||
Meta,
|
||||
{down, DownPid, _},
|
||||
#machine_state{
|
||||
client_ids = Ids,
|
||||
pids = Pids0
|
||||
} = State0
|
||||
) ->
|
||||
|
||||
apply(Meta, {down, DownPid, _}, #machine_state{client_ids = Ids,
|
||||
pids = Pids0} = State0) ->
|
||||
case maps:get(DownPid, Pids0, undefined) of
|
||||
undefined ->
|
||||
{State0, ok, []};
|
||||
ClientIds ->
|
||||
Ids1 = maps:without(ClientIds, Ids),
|
||||
State = State0#machine_state{
|
||||
client_ids = Ids1,
|
||||
pids = maps:remove(DownPid, Pids0)
|
||||
},
|
||||
Effects = lists:map(
|
||||
fun(Id) ->
|
||||
[
|
||||
{mod_call, rabbit_log, debug, [
|
||||
"MQTT connection with client id '~ts' failed", [Id]
|
||||
]}
|
||||
]
|
||||
end,
|
||||
ClientIds
|
||||
),
|
||||
State = State0#machine_state{client_ids = Ids1,
|
||||
pids = maps:remove(DownPid, Pids0)},
|
||||
Effects = lists:map(fun(Id) ->
|
||||
[{mod_call, rabbit_log, debug,
|
||||
["MQTT connection with client id '~ts' failed", [Id]]}]
|
||||
end, ClientIds),
|
||||
{State, ok, Effects ++ snapshot_effects(Meta, State)}
|
||||
end;
|
||||
|
||||
apply(_Meta, {nodeup, Node}, State) ->
|
||||
%% Work out if any pids that were disconnected are still
|
||||
%% alive.
|
||||
|
@ -173,69 +133,41 @@ apply(_Meta, {nodeup, Node}, State) ->
|
|||
{State, ok, Effects};
|
||||
apply(_Meta, {nodedown, _Node}, State) ->
|
||||
{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{
|
||||
client_ids = Keep,
|
||||
pids = maps:without(maps:keys(Remove), Pids0)
|
||||
},
|
||||
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{client_ids = Keep,
|
||||
pids = maps:without(maps:keys(Remove), Pids0)},
|
||||
{State, ok, Effects ++ snapshot_effects(Meta, State)};
|
||||
apply(_Meta, {machine_version, 0, 1}, {machine_state, Ids}) ->
|
||||
Pids = maps:fold(
|
||||
fun(Id, Pid, Acc) ->
|
||||
maps:update_with(
|
||||
Pid,
|
||||
fun(CIds) -> [Id | CIds] end,
|
||||
[Id],
|
||||
Acc
|
||||
)
|
||||
end,
|
||||
#{},
|
||||
Ids
|
||||
),
|
||||
{
|
||||
#machine_state{
|
||||
client_ids = Ids,
|
||||
pids = Pids
|
||||
},
|
||||
ok,
|
||||
[]
|
||||
};
|
||||
fun(Id, Pid, Acc) ->
|
||||
maps:update_with(Pid,
|
||||
fun(CIds) -> [Id | CIds] end,
|
||||
[Id], Acc)
|
||||
end, #{}, Ids),
|
||||
{#machine_state{client_ids = Ids,
|
||||
pids = Pids}, ok, []};
|
||||
apply(_Meta, Unknown, State) ->
|
||||
logger:error("MQTT Raft state machine v1 received unknown command ~tp", [Unknown]),
|
||||
{State, {error, {unknown_command, Unknown}}, []}.
|
||||
|
@ -250,21 +182,17 @@ state_enter(_, _) ->
|
|||
[].
|
||||
|
||||
-spec overview(state()) -> map().
|
||||
overview(#machine_state{
|
||||
client_ids = ClientIds,
|
||||
pids = Pids
|
||||
}) ->
|
||||
#{
|
||||
num_client_ids => maps:size(ClientIds),
|
||||
num_pids => maps:size(Pids)
|
||||
}.
|
||||
overview(#machine_state{client_ids = ClientIds,
|
||||
pids = Pids}) ->
|
||||
#{num_client_ids => maps:size(ClientIds),
|
||||
num_pids => maps:size(Pids)}.
|
||||
|
||||
%% ==========================
|
||||
|
||||
%% Avoids blocking the Raft leader.
|
||||
-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
|
||||
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().
|
||||
snapshot_effects(#{index := RaftIdx}, State) ->
|
||||
|
|
|
@ -9,12 +9,10 @@
|
|||
|
||||
-include("mqtt_machine_v0.hrl").
|
||||
|
||||
-export([
|
||||
init/1,
|
||||
apply/3,
|
||||
state_enter/2,
|
||||
notify_connection/2
|
||||
]).
|
||||
-export([init/1,
|
||||
apply/3,
|
||||
state_enter/2,
|
||||
notify_connection/2]).
|
||||
|
||||
-type state() :: #machine_state{}.
|
||||
|
||||
|
@ -23,10 +21,9 @@
|
|||
-type reply() :: {ok, term()} | {error, term()}.
|
||||
-type client_id() :: term().
|
||||
|
||||
-type command() ::
|
||||
{register, client_id(), pid()}
|
||||
| {unregister, client_id(), pid()}
|
||||
| list.
|
||||
-type command() :: {register, client_id(), pid()} |
|
||||
{unregister, client_id(), pid()} |
|
||||
list.
|
||||
|
||||
-spec init(config()) -> state().
|
||||
init(_Conf) ->
|
||||
|
@ -38,60 +35,53 @@ apply(_Meta, {register, ClientId, Pid}, #machine_state{client_ids = Ids} = State
|
|||
{Effects, Ids1} =
|
||||
case maps:find(ClientId, Ids) of
|
||||
{ok, OldPid} when Pid =/= OldPid ->
|
||||
Effects0 = [
|
||||
{demonitor, process, OldPid},
|
||||
{monitor, process, Pid},
|
||||
{mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]}
|
||||
],
|
||||
Effects0 = [{demonitor, process, OldPid},
|
||||
{monitor, process, Pid},
|
||||
{mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]}],
|
||||
{Effects0, maps:remove(ClientId, Ids)};
|
||||
_ ->
|
||||
Effects0 = [{monitor, process, Pid}],
|
||||
{Effects0, Ids}
|
||||
Effects0 = [{monitor, process, Pid}],
|
||||
{Effects0, Ids}
|
||||
end,
|
||||
State = State0#machine_state{client_ids = maps:put(ClientId, Pid, Ids1)},
|
||||
{State, ok, Effects};
|
||||
|
||||
apply(Meta, {unregister, ClientId, Pid}, #machine_state{client_ids = Ids} = State0) ->
|
||||
State =
|
||||
case maps:find(ClientId, Ids) of
|
||||
{ok, Pid} -> State0#machine_state{client_ids = maps:remove(ClientId, Ids)};
|
||||
%% 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,
|
||||
State = case maps:find(ClientId, Ids) of
|
||||
{ok, Pid} -> State0#machine_state{client_ids = maps:remove(ClientId, Ids)};
|
||||
%% 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}],
|
||||
%% snapshot only when the map has changed
|
||||
Effects =
|
||||
case State of
|
||||
State0 -> Effects0;
|
||||
_ -> Effects0 ++ snapshot_effects(Meta, State)
|
||||
end,
|
||||
Effects = case State of
|
||||
State0 -> Effects0;
|
||||
_ -> Effects0 ++ snapshot_effects(Meta, State)
|
||||
end,
|
||||
{State, ok, Effects};
|
||||
|
||||
apply(_Meta, {down, DownPid, noconnection}, State) ->
|
||||
%% 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
|
||||
%% actual fate of the connection processes on it
|
||||
Effect = {monitor, node, node(DownPid)},
|
||||
{State, ok, Effect};
|
||||
|
||||
apply(Meta, {down, DownPid, _}, #machine_state{client_ids = Ids} = State0) ->
|
||||
Ids1 = maps:filter(
|
||||
fun
|
||||
(_ClientId, Pid) when Pid =:= DownPid ->
|
||||
false;
|
||||
(_, _) ->
|
||||
true
|
||||
end,
|
||||
Ids
|
||||
),
|
||||
Ids1 = maps:filter(fun (_ClientId, Pid) when Pid =:= DownPid ->
|
||||
false;
|
||||
(_, _) ->
|
||||
true
|
||||
end, Ids),
|
||||
State = State0#machine_state{client_ids = Ids1},
|
||||
Delta = maps:keys(Ids) -- maps:keys(Ids1),
|
||||
Effects = lists:map(
|
||||
fun(Id) ->
|
||||
[{mod_call, rabbit_log, debug, ["MQTT connection with client id '~ts' failed", [Id]]}]
|
||||
end,
|
||||
Delta
|
||||
),
|
||||
Effects = lists:map(fun(Id) ->
|
||||
[{mod_call, rabbit_log, debug,
|
||||
["MQTT connection with client id '~ts' failed", [Id]]}] end, Delta),
|
||||
{State, ok, Effects ++ snapshot_effects(Meta, State)};
|
||||
|
||||
apply(_Meta, {nodeup, Node}, State) ->
|
||||
%% Work out if any pids that were disconnected are still
|
||||
%% alive.
|
||||
|
@ -100,29 +90,25 @@ apply(_Meta, {nodeup, Node}, State) ->
|
|||
{State, ok, Effects};
|
||||
apply(_Meta, {nodedown, _Node}, State) ->
|
||||
{State, ok};
|
||||
|
||||
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),
|
||||
|
||||
Effects = lists:foldl(
|
||||
fun(ClientId, 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,
|
||||
[],
|
||||
Delta
|
||||
),
|
||||
Effects = lists:foldl(fun (ClientId, 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, [], Delta),
|
||||
|
||||
State = State0#machine_state{client_ids = Ids1},
|
||||
{State, ok, Effects ++ snapshot_effects(Meta, State)};
|
||||
|
||||
apply(_Meta, Unknown, State) ->
|
||||
logger:error("MQTT Raft state machine received an unknown command ~tp", [Unknown]),
|
||||
{State, {error, {unknown_command, Unknown}}, []}.
|
||||
|
@ -141,7 +127,7 @@ state_enter(_, _) ->
|
|||
%% Avoids blocking the Raft leader.
|
||||
-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
|
||||
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().
|
||||
snapshot_effects(#{index := RaftIdx}, State) ->
|
||||
|
|
|
@ -6,15 +6,8 @@
|
|||
%%
|
||||
-module(mqtt_node).
|
||||
|
||||
-export([
|
||||
start/0,
|
||||
node_id/0,
|
||||
server_id/0,
|
||||
all_node_ids/0,
|
||||
leave/1,
|
||||
trigger_election/0,
|
||||
delete/1
|
||||
]).
|
||||
-export([start/0, node_id/0, server_id/0, all_node_ids/0, leave/1, trigger_election/0,
|
||||
delete/1]).
|
||||
|
||||
-define(ID_NAME, mqtt_node).
|
||||
-define(START_TIMEOUT, 100_000).
|
||||
|
@ -32,11 +25,8 @@ server_id(Node) ->
|
|||
{?ID_NAME, Node}.
|
||||
|
||||
all_node_ids() ->
|
||||
[
|
||||
server_id(N)
|
||||
|| N <- rabbit_nodes:all(),
|
||||
can_participate_in_clientid_tracking(N)
|
||||
].
|
||||
[server_id(N) || N <- rabbit_nodes:all(),
|
||||
can_participate_in_clientid_tracking(N)].
|
||||
|
||||
start() ->
|
||||
%% 3s to 6s randomized
|
||||
|
@ -50,41 +40,34 @@ start(Delay, AttemptsLeft) ->
|
|||
NodeId = server_id(),
|
||||
Nodes = compatible_peer_servers(),
|
||||
case ra_directory:uid_of(?RA_SYSTEM, ?ID_NAME) of
|
||||
undefined ->
|
||||
case Nodes of
|
||||
[] ->
|
||||
%% 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
|
||||
%% 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
|
||||
%% at the same time by a deployment tool.
|
||||
%%
|
||||
%% 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
|
||||
%% to achieve without having consensus around expected cluster members.
|
||||
rabbit_log:info(
|
||||
"MQTT: will wait for ~tp more ms for cluster members to join before triggering a Raft leader election",
|
||||
[Delay]
|
||||
),
|
||||
timer:sleep(Delay),
|
||||
start(Delay, AttemptsLeft - 1);
|
||||
Peers ->
|
||||
%% Trigger an election.
|
||||
%% This is required when we start a node for the first time.
|
||||
%% Using default timeout because it supposed to reply fast.
|
||||
rabbit_log:info(
|
||||
"MQTT: discovered ~tp cluster peers that support client ID tracking", [
|
||||
length(Peers)
|
||||
]
|
||||
),
|
||||
ok = start_server(),
|
||||
_ = 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)
|
||||
undefined ->
|
||||
case Nodes of
|
||||
[] ->
|
||||
%% 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
|
||||
%% 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
|
||||
%% at the same time by a deployment tool.
|
||||
%%
|
||||
%% 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
|
||||
%% to achieve without having consensus around expected cluster members.
|
||||
rabbit_log:info("MQTT: will wait for ~tp more ms for cluster members to join before triggering a Raft leader election", [Delay]),
|
||||
timer:sleep(Delay),
|
||||
start(Delay, AttemptsLeft - 1);
|
||||
Peers ->
|
||||
%% Trigger an election.
|
||||
%% This is required when we start a node for the first time.
|
||||
%% Using default timeout because it supposed to reply fast.
|
||||
rabbit_log:info("MQTT: discovered ~tp cluster peers that support client ID tracking", [length(Peers)]),
|
||||
ok = start_server(),
|
||||
_ = 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.
|
||||
|
||||
compatible_peer_servers() ->
|
||||
|
@ -95,15 +78,14 @@ start_server() ->
|
|||
Nodes = compatible_peer_servers(),
|
||||
UId = ra:new_uid(ra_lib:to_binary(?ID_NAME)),
|
||||
Timeout = application:get_env(kernel, net_ticktime, 60) + 5,
|
||||
Conf = #{
|
||||
cluster_name => ?ID_NAME,
|
||||
id => NodeId,
|
||||
uid => UId,
|
||||
friendly_name => atom_to_list(?ID_NAME),
|
||||
initial_members => Nodes,
|
||||
log_init_args => #{uid => UId},
|
||||
tick_timeout => Timeout,
|
||||
machine => {module, mqtt_machine, #{}}
|
||||
Conf = #{cluster_name => ?ID_NAME,
|
||||
id => NodeId,
|
||||
uid => UId,
|
||||
friendly_name => atom_to_list(?ID_NAME),
|
||||
initial_members => Nodes,
|
||||
log_init_args => #{uid => UId},
|
||||
tick_timeout => Timeout,
|
||||
machine => {module, mqtt_machine, #{}}
|
||||
},
|
||||
ra:start_server(?RA_SYSTEM, Conf).
|
||||
|
||||
|
@ -121,13 +103,11 @@ join_peers(NodeId, Nodes, RetriesLeft) ->
|
|||
case ra:members(Nodes, ?START_TIMEOUT) of
|
||||
{ok, Members, _} ->
|
||||
case lists:member(NodeId, Members) of
|
||||
true -> ok;
|
||||
true -> ok;
|
||||
false -> ra:add_member(Members, NodeId)
|
||||
end;
|
||||
{timeout, _} ->
|
||||
rabbit_log:debug("MQTT: timed out contacting cluster peers, %s retries left", [
|
||||
RetriesLeft
|
||||
]),
|
||||
rabbit_log:debug("MQTT: timed out contacting cluster peers, %s retries left", [RetriesLeft]),
|
||||
timer:sleep(?RETRY_INTERVAL),
|
||||
join_peers(NodeId, Nodes, RetriesLeft - 1);
|
||||
Err ->
|
||||
|
@ -148,12 +128,12 @@ leave(Node) ->
|
|||
can_participate_in_clientid_tracking(Node) ->
|
||||
case rpc:call(Node, mqtt_machine, module_info, []) of
|
||||
{badrpc, _} -> false;
|
||||
_ -> true
|
||||
_ -> true
|
||||
end.
|
||||
|
||||
-spec delete(Args) -> Ret when
|
||||
Args :: rabbit_feature_flags:enable_callback_args(),
|
||||
Ret :: rabbit_feature_flags:enable_callback_ret().
|
||||
Args :: rabbit_feature_flags:enable_callback_args(),
|
||||
Ret :: rabbit_feature_flags:enable_callback_ret().
|
||||
delete(_) ->
|
||||
RaNodes = all_node_ids(),
|
||||
Nodes = lists:map(fun({_, N}) -> N end, RaNodes),
|
||||
|
@ -171,13 +151,12 @@ delete(_) ->
|
|||
{ok, _Leader} ->
|
||||
rabbit_log:info("Successfully deleted Ra cluster ~s", [?ID_NAME]),
|
||||
ok;
|
||||
{error, _} = Err ->
|
||||
{error, _} = Err ->
|
||||
rabbit_log:info("Failed to delete Ra cluster ~s: ~p", [?ID_NAME, Err]),
|
||||
Err
|
||||
catch
|
||||
exit:{{shutdown, delete}, _Stacktrace} ->
|
||||
rabbit_log:info("Ra cluster ~s already being deleted", [?ID_NAME]),
|
||||
ok
|
||||
catch exit:{{shutdown, delete}, _Stacktrace} ->
|
||||
rabbit_log:info("Ra cluster ~s already being deleted", [?ID_NAME]),
|
||||
ok
|
||||
end
|
||||
after
|
||||
true = global:del_lock(LockId, Nodes),
|
||||
|
|
|
@ -13,13 +13,11 @@
|
|||
-include_lib("stdlib/include/assert.hrl").
|
||||
|
||||
-export([start/2, stop/1]).
|
||||
-export([
|
||||
emit_connection_info_all/4,
|
||||
emit_connection_info_local/3,
|
||||
close_local_client_connections/1,
|
||||
%% Exported for tests, but could also be used for debugging.
|
||||
local_connection_pids/0
|
||||
]).
|
||||
-export([emit_connection_info_all/4,
|
||||
emit_connection_info_local/3,
|
||||
close_local_client_connections/1,
|
||||
%% Exported for tests, but could also be used for debugging.
|
||||
local_connection_pids/0]).
|
||||
|
||||
start(normal, []) ->
|
||||
init_global_counters(),
|
||||
|
@ -33,11 +31,10 @@ start(normal, []) ->
|
|||
ok
|
||||
end,
|
||||
Result = rabbit_mqtt_sup:start_link({Listeners, SslListeners}, []),
|
||||
EMPid =
|
||||
case rabbit_event:start_link() of
|
||||
{ok, Pid} -> Pid;
|
||||
{error, {already_started, Pid}} -> Pid
|
||||
end,
|
||||
EMPid = case rabbit_event:start_link() of
|
||||
{ok, Pid} -> Pid;
|
||||
{error, {already_started, Pid}} -> Pid
|
||||
end,
|
||||
gen_event:add_handler(EMPid, rabbit_mqtt_internal_event_handler, []),
|
||||
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.
|
||||
[AggregatorPid ! {Ref, finished} || _ <- lists:seq(1, length(Nodes) - 1)];
|
||||
false ->
|
||||
Pids = [
|
||||
spawn_link(
|
||||
Node,
|
||||
?MODULE,
|
||||
emit_connection_info_local,
|
||||
[Items, Ref, AggregatorPid]
|
||||
)
|
||||
|| Node <- Nodes
|
||||
],
|
||||
Pids = [spawn_link(Node, ?MODULE, emit_connection_info_local,
|
||||
[Items, Ref, AggregatorPid])
|
||||
|| Node <- Nodes],
|
||||
rabbit_control_misc:await_emitters_termination(Pids)
|
||||
end.
|
||||
|
||||
|
@ -74,23 +65,17 @@ emit_connection_info_local(Items, Ref, AggregatorPid) ->
|
|||
|
||||
emit_connection_info(Items, Ref, AggregatorPid, Pids) ->
|
||||
rabbit_control_misc:emitting_map_with_exit_handler(
|
||||
AggregatorPid,
|
||||
Ref,
|
||||
fun(Pid) ->
|
||||
rabbit_mqtt_reader:info(Pid, Items)
|
||||
end,
|
||||
Pids
|
||||
).
|
||||
AggregatorPid, Ref,
|
||||
fun(Pid) ->
|
||||
rabbit_mqtt_reader:info(Pid, Items)
|
||||
end, Pids).
|
||||
|
||||
-spec close_local_client_connections(string() | binary()) -> {'ok', non_neg_integer()}.
|
||||
close_local_client_connections(Reason) ->
|
||||
Pids = local_connection_pids(),
|
||||
lists:foreach(
|
||||
fun(Pid) ->
|
||||
rabbit_mqtt_reader:close_connection(Pid, Reason)
|
||||
end,
|
||||
Pids
|
||||
),
|
||||
lists:foreach(fun(Pid) ->
|
||||
rabbit_mqtt_reader:close_connection(Pid, Reason)
|
||||
end, Pids),
|
||||
{ok, length(Pids)}.
|
||||
|
||||
-spec local_connection_pids() -> [pid()].
|
||||
|
@ -101,12 +86,9 @@ local_connection_pids() ->
|
|||
lists:filter(fun(Pid) -> node(Pid) =:= node() end, AllPids);
|
||||
false ->
|
||||
PgScope = persistent_term:get(?PG_SCOPE),
|
||||
lists:flatmap(
|
||||
fun(Group) ->
|
||||
pg:get_local_members(PgScope, Group)
|
||||
end,
|
||||
pg:which_groups(PgScope)
|
||||
)
|
||||
lists:flatmap(fun(Group) ->
|
||||
pg:get_local_members(PgScope, Group)
|
||||
end, pg:which_groups(PgScope))
|
||||
end.
|
||||
|
||||
init_global_counters() ->
|
||||
|
|
|
@ -9,13 +9,8 @@
|
|||
|
||||
-include("mqtt_machine.hrl").
|
||||
|
||||
-export([
|
||||
register/2, register/3,
|
||||
unregister/2,
|
||||
list/0,
|
||||
list_pids/0,
|
||||
leave/1
|
||||
]).
|
||||
-export([register/2, register/3, unregister/2,
|
||||
list/0, list_pids/0, leave/1]).
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
-spec register(term(), pid()) -> {ok, reference()} | {error, term()}.
|
||||
|
@ -26,7 +21,7 @@ register(ClientId, Pid) ->
|
|||
case ra:members(NodeId) of
|
||||
{ok, _, Leader} ->
|
||||
register(Leader, ClientId, Pid);
|
||||
_ = Error ->
|
||||
_ = Error ->
|
||||
Error
|
||||
end;
|
||||
Leader ->
|
||||
|
@ -65,31 +60,25 @@ list(QF) ->
|
|||
undefined ->
|
||||
NodeIds = mqtt_node:all_node_ids(),
|
||||
case ra:leader_query(NodeIds, QF) of
|
||||
{ok, {_, Result}, _} ->
|
||||
Result;
|
||||
{timeout, _} ->
|
||||
rabbit_log:debug(
|
||||
"~ts:list/1 leader query timed out",
|
||||
[?MODULE]
|
||||
),
|
||||
{ok, {_, Result}, _} -> Result;
|
||||
{timeout, _} ->
|
||||
rabbit_log:debug("~ts:list/1 leader query timed out",
|
||||
[?MODULE]),
|
||||
[]
|
||||
end;
|
||||
Leader ->
|
||||
case ra:leader_query(Leader, QF) of
|
||||
{ok, {_, Result}, _} ->
|
||||
Result;
|
||||
{ok, {_, Result}, _} -> Result;
|
||||
{error, _} ->
|
||||
[];
|
||||
{timeout, _} ->
|
||||
rabbit_log:debug(
|
||||
"~ts:list/1 leader query timed out",
|
||||
[?MODULE]
|
||||
),
|
||||
{timeout, _} ->
|
||||
rabbit_log:debug("~ts:list/1 leader query timed out",
|
||||
[?MODULE]),
|
||||
[]
|
||||
end
|
||||
end.
|
||||
|
||||
-spec leave(binary()) -> ok | timeout | nodedown.
|
||||
-spec leave(binary()) -> ok | timeout | nodedown.
|
||||
leave(NodeBin) ->
|
||||
Node = binary_to_atom(NodeBin, utf8),
|
||||
ServerId = mqtt_node:server_id(),
|
||||
|
|
|
@ -10,15 +10,13 @@
|
|||
-include("rabbit_mqtt_packet.hrl").
|
||||
-compile({no_auto_import, [size/1]}).
|
||||
|
||||
-export([
|
||||
init/0,
|
||||
insert/3,
|
||||
confirm/3,
|
||||
reject/2,
|
||||
remove_queue/2,
|
||||
size/1,
|
||||
contains/2
|
||||
]).
|
||||
-export([init/0,
|
||||
insert/3,
|
||||
confirm/3,
|
||||
reject/2,
|
||||
remove_queue/2,
|
||||
size/1,
|
||||
contains/2]).
|
||||
|
||||
%% As done in OTP's sets module:
|
||||
%% Empty list is cheaper to serialize than atom.
|
||||
|
@ -41,32 +39,26 @@ contains(PktId, State) ->
|
|||
maps:is_key(PktId, State).
|
||||
|
||||
-spec insert(packet_id(), [queue_name()], state()) -> state().
|
||||
insert(PktId, QNames, State) when
|
||||
is_integer(PktId) andalso
|
||||
PktId > 0 andalso
|
||||
not is_map_key(PktId, State)
|
||||
->
|
||||
insert(PktId, QNames, State)
|
||||
when is_integer(PktId) andalso
|
||||
PktId > 0 andalso
|
||||
not is_map_key(PktId, State) ->
|
||||
QMap = maps:from_keys(QNames, ?VALUE),
|
||||
maps:put(PktId, QMap, State).
|
||||
|
||||
-spec confirm([packet_id()], queue_name(), state()) ->
|
||||
{[packet_id()], state()}.
|
||||
confirm(PktIds, QName, State0) ->
|
||||
{L0, State} = lists:foldl(
|
||||
fun(PktId, Acc) ->
|
||||
confirm_one(PktId, QName, Acc)
|
||||
end,
|
||||
{[], State0},
|
||||
PktIds
|
||||
),
|
||||
{L0, State} = lists:foldl(fun(PktId, Acc) ->
|
||||
confirm_one(PktId, QName, Acc)
|
||||
end, {[], State0}, PktIds),
|
||||
L = lists:reverse(L0),
|
||||
{L, State}.
|
||||
|
||||
-spec reject(packet_id(), state()) ->
|
||||
{ok, state()} | {error, not_found}.
|
||||
reject(PktId, State0) when
|
||||
is_integer(PktId)
|
||||
->
|
||||
reject(PktId, State0)
|
||||
when is_integer(PktId) ->
|
||||
case maps:take(PktId, State0) of
|
||||
{_, State} ->
|
||||
{ok, State};
|
||||
|
@ -79,31 +71,24 @@ reject(PktId, State0) when
|
|||
{[packet_id()], state()}.
|
||||
remove_queue(QName, State) ->
|
||||
PktIds = maps:fold(
|
||||
fun
|
||||
(PktId, QMap, PktIds) when
|
||||
is_map_key(QName, QMap)
|
||||
->
|
||||
[PktId | PktIds];
|
||||
(_, _, PktIds) ->
|
||||
PktIds
|
||||
end,
|
||||
[],
|
||||
State
|
||||
),
|
||||
fun(PktId, QMap, PktIds)
|
||||
when is_map_key(QName, QMap) ->
|
||||
[PktId | PktIds];
|
||||
(_, _, PktIds) ->
|
||||
PktIds
|
||||
end, [], State),
|
||||
confirm(lists:sort(PktIds), QName, State).
|
||||
|
||||
%% INTERNAL
|
||||
|
||||
confirm_one(PktId, QName, {PktIds, State0}) when
|
||||
is_integer(PktId)
|
||||
->
|
||||
confirm_one(PktId, QName, {PktIds, State0})
|
||||
when is_integer(PktId) ->
|
||||
case maps:take(PktId, State0) of
|
||||
{QMap0, State1} when
|
||||
is_map_key(QName, QMap0) andalso
|
||||
map_size(QMap0) =:= 1
|
||||
->
|
||||
{QMap0, State1}
|
||||
when is_map_key(QName, QMap0)
|
||||
andalso map_size(QMap0) =:= 1 ->
|
||||
%% last queue confirm
|
||||
{[PktId | PktIds], State1};
|
||||
{[PktId| PktIds], State1};
|
||||
{QMap0, State1} ->
|
||||
QMap = maps:remove(QName, QMap0),
|
||||
State = maps:put(PktId, QMap, State1),
|
||||
|
|
|
@ -12,19 +12,17 @@
|
|||
-export([track_client_id_in_ra/0]).
|
||||
|
||||
-rabbit_feature_flag(
|
||||
{?QUEUE_TYPE_QOS_0, #{
|
||||
desc => "Support pseudo queue type for MQTT QoS 0 subscribers omitting a queue process",
|
||||
stability => stable
|
||||
}}
|
||||
).
|
||||
{?QUEUE_TYPE_QOS_0,
|
||||
#{desc => "Support pseudo queue type for MQTT QoS 0 subscribers omitting a queue process",
|
||||
stability => stable
|
||||
}}).
|
||||
|
||||
-rabbit_feature_flag(
|
||||
{delete_ra_cluster_mqtt_node, #{
|
||||
desc => "Delete Ra cluster 'mqtt_node' since MQTT client IDs are tracked locally",
|
||||
stability => stable,
|
||||
callbacks => #{enable => {mqtt_node, delete}}
|
||||
}}
|
||||
).
|
||||
{delete_ra_cluster_mqtt_node,
|
||||
#{desc => "Delete Ra cluster 'mqtt_node' since MQTT client IDs are tracked locally",
|
||||
stability => stable,
|
||||
callbacks => #{enable => {mqtt_node, delete}}
|
||||
}}).
|
||||
|
||||
-spec track_client_id_in_ra() -> boolean().
|
||||
track_client_id_in_ra() ->
|
||||
|
|
|
@ -28,9 +28,7 @@ handle_event({event, vhost_deleted, Info, _, _}, ?STATE) ->
|
|||
{ok, ?STATE};
|
||||
handle_event({event, maintenance_connections_closed, _Info, _, _}, ?STATE) ->
|
||||
%% we should close our connections
|
||||
{ok, NConnections} = rabbit_mqtt:close_local_client_connections(
|
||||
"node is being put into maintenance mode"
|
||||
),
|
||||
{ok, NConnections} = rabbit_mqtt:close_local_client_connections("node is being put into maintenance mode"),
|
||||
rabbit_log:warning("Closed ~b local MQTT client connections", [NConnections]),
|
||||
{ok, ?STATE};
|
||||
handle_event(_Event, ?STATE) ->
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
-module(rabbit_mqtt_keepalive).
|
||||
|
||||
-export([
|
||||
init/0,
|
||||
start/2,
|
||||
handle/2,
|
||||
start_timer/1,
|
||||
cancel_timer/1,
|
||||
interval_secs/1
|
||||
]).
|
||||
-export([init/0,
|
||||
start/2,
|
||||
handle/2,
|
||||
start_timer/1,
|
||||
cancel_timer/1,
|
||||
interval_secs/1]).
|
||||
|
||||
-export_type([state/0]).
|
||||
|
||||
-record(state, {
|
||||
%% Keep Alive value as sent in the CONNECT packet.
|
||||
interval_secs :: pos_integer(),
|
||||
timer :: reference(),
|
||||
socket :: inet:socket(),
|
||||
recv_oct :: non_neg_integer(),
|
||||
received :: boolean()
|
||||
}).
|
||||
%% Keep Alive value as sent in the CONNECT packet.
|
||||
interval_secs :: pos_integer(),
|
||||
timer :: reference(),
|
||||
socket :: inet:socket(),
|
||||
recv_oct :: non_neg_integer(),
|
||||
received :: boolean()}).
|
||||
|
||||
-opaque state() :: disabled | #state{}.
|
||||
-opaque(state() :: disabled | #state{}).
|
||||
|
||||
-spec init() -> state().
|
||||
init() ->
|
||||
|
@ -29,9 +26,8 @@ init() ->
|
|||
-spec start(IntervalSeconds :: non_neg_integer(), inet:socket()) -> ok.
|
||||
start(0, _Sock) ->
|
||||
ok;
|
||||
start(Seconds, Sock) when
|
||||
is_integer(Seconds) andalso Seconds > 0
|
||||
->
|
||||
start(Seconds, Sock)
|
||||
when is_integer(Seconds) andalso Seconds > 0 ->
|
||||
self() ! {keepalive, {init, Seconds, Sock}},
|
||||
ok.
|
||||
|
||||
|
@ -40,28 +36,20 @@ start(Seconds, Sock) when
|
|||
handle({init, IntervalSecs, Sock}, _State) ->
|
||||
case rabbit_net:getstat(Sock, [recv_oct]) of
|
||||
{ok, [{recv_oct, RecvOct}]} ->
|
||||
{ok, #state{
|
||||
interval_secs = IntervalSecs,
|
||||
timer = start_timer0(IntervalSecs),
|
||||
socket = Sock,
|
||||
recv_oct = RecvOct,
|
||||
received = true
|
||||
}};
|
||||
{ok, #state{interval_secs = IntervalSecs,
|
||||
timer = start_timer0(IntervalSecs),
|
||||
socket = Sock,
|
||||
recv_oct = RecvOct,
|
||||
received = true}};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
handle(
|
||||
check,
|
||||
State = #state{
|
||||
socket = Sock,
|
||||
recv_oct = SameRecvOct,
|
||||
received = ReceivedPreviously
|
||||
}
|
||||
) ->
|
||||
handle(check, State = #state{socket = Sock,
|
||||
recv_oct = SameRecvOct,
|
||||
received = ReceivedPreviously}) ->
|
||||
case rabbit_net:getstat(Sock, [recv_oct]) of
|
||||
{ok, [{recv_oct, SameRecvOct}]} when
|
||||
ReceivedPreviously
|
||||
->
|
||||
{ok, [{recv_oct, SameRecvOct}]}
|
||||
when ReceivedPreviously ->
|
||||
%% Did not receive from socket for the 1st time.
|
||||
{ok, start_timer(State#state{received = false})};
|
||||
{ok, [{recv_oct, SameRecvOct}]} ->
|
||||
|
@ -69,11 +57,8 @@ handle(
|
|||
{error, timeout};
|
||||
{ok, [{recv_oct, NewRecvOct}]} ->
|
||||
%% Received from socket.
|
||||
{ok,
|
||||
start_timer(State#state{
|
||||
recv_oct = NewRecvOct,
|
||||
received = true
|
||||
})};
|
||||
{ok, start_timer(State#state{recv_oct = NewRecvOct,
|
||||
received = true})};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
@ -89,13 +74,10 @@ start_timer0(KeepAliveSeconds) ->
|
|||
erlang:send_after(timer_ms(KeepAliveSeconds), self(), {keepalive, check}).
|
||||
|
||||
-spec cancel_timer(state()) -> state().
|
||||
cancel_timer(#state{timer = Ref} = State) when
|
||||
is_reference(Ref)
|
||||
->
|
||||
ok = erlang:cancel_timer(Ref, [
|
||||
{async, true},
|
||||
{info, false}
|
||||
]),
|
||||
cancel_timer(#state{timer = Ref} = State)
|
||||
when is_reference(Ref) ->
|
||||
ok = erlang:cancel_timer(Ref, [{async, true},
|
||||
{info, false}]),
|
||||
State;
|
||||
cancel_timer(disabled) ->
|
||||
disabled.
|
||||
|
|
|
@ -24,148 +24,119 @@
|
|||
initial_state() -> none.
|
||||
|
||||
-spec parse(binary(), state()) ->
|
||||
{more, state()}
|
||||
| {ok, mqtt_packet(), binary()}
|
||||
| {error, any()}.
|
||||
{more, state()} |
|
||||
{ok, mqtt_packet(), binary()} |
|
||||
{error, any()}.
|
||||
parse(<<>>, none) ->
|
||||
{more, fun(Bin) -> parse(Bin, none) end};
|
||||
parse(<<MessageType:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, none) ->
|
||||
parse_remaining_len(Rest, #mqtt_packet_fixed{
|
||||
type = MessageType,
|
||||
dup = bool(Dup),
|
||||
qos = QoS,
|
||||
retain = bool(Retain)
|
||||
});
|
||||
parse(Bin, Cont) ->
|
||||
Cont(Bin).
|
||||
parse_remaining_len(Rest, #mqtt_packet_fixed{ type = MessageType,
|
||||
dup = bool(Dup),
|
||||
qos = QoS,
|
||||
retain = bool(Retain) });
|
||||
parse(Bin, Cont) -> Cont(Bin).
|
||||
|
||||
parse_remaining_len(<<>>, Fixed) ->
|
||||
{more, fun(Bin) -> parse_remaining_len(Bin, Fixed) end};
|
||||
parse_remaining_len(Rest, Fixed) ->
|
||||
parse_remaining_len(Rest, Fixed, 1, 0).
|
||||
|
||||
parse_remaining_len(_Bin, _Fixed, _Multiplier, Length) when
|
||||
Length > ?MAX_LEN
|
||||
->
|
||||
parse_remaining_len(_Bin, _Fixed, _Multiplier, Length)
|
||||
when Length > ?MAX_LEN ->
|
||||
{error, invalid_mqtt_packet_len};
|
||||
parse_remaining_len(<<>>, Fixed, Multiplier, Length) ->
|
||||
{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(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(
|
||||
Bin,
|
||||
#mqtt_packet_fixed{
|
||||
type = Type,
|
||||
qos = Qos
|
||||
} = Fixed,
|
||||
Length
|
||||
) when
|
||||
Length =< ?MAX_LEN
|
||||
->
|
||||
parse_packet(Bin, #mqtt_packet_fixed{ type = Type,
|
||||
qos = Qos } = Fixed, Length)
|
||||
when Length =< ?MAX_LEN ->
|
||||
case {Type, Bin} of
|
||||
{?CONNECT, <<PacketBin:Length/binary, Rest/binary>>} ->
|
||||
{ProtoName, Rest1} = parse_utf(PacketBin),
|
||||
<<ProtoVersion:8, Rest2/binary>> = Rest1,
|
||||
<<UsernameFlag:1, PasswordFlag:1, WillRetain:1, WillQos:2, WillFlag:1, CleanSession:1,
|
||||
_Reserved:1, KeepAlive:16/big, Rest3/binary>> = Rest2,
|
||||
{ClientId, Rest4} = parse_utf(Rest3),
|
||||
<<ProtoVersion : 8, Rest2/binary>> = Rest1,
|
||||
<<UsernameFlag : 1,
|
||||
PasswordFlag : 1,
|
||||
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),
|
||||
{WillMsg, Rest6} = parse_msg(Rest5, WillFlag),
|
||||
{UserName, Rest7} = parse_utf(Rest6, UsernameFlag),
|
||||
{PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag),
|
||||
{WillMsg, Rest6} = parse_msg(Rest5, WillFlag),
|
||||
{UserName, Rest7} = parse_utf(Rest6, UsernameFlag),
|
||||
{PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag),
|
||||
case protocol_name_approved(ProtoVersion, ProtoName) of
|
||||
true ->
|
||||
wrap(
|
||||
Fixed,
|
||||
#mqtt_packet_connect{
|
||||
proto_ver = ProtoVersion,
|
||||
will_retain = bool(WillRetain),
|
||||
will_qos = WillQos,
|
||||
will_flag = bool(WillFlag),
|
||||
clean_sess = bool(CleanSession),
|
||||
keep_alive = KeepAlive,
|
||||
client_id = ClientId,
|
||||
will_topic = WillTopic,
|
||||
will_msg = WillMsg,
|
||||
username = UserName,
|
||||
password = PasssWord
|
||||
},
|
||||
Rest
|
||||
);
|
||||
false ->
|
||||
wrap(Fixed,
|
||||
#mqtt_packet_connect{
|
||||
proto_ver = ProtoVersion,
|
||||
will_retain = bool(WillRetain),
|
||||
will_qos = WillQos,
|
||||
will_flag = bool(WillFlag),
|
||||
clean_sess = bool(CleanSession),
|
||||
keep_alive = KeepAlive,
|
||||
client_id = ClientId,
|
||||
will_topic = WillTopic,
|
||||
will_msg = WillMsg,
|
||||
username = UserName,
|
||||
password = PasssWord}, Rest);
|
||||
false ->
|
||||
{error, protocol_header_corrupt}
|
||||
end;
|
||||
{?PUBLISH, <<PacketBin:Length/binary, Rest/binary>>} ->
|
||||
{TopicName, Rest1} = parse_utf(PacketBin),
|
||||
{PacketId, Payload} =
|
||||
case Qos of
|
||||
0 ->
|
||||
{undefined, Rest1};
|
||||
_ ->
|
||||
<<M:16/big, R/binary>> = Rest1,
|
||||
{M, R}
|
||||
end,
|
||||
wrap(
|
||||
Fixed,
|
||||
#mqtt_packet_publish{
|
||||
topic_name = TopicName,
|
||||
packet_id = PacketId
|
||||
},
|
||||
Payload,
|
||||
Rest
|
||||
);
|
||||
{PacketId, Payload} = case Qos of
|
||||
0 -> {undefined, Rest1};
|
||||
_ -> <<M:16/big, R/binary>> = Rest1,
|
||||
{M, R}
|
||||
end,
|
||||
wrap(Fixed, #mqtt_packet_publish { topic_name = TopicName,
|
||||
packet_id = PacketId },
|
||||
Payload, Rest);
|
||||
{?PUBACK, <<PacketBin:Length/binary, Rest/binary>>} ->
|
||||
<<PacketId:16/big>> = PacketBin,
|
||||
wrap(Fixed, #mqtt_packet_publish{packet_id = PacketId}, Rest);
|
||||
{Subs, <<PacketBin:Length/binary, Rest/binary>>} when
|
||||
Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE
|
||||
->
|
||||
wrap(Fixed, #mqtt_packet_publish { packet_id = PacketId }, Rest);
|
||||
{Subs, <<PacketBin:Length/binary, Rest/binary>>}
|
||||
when Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE ->
|
||||
1 = Qos,
|
||||
<<PacketId:16/big, Rest1/binary>> = PacketBin,
|
||||
Topics = parse_topics(Subs, Rest1, []),
|
||||
wrap(
|
||||
Fixed,
|
||||
#mqtt_packet_subscribe{
|
||||
packet_id = PacketId,
|
||||
topic_table = Topics
|
||||
},
|
||||
Rest
|
||||
);
|
||||
{Minimal, Rest} when
|
||||
Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ
|
||||
->
|
||||
wrap(Fixed, #mqtt_packet_subscribe { packet_id = PacketId,
|
||||
topic_table = Topics }, Rest);
|
||||
{Minimal, Rest}
|
||||
when Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ ->
|
||||
Length = 0,
|
||||
wrap(Fixed, Rest);
|
||||
{_, TooShortBin} when
|
||||
byte_size(TooShortBin) < Length
|
||||
->
|
||||
{_, TooShortBin}
|
||||
when byte_size(TooShortBin) < Length ->
|
||||
{more, fun(BinMore) ->
|
||||
parse_packet(
|
||||
<<TooShortBin/binary, BinMore/binary>>,
|
||||
Fixed,
|
||||
Length
|
||||
)
|
||||
end}
|
||||
parse_packet(<<TooShortBin/binary, BinMore/binary>>,
|
||||
Fixed, Length)
|
||||
end}
|
||||
end.
|
||||
|
||||
parse_topics(_, <<>>, Topics) ->
|
||||
Topics;
|
||||
parse_topics(?SUBSCRIBE = Sub, Bin, Topics) ->
|
||||
{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) ->
|
||||
{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) ->
|
||||
{ok, #mqtt_packet{variable = Variable, fixed = Fixed, payload = Payload}, Rest}.
|
||||
{ok, #mqtt_packet { variable = Variable, fixed = Fixed, payload = Payload }, Rest}.
|
||||
wrap(Fixed, Variable, Rest) ->
|
||||
{ok, #mqtt_packet{variable = Variable, fixed = Fixed}, Rest}.
|
||||
{ok, #mqtt_packet { variable = Variable, fixed = Fixed }, Rest}.
|
||||
wrap(Fixed, Rest) ->
|
||||
{ok, #mqtt_packet{fixed = Fixed}, Rest}.
|
||||
{ok, #mqtt_packet { fixed = Fixed }, Rest}.
|
||||
|
||||
parse_utf(Bin, 0) ->
|
||||
{undefined, Bin};
|
||||
|
@ -187,109 +158,72 @@ bool(1) -> true.
|
|||
|
||||
-spec serialise(#mqtt_packet{}, ?MQTT_PROTO_V3 | ?MQTT_PROTO_V4) ->
|
||||
iodata().
|
||||
serialise(
|
||||
#mqtt_packet{
|
||||
fixed = Fixed,
|
||||
variable = Variable,
|
||||
payload = Payload
|
||||
},
|
||||
Vsn
|
||||
) ->
|
||||
serialise(#mqtt_packet{fixed = Fixed,
|
||||
variable = Variable,
|
||||
payload = Payload}, Vsn) ->
|
||||
serialise_variable(Fixed, Variable, serialise_payload(Payload), Vsn).
|
||||
|
||||
serialise_payload(undefined) ->
|
||||
<<>>;
|
||||
serialise_payload(P) when
|
||||
is_binary(P) orelse is_list(P)
|
||||
->
|
||||
serialise_payload(P)
|
||||
when is_binary(P) orelse is_list(P) ->
|
||||
P.
|
||||
|
||||
serialise_variable(
|
||||
#mqtt_packet_fixed{type = ?CONNACK} = Fixed,
|
||||
#mqtt_packet_connack{
|
||||
session_present = SessionPresent,
|
||||
return_code = ReturnCode
|
||||
},
|
||||
<<>> = PayloadBin,
|
||||
_Vsn
|
||||
) ->
|
||||
serialise_variable(#mqtt_packet_fixed { type = ?CONNACK } = Fixed,
|
||||
#mqtt_packet_connack { session_present = SessionPresent,
|
||||
return_code = ReturnCode },
|
||||
<<>> = PayloadBin, _Vsn) ->
|
||||
VariableBin = <<?RESERVED:7, (opt(SessionPresent)):1, ReturnCode:8>>,
|
||||
serialise_fixed(Fixed, VariableBin, PayloadBin);
|
||||
serialise_variable(
|
||||
#mqtt_packet_fixed{type = SubAck} = Fixed,
|
||||
#mqtt_packet_suback{
|
||||
packet_id = PacketId,
|
||||
qos_table = Qos
|
||||
},
|
||||
<<>> = _PayloadBin,
|
||||
Vsn
|
||||
) when
|
||||
SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK
|
||||
->
|
||||
|
||||
serialise_variable(#mqtt_packet_fixed { type = SubAck } = Fixed,
|
||||
#mqtt_packet_suback { packet_id = PacketId,
|
||||
qos_table = Qos },
|
||||
<<>> = _PayloadBin, Vsn)
|
||||
when SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK ->
|
||||
VariableBin = <<PacketId:16/big>>,
|
||||
QosBin =
|
||||
case Vsn of
|
||||
?MQTT_PROTO_V3 ->
|
||||
<<<<?RESERVED:6, Q:2>> || Q <- Qos>>;
|
||||
?MQTT_PROTO_V4 ->
|
||||
%% Allow error code (0x80) in the MQTT SUBACK message.
|
||||
<<<<Q:8>> || Q <- Qos>>
|
||||
end,
|
||||
QosBin = case Vsn of
|
||||
?MQTT_PROTO_V3 ->
|
||||
<< <<?RESERVED:6, Q:2>> || Q <- Qos >>;
|
||||
?MQTT_PROTO_V4 ->
|
||||
%% Allow error code (0x80) in the MQTT SUBACK message.
|
||||
<< <<Q:8>> || Q <- Qos >>
|
||||
end,
|
||||
serialise_fixed(Fixed, VariableBin, QosBin);
|
||||
serialise_variable(
|
||||
#mqtt_packet_fixed{
|
||||
type = ?PUBLISH,
|
||||
qos = Qos
|
||||
} = Fixed,
|
||||
#mqtt_packet_publish{
|
||||
topic_name = TopicName,
|
||||
packet_id = PacketId
|
||||
},
|
||||
Payload,
|
||||
_Vsn
|
||||
) ->
|
||||
|
||||
serialise_variable(#mqtt_packet_fixed { type = ?PUBLISH,
|
||||
qos = Qos } = Fixed,
|
||||
#mqtt_packet_publish { topic_name = TopicName,
|
||||
packet_id = PacketId },
|
||||
Payload, _Vsn) ->
|
||||
TopicBin = serialise_utf(TopicName),
|
||||
PacketIdBin =
|
||||
case Qos of
|
||||
0 -> <<>>;
|
||||
1 -> <<PacketId:16/big>>
|
||||
end,
|
||||
PacketIdBin = case Qos of
|
||||
0 -> <<>>;
|
||||
1 -> <<PacketId:16/big>>
|
||||
end,
|
||||
serialise_fixed(Fixed, <<TopicBin/binary, PacketIdBin/binary>>, Payload);
|
||||
serialise_variable(
|
||||
#mqtt_packet_fixed{type = ?PUBACK} = Fixed,
|
||||
#mqtt_packet_publish{packet_id = PacketId},
|
||||
PayloadBin,
|
||||
_Vsn
|
||||
) ->
|
||||
|
||||
serialise_variable(#mqtt_packet_fixed { type = ?PUBACK } = Fixed,
|
||||
#mqtt_packet_publish { packet_id = PacketId },
|
||||
PayloadBin, _Vsn) ->
|
||||
PacketIdBin = <<PacketId:16/big>>,
|
||||
serialise_fixed(Fixed, PacketIdBin, PayloadBin);
|
||||
serialise_variable(
|
||||
#mqtt_packet_fixed{} = Fixed,
|
||||
undefined,
|
||||
<<>> = _PayloadBin,
|
||||
_Vsn
|
||||
) ->
|
||||
|
||||
serialise_variable(#mqtt_packet_fixed {} = Fixed,
|
||||
undefined,
|
||||
<<>> = _PayloadBin, _Vsn) ->
|
||||
serialise_fixed(Fixed, <<>>, <<>>).
|
||||
|
||||
serialise_fixed(
|
||||
#mqtt_packet_fixed{
|
||||
type = Type,
|
||||
dup = Dup,
|
||||
qos = Qos,
|
||||
retain = Retain
|
||||
},
|
||||
VariableBin,
|
||||
Payload
|
||||
) when
|
||||
is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT
|
||||
->
|
||||
serialise_fixed(#mqtt_packet_fixed{ type = Type,
|
||||
dup = Dup,
|
||||
qos = Qos,
|
||||
retain = Retain }, VariableBin, Payload)
|
||||
when is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT ->
|
||||
Len = size(VariableBin) + iolist_size(Payload),
|
||||
true = (Len =< ?MAX_LEN),
|
||||
LenBin = serialise_len(Len),
|
||||
[
|
||||
<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1, LenBin/binary, VariableBin/binary>>,
|
||||
Payload
|
||||
].
|
||||
[<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1,
|
||||
LenBin/binary, VariableBin/binary>>, Payload].
|
||||
|
||||
serialise_utf(String) ->
|
||||
StringBin = unicode:characters_to_binary(String),
|
||||
|
@ -302,9 +236,9 @@ serialise_len(N) when N =< ?LOWBITS ->
|
|||
serialise_len(N) ->
|
||||
<<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>.
|
||||
|
||||
opt(undefined) -> ?RESERVED;
|
||||
opt(false) -> 0;
|
||||
opt(true) -> 1;
|
||||
opt(undefined) -> ?RESERVED;
|
||||
opt(false) -> 0;
|
||||
opt(true) -> 1;
|
||||
opt(X) when is_integer(X) -> X.
|
||||
|
||||
protocol_name_approved(Ver, Name) ->
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -24,50 +24,42 @@
|
|||
|
||||
%% Stateless rabbit_queue_type callbacks.
|
||||
-export([
|
||||
is_stateful/0,
|
||||
declare/2,
|
||||
delete/4,
|
||||
deliver/2,
|
||||
is_enabled/0,
|
||||
is_compatible/3,
|
||||
is_recoverable/1,
|
||||
recover/2,
|
||||
purge/1,
|
||||
policy_changed/1,
|
||||
info/2,
|
||||
stat/1,
|
||||
capabilities/0,
|
||||
notify_decorators/1
|
||||
]).
|
||||
is_stateful/0,
|
||||
declare/2,
|
||||
delete/4,
|
||||
deliver/2,
|
||||
is_enabled/0,
|
||||
is_compatible/3,
|
||||
is_recoverable/1,
|
||||
recover/2,
|
||||
purge/1,
|
||||
policy_changed/1,
|
||||
info/2,
|
||||
stat/1,
|
||||
capabilities/0,
|
||||
notify_decorators/1
|
||||
]).
|
||||
|
||||
%% Stateful rabbit_queue_type callbacks are unsupported by this queue type.
|
||||
-define(STATEFUL_CALLBACKS, [
|
||||
init/1,
|
||||
close/1,
|
||||
update/2,
|
||||
consume/3,
|
||||
cancel/5,
|
||||
handle_event/3,
|
||||
settle/5,
|
||||
credit/5,
|
||||
dequeue/5,
|
||||
state_info/1
|
||||
]).
|
||||
-define(STATEFUL_CALLBACKS,
|
||||
[
|
||||
init/1,
|
||||
close/1,
|
||||
update/2,
|
||||
consume/3,
|
||||
cancel/5,
|
||||
handle_event/3,
|
||||
settle/5,
|
||||
credit/5,
|
||||
dequeue/5,
|
||||
state_info/1
|
||||
]).
|
||||
-export(?STATEFUL_CALLBACKS).
|
||||
-dialyzer({nowarn_function, ?STATEFUL_CALLBACKS}).
|
||||
-define(UNSUPPORTED(Args), erlang:error(unsupported, Args)).
|
||||
|
||||
-define(INFO_KEYS, [
|
||||
type,
|
||||
name,
|
||||
durable,
|
||||
auto_delete,
|
||||
arguments,
|
||||
pid,
|
||||
owner_pid,
|
||||
state,
|
||||
messages
|
||||
]).
|
||||
-define(INFO_KEYS, [type, name, durable, auto_delete, arguments,
|
||||
pid, owner_pid, state, messages]).
|
||||
|
||||
-spec is_stateful() ->
|
||||
boolean().
|
||||
|
@ -75,8 +67,8 @@ is_stateful() ->
|
|||
false.
|
||||
|
||||
-spec declare(amqqueue:amqqueue(), node()) ->
|
||||
{'new' | 'existing' | 'owner_died', amqqueue:amqqueue()}
|
||||
| {'absent', amqqueue:amqqueue(), rabbit_amqqueue:absent_reason()}.
|
||||
{'new' | 'existing' | 'owner_died', amqqueue:amqqueue()} |
|
||||
{'absent', amqqueue:amqqueue(), rabbit_amqqueue:absent_reason()}.
|
||||
declare(Q0, _Node) ->
|
||||
%% The queue gets persisted such that routing to this
|
||||
%% queue (via the topic exchange) works as usual.
|
||||
|
@ -84,29 +76,23 @@ declare(Q0, _Node) ->
|
|||
{created, Q} ->
|
||||
Opts = amqqueue:get_options(Q),
|
||||
ActingUser = maps:get(user, Opts, ?UNKNOWN_USER),
|
||||
rabbit_event:notify(
|
||||
queue_created,
|
||||
[
|
||||
{name, amqqueue:get_name(Q0)},
|
||||
{durable, true},
|
||||
{auto_delete, false},
|
||||
{exclusive, true},
|
||||
{type, amqqueue:get_type(Q0)},
|
||||
{arguments, amqqueue:get_arguments(Q0)},
|
||||
{user_who_performed_action, ActingUser}
|
||||
]
|
||||
),
|
||||
rabbit_event:notify(queue_created,
|
||||
[{name, amqqueue:get_name(Q0)},
|
||||
{durable, true},
|
||||
{auto_delete, false},
|
||||
{exclusive, true},
|
||||
{type, amqqueue:get_type(Q0)},
|
||||
{arguments, amqqueue:get_arguments(Q0)},
|
||||
{user_who_performed_action, ActingUser}]),
|
||||
{new, Q};
|
||||
Other ->
|
||||
Other
|
||||
end.
|
||||
|
||||
-spec delete(
|
||||
amqqueue:amqqueue(),
|
||||
boolean(),
|
||||
boolean(),
|
||||
rabbit_types:username()
|
||||
) ->
|
||||
-spec delete(amqqueue:amqqueue(),
|
||||
boolean(),
|
||||
boolean(),
|
||||
rabbit_types:username()) ->
|
||||
rabbit_types:ok(non_neg_integer()).
|
||||
delete(Q, _IfUnused, _IfEmpty, ActingUser) ->
|
||||
QName = amqqueue:get_name(Q),
|
||||
|
@ -116,41 +102,35 @@ delete(Q, _IfUnused, _IfEmpty, ActingUser) ->
|
|||
|
||||
-spec deliver([{amqqueue:amqqueue(), stateless}], Delivery :: term()) ->
|
||||
{[], rabbit_queue_type:actions()}.
|
||||
deliver(Qs, #delivery{
|
||||
message = BasicMessage,
|
||||
confirm = Confirm,
|
||||
msg_seq_no = SeqNo
|
||||
}) ->
|
||||
Msg =
|
||||
{queue_event, ?MODULE,
|
||||
{?MODULE, _QPid = none, _QMsgId = none, _Redelivered = false, BasicMessage}},
|
||||
deliver(Qs, #delivery{message = BasicMessage,
|
||||
confirm = Confirm,
|
||||
msg_seq_no = SeqNo}) ->
|
||||
Msg = {queue_event, ?MODULE,
|
||||
{?MODULE, _QPid = none, _QMsgId = none, _Redelivered = false, BasicMessage}},
|
||||
{Pids, Actions} =
|
||||
case Confirm of
|
||||
false ->
|
||||
Pids0 = lists:map(fun({Q, stateless}) -> amqqueue:get_pid(Q) end, Qs),
|
||||
{Pids0, []};
|
||||
true ->
|
||||
%% We confirm the message directly here in the queue client.
|
||||
%% 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
|
||||
%% process and MQTT subscriber, and we know that the MQTT subscriber wants to receive
|
||||
%% this message at most once, we confirm here directly.
|
||||
%% Benefits:
|
||||
%% 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.
|
||||
%% 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.
|
||||
%% 3. Reduced network traffic across RabbitMQ nodes.
|
||||
%% 4. Lower latency of sending publisher confirmation back to the publishing client.
|
||||
SeqNos = [SeqNo],
|
||||
lists:mapfoldl(
|
||||
fun({Q, stateless}, Actions) ->
|
||||
{amqqueue:get_pid(Q), [{settled, amqqueue:get_name(Q), SeqNos} | Actions]}
|
||||
end,
|
||||
[],
|
||||
Qs
|
||||
)
|
||||
end,
|
||||
case Confirm of
|
||||
false ->
|
||||
Pids0 = lists:map(fun({Q, stateless}) -> amqqueue:get_pid(Q) end, Qs),
|
||||
{Pids0, []};
|
||||
true ->
|
||||
%% We confirm the message directly here in the queue client.
|
||||
%% 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
|
||||
%% process and MQTT subscriber, and we know that the MQTT subscriber wants to receive
|
||||
%% this message at most once, we confirm here directly.
|
||||
%% Benefits:
|
||||
%% 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.
|
||||
%% 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.
|
||||
%% 3. Reduced network traffic across RabbitMQ nodes.
|
||||
%% 4. Lower latency of sending publisher confirmation back to the publishing client.
|
||||
SeqNos = [SeqNo],
|
||||
lists:mapfoldl(fun({Q, stateless}, Actions) ->
|
||||
{amqqueue:get_pid(Q),
|
||||
[{settled, amqqueue:get_name(Q), SeqNos} | Actions]}
|
||||
end, [], Qs)
|
||||
end,
|
||||
delegate:invoke_no_result(Pids, {gen_server, cast, [Msg]}),
|
||||
{[], Actions}.
|
||||
|
||||
|
@ -172,8 +152,8 @@ is_recoverable(Q) ->
|
|||
Pid = amqqueue:get_pid(Q),
|
||||
OwnerPid = amqqueue:get_exclusive_owner(Q),
|
||||
node() =:= node(Pid) andalso
|
||||
Pid =:= OwnerPid andalso
|
||||
not is_process_alive(Pid).
|
||||
Pid =:= OwnerPid andalso
|
||||
not is_process_alive(Pid).
|
||||
|
||||
%% We (mis)use the recover callback to clean up our exclusive queues
|
||||
%% which otherwise do not get cleaned up after a node crash.
|
||||
|
@ -181,24 +161,20 @@ is_recoverable(Q) ->
|
|||
{Recovered :: [amqqueue:amqqueue()], Failed :: [amqqueue:amqqueue()]}.
|
||||
recover(_VHost, Queues) ->
|
||||
lists:foreach(
|
||||
fun(Q) ->
|
||||
%% sanity check
|
||||
true = is_recoverable(Q),
|
||||
QName = amqqueue:get_name(Q),
|
||||
log_delete(QName, amqqueue:get_exclusive_owner(Q)),
|
||||
rabbit_amqqueue:internal_delete(QName, ?INTERNAL_USER)
|
||||
end,
|
||||
Queues
|
||||
),
|
||||
fun(Q) ->
|
||||
%% sanity check
|
||||
true = is_recoverable(Q),
|
||||
QName = amqqueue:get_name(Q),
|
||||
log_delete(QName, amqqueue:get_exclusive_owner(Q)),
|
||||
rabbit_amqqueue:internal_delete(QName, ?INTERNAL_USER)
|
||||
end, Queues),
|
||||
%% We mark the queue recovery as failed because these queues are not really
|
||||
%% recovered, but deleted.
|
||||
{[], Queues}.
|
||||
|
||||
log_delete(QName, ConPid) ->
|
||||
rabbit_log_queue:debug(
|
||||
"Deleting ~s of type ~s because its declaring connection ~tp was closed",
|
||||
[rabbit_misc:rs(QName), ?MODULE, ConPid]
|
||||
).
|
||||
rabbit_log_queue:debug("Deleting ~s of type ~s because its declaring connection ~tp was closed",
|
||||
[rabbit_misc:rs(QName), ?MODULE, ConPid]).
|
||||
|
||||
-spec purge(amqqueue:amqqueue()) ->
|
||||
{ok, non_neg_integer()}.
|
||||
|
@ -227,13 +203,11 @@ capabilities() ->
|
|||
|
||||
-spec info(amqqueue:amqqueue(), all_keys | rabbit_types:info_keys()) ->
|
||||
rabbit_types:infos().
|
||||
info(Q, all_keys) when
|
||||
?is_amqqueue(Q)
|
||||
->
|
||||
info(Q, all_keys)
|
||||
when ?is_amqqueue(Q) ->
|
||||
info(Q, ?INFO_KEYS);
|
||||
info(Q, Items) when
|
||||
?is_amqqueue(Q)
|
||||
->
|
||||
info(Q, Items)
|
||||
when ?is_amqqueue(Q) ->
|
||||
[{Item, i(Item, Q)} || Item <- Items].
|
||||
|
||||
i(type, _) ->
|
||||
|
@ -275,26 +249,26 @@ init(A1) ->
|
|||
close(A1) ->
|
||||
?UNSUPPORTED([A1]).
|
||||
|
||||
update(A1, A2) ->
|
||||
?UNSUPPORTED([A1, A2]).
|
||||
update(A1,A2) ->
|
||||
?UNSUPPORTED([A1,A2]).
|
||||
|
||||
consume(A1, A2, A3) ->
|
||||
?UNSUPPORTED([A1, A2, A3]).
|
||||
consume(A1,A2,A3) ->
|
||||
?UNSUPPORTED([A1,A2,A3]).
|
||||
|
||||
cancel(A1, A2, A3, A4, A5) ->
|
||||
?UNSUPPORTED([A1, A2, A3, A4, A5]).
|
||||
cancel(A1,A2,A3,A4,A5) ->
|
||||
?UNSUPPORTED([A1,A2,A3,A4,A5]).
|
||||
|
||||
handle_event(A1, A2, A3) ->
|
||||
?UNSUPPORTED([A1, A2, A3]).
|
||||
handle_event(A1,A2,A3) ->
|
||||
?UNSUPPORTED([A1,A2,A3]).
|
||||
|
||||
settle(A1, A2, A3, A4, A5) ->
|
||||
?UNSUPPORTED([A1, A2, A3, A4, A5]).
|
||||
settle(A1,A2,A3,A4,A5) ->
|
||||
?UNSUPPORTED([A1,A2,A3,A4,A5]).
|
||||
|
||||
credit(A1, A2, A3, A4, A5) ->
|
||||
?UNSUPPORTED([A1, A2, A3, A4, A5]).
|
||||
credit(A1,A2,A3,A4,A5) ->
|
||||
?UNSUPPORTED([A1,A2,A3,A4,A5]).
|
||||
|
||||
dequeue(A1, A2, A3, A4, A5) ->
|
||||
?UNSUPPORTED([A1, A2, A3, A4, A5]).
|
||||
dequeue(A1,A2,A3,A4,A5) ->
|
||||
?UNSUPPORTED([A1,A2,A3,A4,A5]).
|
||||
|
||||
state_info(A1) ->
|
||||
?UNSUPPORTED([A1]).
|
||||
|
|
|
@ -14,20 +14,11 @@
|
|||
-include_lib("rabbit_common/include/logging.hrl").
|
||||
|
||||
-export([start_link/3]).
|
||||
-export([
|
||||
init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
code_change/3,
|
||||
terminate/2,
|
||||
format_status/1
|
||||
]).
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
code_change/3, terminate/2, format_status/1]).
|
||||
|
||||
-export([
|
||||
conserve_resources/3,
|
||||
close_connection/2
|
||||
]).
|
||||
-export([conserve_resources/3,
|
||||
close_connection/2]).
|
||||
|
||||
-export([info/2]).
|
||||
|
||||
|
@ -38,22 +29,22 @@
|
|||
-define(HIBERNATE_AFTER, 1000).
|
||||
-define(PROTO_FAMILY, 'MQTT').
|
||||
|
||||
-record(state, {
|
||||
socket :: rabbit_net:socket(),
|
||||
proxy_socket :: option({rabbit_proxy_socket, any(), any()}),
|
||||
await_recv :: boolean(),
|
||||
deferred_recv :: option(binary()),
|
||||
parse_state :: rabbit_mqtt_packet:state(),
|
||||
proc_state :: rabbit_mqtt_processor:state(),
|
||||
connection_state :: running | blocked,
|
||||
conserve :: boolean(),
|
||||
stats_timer :: option(rabbit_event:state()),
|
||||
keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
|
||||
conn_name :: binary(),
|
||||
received_connect_packet :: boolean()
|
||||
}).
|
||||
-record(state,
|
||||
{socket :: rabbit_net:socket(),
|
||||
proxy_socket :: option({rabbit_proxy_socket, any(), any()}),
|
||||
await_recv :: boolean(),
|
||||
deferred_recv :: option(binary()),
|
||||
parse_state :: rabbit_mqtt_packet:state(),
|
||||
proc_state :: rabbit_mqtt_processor:state(),
|
||||
connection_state :: running | blocked,
|
||||
conserve :: boolean(),
|
||||
stats_timer :: option(rabbit_event:state()),
|
||||
keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
|
||||
conn_name :: binary(),
|
||||
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]),
|
||||
{ok, Pid}.
|
||||
|
||||
-spec conserve_resources(
|
||||
pid(),
|
||||
rabbit_alarm:resource_alarm_source(),
|
||||
rabbit_alarm:resource_alert()
|
||||
) -> ok.
|
||||
-spec conserve_resources(pid(),
|
||||
rabbit_alarm:resource_alarm_source(),
|
||||
rabbit_alarm:resource_alert()) -> ok.
|
||||
conserve_resources(Pid, _, {_, Conserve, _}) ->
|
||||
Pid ! {conserve_resources, Conserve},
|
||||
ok.
|
||||
|
@ -84,10 +73,8 @@ close_connection(Pid, Reason) ->
|
|||
init(Ref) ->
|
||||
process_flag(trap_exit, true),
|
||||
logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [mqtt]}),
|
||||
{ok, Sock} = rabbit_networking:handshake(
|
||||
Ref,
|
||||
application:get_env(?APP_NAME, proxy_protocol, false)
|
||||
),
|
||||
{ok, Sock} = rabbit_networking:handshake(Ref,
|
||||
application:get_env(?APP_NAME, proxy_protocol, false)),
|
||||
RealSocket = rabbit_net:unwrap_socket(Sock),
|
||||
case rabbit_net:connection_string(Sock, inbound) of
|
||||
{ok, ConnStr} ->
|
||||
|
@ -97,17 +84,15 @@ init(Ref) ->
|
|||
LoginTimeout = application:get_env(?APP_NAME, login_timeout, 10_000),
|
||||
erlang:send_after(LoginTimeout, self(), login_timeout),
|
||||
ProcessorState = rabbit_mqtt_processor:initial_state(RealSocket, ConnName),
|
||||
State0 = #state{
|
||||
socket = RealSocket,
|
||||
proxy_socket = rabbit_net:maybe_get_proxy_socket(Sock),
|
||||
conn_name = ConnName,
|
||||
await_recv = false,
|
||||
connection_state = running,
|
||||
received_connect_packet = false,
|
||||
conserve = false,
|
||||
parse_state = rabbit_mqtt_packet:initial_state(),
|
||||
proc_state = ProcessorState
|
||||
},
|
||||
State0 = #state{socket = RealSocket,
|
||||
proxy_socket = rabbit_net:maybe_get_proxy_socket(Sock),
|
||||
conn_name = ConnName,
|
||||
await_recv = false,
|
||||
connection_state = running,
|
||||
received_connect_packet = false,
|
||||
conserve = false,
|
||||
parse_state = rabbit_mqtt_packet:initial_state(),
|
||||
proc_state = ProcessorState},
|
||||
State1 = control_throttle(State0),
|
||||
State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
|
||||
gen_server:enter_loop(?MODULE, [], State);
|
||||
|
@ -123,67 +108,51 @@ init(Ref) ->
|
|||
|
||||
handle_call({info, InfoItems}, _From, State) ->
|
||||
{reply, infos(InfoItems, State), State, ?HIBERNATE_AFTER};
|
||||
|
||||
handle_call(Msg, From, State) ->
|
||||
{stop, {mqtt_unexpected_call, Msg, From}, State}.
|
||||
|
||||
handle_cast(
|
||||
duplicate_id,
|
||||
State = #state{
|
||||
proc_state = PState,
|
||||
conn_name = ConnName
|
||||
}
|
||||
) ->
|
||||
?LOG_WARNING(
|
||||
"MQTT disconnecting client ~tp with duplicate id '~ts'",
|
||||
[ConnName, rabbit_mqtt_processor:info(client_id, PState)]
|
||||
),
|
||||
handle_cast(duplicate_id,
|
||||
State = #state{ proc_state = PState,
|
||||
conn_name = ConnName }) ->
|
||||
?LOG_WARNING("MQTT disconnecting client ~tp with duplicate id '~ts'",
|
||||
[ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
|
||||
{stop, {shutdown, duplicate_id}, State};
|
||||
handle_cast(
|
||||
decommission_node,
|
||||
State = #state{
|
||||
proc_state = PState,
|
||||
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)]
|
||||
),
|
||||
|
||||
handle_cast(decommission_node,
|
||||
State = #state{ proc_state = PState,
|
||||
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)]),
|
||||
{stop, {shutdown, decommission_node}, State};
|
||||
handle_cast(
|
||||
{close_connection, Reason},
|
||||
State = #state{conn_name = ConnName, proc_state = PState}
|
||||
) ->
|
||||
?LOG_WARNING(
|
||||
"MQTT disconnecting client ~tp with client ID '~ts', reason: ~ts",
|
||||
[ConnName, rabbit_mqtt_processor:info(client_id, PState), Reason]
|
||||
),
|
||||
|
||||
handle_cast({close_connection, Reason},
|
||||
State = #state{conn_name = ConnName, proc_state = PState}) ->
|
||||
?LOG_WARNING("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};
|
||||
handle_cast(
|
||||
QueueEvent = {queue_event, _, _},
|
||||
State = #state{proc_state = PState0}
|
||||
) ->
|
||||
|
||||
handle_cast(QueueEvent = {queue_event, _, _},
|
||||
State = #state{proc_state = PState0}) ->
|
||||
case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
|
||||
{ok, PState} ->
|
||||
maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
|
||||
{error, Reason, PState} ->
|
||||
{stop, Reason, pstate(State, PState)}
|
||||
end;
|
||||
|
||||
handle_cast({force_event_refresh, Ref}, State0) ->
|
||||
Infos = infos(?CREATION_EVENT_KEYS, State0),
|
||||
rabbit_event:notify(connection_created, Infos, Ref),
|
||||
State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
|
||||
{noreply, State, ?HIBERNATE_AFTER};
|
||||
handle_cast(
|
||||
refresh_config,
|
||||
State = #state{
|
||||
proc_state = PState0,
|
||||
conn_name = ConnName
|
||||
}
|
||||
) ->
|
||||
|
||||
handle_cast(refresh_config, State = #state{proc_state = PState0,
|
||||
conn_name = ConnName}) ->
|
||||
PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
|
||||
{noreply, pstate(State, PState), ?HIBERNATE_AFTER};
|
||||
|
||||
handle_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_event:notify(connection_created, Infos),
|
||||
{noreply, State, ?HIBERNATE_AFTER};
|
||||
|
||||
handle_info(timeout, State) ->
|
||||
rabbit_mqtt_processor:handle_pre_hibernate(),
|
||||
{noreply, State, hibernate};
|
||||
|
||||
handle_info({'EXIT', _Conn, Reason}, State) ->
|
||||
{stop, {connection_died, Reason}, State};
|
||||
handle_info(
|
||||
{Tag, Sock, Data},
|
||||
State = #state{socket = Sock, connection_state = blocked}
|
||||
) when
|
||||
Tag =:= tcp; Tag =:= ssl
|
||||
->
|
||||
{noreply, State#state{deferred_recv = Data}, ?HIBERNATE_AFTER};
|
||||
handle_info(
|
||||
{Tag, Sock, Data},
|
||||
State = #state{socket = Sock, connection_state = running}
|
||||
) when
|
||||
Tag =:= tcp; Tag =:= ssl
|
||||
->
|
||||
|
||||
handle_info({Tag, Sock, Data},
|
||||
State = #state{ socket = Sock, connection_state = blocked })
|
||||
when Tag =:= tcp; Tag =:= ssl ->
|
||||
{noreply, State#state{ deferred_recv = Data }, ?HIBERNATE_AFTER};
|
||||
|
||||
handle_info({Tag, Sock, Data},
|
||||
State = #state{ socket = Sock, connection_state = running })
|
||||
when Tag =:= tcp; Tag =:= ssl ->
|
||||
process_received_bytes(
|
||||
Data, control_throttle(State#state{await_recv = false})
|
||||
);
|
||||
handle_info({Tag, Sock}, State = #state{socket = Sock}) when
|
||||
Tag =:= tcp_closed; Tag =:= ssl_closed
|
||||
->
|
||||
Data, control_throttle(State #state{ await_recv = false }));
|
||||
|
||||
handle_info({Tag, Sock}, State = #state{socket = Sock})
|
||||
when Tag =:= tcp_closed; Tag =:= ssl_closed ->
|
||||
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);
|
||||
|
||||
handle_info({inet_reply, Sock, ok}, State = #state{socket = Sock}) ->
|
||||
{noreply, State, ?HIBERNATE_AFTER};
|
||||
|
||||
handle_info({inet_reply, Sock, {error, Reason}}, State = #state{socket = Sock}) ->
|
||||
network_error(Reason, State);
|
||||
|
||||
handle_info({conserve_resources, Conserve}, State) ->
|
||||
maybe_process_deferred_recv(
|
||||
control_throttle(State#state{conserve = Conserve})
|
||||
);
|
||||
control_throttle(State #state{ conserve = Conserve }));
|
||||
|
||||
handle_info({bump_credit, Msg}, State) ->
|
||||
credit_flow:handle_bump_msg(Msg),
|
||||
maybe_process_deferred_recv(control_throttle(State));
|
||||
handle_info(
|
||||
{keepalive, Req},
|
||||
State = #state{
|
||||
keepalive = KState0,
|
||||
conn_name = ConnName
|
||||
}
|
||||
) ->
|
||||
|
||||
handle_info({keepalive, Req}, State = #state{keepalive = KState0,
|
||||
conn_name = ConnName}) ->
|
||||
case rabbit_mqtt_keepalive:handle(Req, KState0) of
|
||||
{ok, KState} ->
|
||||
{noreply, State#state{keepalive = KState}, ?HIBERNATE_AFTER};
|
||||
|
@ -248,6 +213,7 @@ handle_info(
|
|||
{error, Reason} ->
|
||||
{stop, Reason, State}
|
||||
end;
|
||||
|
||||
handle_info(login_timeout, State = #state{received_connect_packet = true}) ->
|
||||
{noreply, State, ?HIBERNATE_AFTER};
|
||||
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.
|
||||
?LOG_ERROR("closing MQTT connection ~tp (login timeout)", [ConnName]),
|
||||
{stop, {shutdown, login_timeout}, State};
|
||||
|
||||
handle_info(emit_stats, State) ->
|
||||
{noreply, emit_stats(State), ?HIBERNATE_AFTER};
|
||||
handle_info(
|
||||
{ra_event, _From, Evt},
|
||||
#state{proc_state = PState0} = State
|
||||
) ->
|
||||
|
||||
handle_info({ra_event, _From, Evt},
|
||||
#state{proc_state = PState0} = State) ->
|
||||
%% handle applied event to ensure registration command actually got applied
|
||||
%% handle not_leader notification in case we send the command to a non-leader
|
||||
PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
|
||||
{noreply, pstate(State, PState), ?HIBERNATE_AFTER};
|
||||
handle_info(
|
||||
{{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
|
||||
#state{proc_state = PState0} = State
|
||||
) ->
|
||||
|
||||
handle_info({{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
|
||||
#state{proc_state = PState0} = State) ->
|
||||
case rabbit_mqtt_processor:handle_down(Evt, PState0) of
|
||||
{ok, PState} ->
|
||||
maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
|
||||
{error, Reason} ->
|
||||
{stop, {shutdown, Reason, State}}
|
||||
end;
|
||||
|
||||
handle_info({'DOWN', _MRef, process, QPid, _Reason}, State) ->
|
||||
rabbit_amqqueue_common:notify_sent_queue_down(QPid),
|
||||
{noreply, State, ?HIBERNATE_AFTER};
|
||||
|
||||
handle_info({shutdown, Explanation} = Reason, State = #state{conn_name = ConnName}) ->
|
||||
%% rabbitmq_management plugin requests to close connection.
|
||||
?LOG_INFO("MQTT closing connection ~tp: ~p", [ConnName, Explanation]),
|
||||
{stop, Reason, State};
|
||||
|
||||
handle_info(Msg, State) ->
|
||||
{stop, {mqtt_unexpected_msg, Msg}, State}.
|
||||
|
||||
terminate(Reason, State = #state{}) ->
|
||||
terminate(Reason, {true, State});
|
||||
terminate(
|
||||
Reason,
|
||||
{SendWill,
|
||||
State = #state{
|
||||
conn_name = ConnName,
|
||||
keepalive = KState0,
|
||||
proc_state = PState
|
||||
}}
|
||||
) ->
|
||||
terminate(Reason, {SendWill, State = #state{conn_name = ConnName,
|
||||
keepalive = KState0,
|
||||
proc_state = PState}}) ->
|
||||
KState = rabbit_mqtt_keepalive:cancel_timer(KState0),
|
||||
maybe_emit_stats(State#state{keepalive = KState}),
|
||||
_ = 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_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_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_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_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_terminate({network_error, {ssl_upgrade_error, Reason}, ConnName}, _State) ->
|
||||
?LOG_ERROR("MQTT detected TLS upgrade error on ~s: ~p", [ConnName, Reason]);
|
||||
|
||||
log_terminate({network_error, Reason, ConnName}, _State) ->
|
||||
?LOG_ERROR("MQTT detected network error on ~s: ~p", [ConnName, Reason]);
|
||||
|
||||
log_terminate({network_error, Reason}, _State) ->
|
||||
?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]),
|
||||
ok;
|
||||
|
||||
log_terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
|
@ -336,80 +309,54 @@ code_change(_OldVsn, State, _Extra) ->
|
|||
log_tls_alert(handshake_failure, ConnName) ->
|
||||
?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: handshake failure", [ConnName]);
|
||||
log_tls_alert(unknown_ca, ConnName) ->
|
||||
?LOG_ERROR(
|
||||
"MQTT detected TLS certificate verification error on ~ts: alert 'unknown CA'",
|
||||
[ConnName]
|
||||
);
|
||||
?LOG_ERROR("MQTT detected TLS certificate verification error on ~ts: alert 'unknown CA'",
|
||||
[ConnName]);
|
||||
log_tls_alert(Alert, ConnName) ->
|
||||
?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: alert ~ts", [ConnName, Alert]).
|
||||
|
||||
process_received_bytes(
|
||||
<<>>,
|
||||
State = #state{
|
||||
received_connect_packet = false,
|
||||
proc_state = PState,
|
||||
conn_name = ConnName
|
||||
}
|
||||
) ->
|
||||
?LOG_INFO(
|
||||
"Accepted MQTT connection ~p (~s, client ID: ~s)",
|
||||
[self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]
|
||||
),
|
||||
process_received_bytes(<<>>, State = #state{received_connect_packet = false,
|
||||
proc_state = 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};
|
||||
process_received_bytes(<<>>, State) ->
|
||||
{noreply, ensure_stats_timer(State), ?HIBERNATE_AFTER};
|
||||
process_received_bytes(
|
||||
Bytes,
|
||||
State = #state{
|
||||
parse_state = ParseState,
|
||||
proc_state = ProcState,
|
||||
conn_name = ConnName
|
||||
}
|
||||
) ->
|
||||
process_received_bytes(Bytes,
|
||||
State = #state{ parse_state = ParseState,
|
||||
proc_state = ProcState,
|
||||
conn_name = ConnName }) ->
|
||||
case parse(Bytes, ParseState) of
|
||||
{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} ->
|
||||
case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
|
||||
{ok, ProcState1} ->
|
||||
process_received_bytes(
|
||||
Rest,
|
||||
State#state{
|
||||
parse_state = rabbit_mqtt_packet:initial_state(),
|
||||
proc_state = ProcState1
|
||||
}
|
||||
);
|
||||
Rest,
|
||||
State #state{parse_state = rabbit_mqtt_packet:initial_state(),
|
||||
proc_state = ProcState1});
|
||||
%% PUBLISH and more
|
||||
{error, unauthorized = Reason, ProcState1} ->
|
||||
?LOG_ERROR("MQTT connection ~ts is closing due to an authorization failure", [
|
||||
ConnName
|
||||
]),
|
||||
?LOG_ERROR("MQTT connection ~ts is closing due to an authorization failure", [ConnName]),
|
||||
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
||||
%% CONNECT packets only
|
||||
{error, unauthenticated = Reason, ProcState1} ->
|
||||
?LOG_ERROR("MQTT connection ~ts is closing due to an authentication failure", [
|
||||
ConnName
|
||||
]),
|
||||
?LOG_ERROR("MQTT connection ~ts is closing due to an authentication failure", [ConnName]),
|
||||
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
||||
%% CONNECT packets only
|
||||
{error, invalid_client_id = Reason, ProcState1} ->
|
||||
?LOG_ERROR("MQTT cannot accept connection ~ts: client uses an invalid ID", [
|
||||
ConnName
|
||||
]),
|
||||
?LOG_ERROR("MQTT cannot accept connection ~ts: client uses an invalid ID", [ConnName]),
|
||||
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
||||
%% CONNECT packets only
|
||||
{error, unsupported_protocol_version = Reason, ProcState1} ->
|
||||
?LOG_ERROR(
|
||||
"MQTT cannot accept connection ~ts: incompatible protocol version", [
|
||||
ConnName
|
||||
]
|
||||
),
|
||||
?LOG_ERROR("MQTT cannot accept connection ~ts: incompatible protocol version", [ConnName]),
|
||||
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
||||
{error, unavailable = Reason, ProcState1} ->
|
||||
?LOG_ERROR(
|
||||
"MQTT cannot accept connection ~ts due to an internal error or unavailable component",
|
||||
[ConnName]
|
||||
),
|
||||
?LOG_ERROR("MQTT cannot accept connection ~ts due to an internal error or unavailable component",
|
||||
[ConnName]),
|
||||
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
||||
{error, Reason, ProcState1} ->
|
||||
?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)}}
|
||||
end;
|
||||
{error, {cannot_parse, Reason, Stacktrace}} ->
|
||||
?LOG_ERROR(
|
||||
"MQTT cannot parse a packet on connection '~ts', reason: ~tp, "
|
||||
"stacktrace: ~tp, payload (first 100 bytes): ~tp",
|
||||
[ConnName, Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Bytes, 100)]
|
||||
),
|
||||
?LOG_ERROR("MQTT cannot parse a packet on connection '~ts', reason: ~tp, "
|
||||
"stacktrace: ~tp, payload (first 100 bytes): ~tp",
|
||||
[ConnName, Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Bytes, 100)]),
|
||||
{stop, {shutdown, Reason}, State};
|
||||
{error, Error} ->
|
||||
?LOG_ERROR("MQTT detected a framing error on connection ~ts: ~tp", [ConnName, Error]),
|
||||
|
@ -430,8 +375,8 @@ process_received_bytes(
|
|||
end.
|
||||
|
||||
-spec pstate(state(), rabbit_mqtt_processor:state()) -> state().
|
||||
pstate(State = #state{}, PState) ->
|
||||
State#state{proc_state = PState}.
|
||||
pstate(State = #state {}, PState) ->
|
||||
State #state{ proc_state = PState }.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
parse(Bytes, ParseState) ->
|
||||
|
@ -442,13 +387,9 @@ parse(Bytes, ParseState) ->
|
|||
{error, {cannot_parse, Reason, Stacktrace}}
|
||||
end.
|
||||
|
||||
network_error(
|
||||
closed,
|
||||
State = #state{
|
||||
conn_name = ConnName,
|
||||
received_connect_packet = Connected
|
||||
}
|
||||
) ->
|
||||
network_error(closed,
|
||||
State = #state{conn_name = ConnName,
|
||||
received_connect_packet = Connected}) ->
|
||||
Fmt = "MQTT connection ~p will terminate because peer closed TCP connection",
|
||||
Args = [ConnName],
|
||||
case Connected of
|
||||
|
@ -456,77 +397,62 @@ network_error(
|
|||
false -> ?LOG_DEBUG(Fmt, Args)
|
||||
end,
|
||||
{stop, {shutdown, conn_closed}, State};
|
||||
network_error(
|
||||
Reason,
|
||||
State = #state{conn_name = ConnName}
|
||||
) ->
|
||||
|
||||
network_error(Reason,
|
||||
State = #state{conn_name = ConnName}) ->
|
||||
?LOG_INFO("MQTT detected network error for ~p: ~p", [ConnName, Reason]),
|
||||
{stop, {shutdown, conn_closed}, State}.
|
||||
|
||||
run_socket(State = #state{connection_state = blocked}) ->
|
||||
run_socket(State = #state{ connection_state = blocked }) ->
|
||||
State;
|
||||
run_socket(State = #state{deferred_recv = Data}) when Data =/= undefined ->
|
||||
run_socket(State = #state{ deferred_recv = Data }) when Data =/= undefined ->
|
||||
State;
|
||||
run_socket(State = #state{await_recv = true}) ->
|
||||
run_socket(State = #state{ await_recv = true }) ->
|
||||
State;
|
||||
run_socket(State = #state{socket = Sock}) ->
|
||||
run_socket(State = #state{ socket = Sock }) ->
|
||||
ok = rabbit_net:setopts(Sock, [{active, once}]),
|
||||
State#state{await_recv = true}.
|
||||
State#state{ await_recv = true }.
|
||||
|
||||
control_throttle(
|
||||
State = #state{
|
||||
connection_state = ConnState,
|
||||
conserve = Conserve,
|
||||
received_connect_packet = Connected,
|
||||
proc_state = PState,
|
||||
keepalive = KState
|
||||
}
|
||||
) ->
|
||||
control_throttle(State = #state{connection_state = ConnState,
|
||||
conserve = Conserve,
|
||||
received_connect_packet = Connected,
|
||||
proc_state = PState,
|
||||
keepalive = KState
|
||||
}) ->
|
||||
Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
|
||||
case {ConnState, Throttle} of
|
||||
{running, true} ->
|
||||
State#state{
|
||||
connection_state = blocked,
|
||||
keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)
|
||||
};
|
||||
State#state{connection_state = blocked,
|
||||
keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)};
|
||||
{blocked, false} ->
|
||||
run_socket(State#state{
|
||||
connection_state = running,
|
||||
keepalive = rabbit_mqtt_keepalive:start_timer(KState)
|
||||
});
|
||||
run_socket(State#state{connection_state = running,
|
||||
keepalive = rabbit_mqtt_keepalive:start_timer(KState)});
|
||||
{_, _} ->
|
||||
run_socket(State)
|
||||
end.
|
||||
|
||||
maybe_process_deferred_recv(State = #state{deferred_recv = undefined}) ->
|
||||
maybe_process_deferred_recv(State = #state{ deferred_recv = undefined }) ->
|
||||
{noreply, State, ?HIBERNATE_AFTER};
|
||||
maybe_process_deferred_recv(State = #state{deferred_recv = Data, socket = Sock}) ->
|
||||
handle_info(
|
||||
{tcp, Sock, Data},
|
||||
State#state{deferred_recv = undefined}
|
||||
).
|
||||
maybe_process_deferred_recv(State = #state{ deferred_recv = Data, socket = Sock }) ->
|
||||
handle_info({tcp, Sock, Data},
|
||||
State#state{ deferred_recv = undefined }).
|
||||
|
||||
maybe_emit_stats(#state{stats_timer = undefined}) ->
|
||||
ok;
|
||||
maybe_emit_stats(State) ->
|
||||
rabbit_event:if_enabled(
|
||||
State,
|
||||
#state.stats_timer,
|
||||
fun() -> emit_stats(State) end
|
||||
).
|
||||
rabbit_event:if_enabled(State, #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
|
||||
%% established, as this causes orphan entries on the stats database
|
||||
State1 = rabbit_event:reset_stats_timer(State, #state.stats_timer),
|
||||
ensure_stats_timer(State1);
|
||||
emit_stats(State) ->
|
||||
[
|
||||
{_, Pid},
|
||||
{_, RecvOct},
|
||||
{_, SendOct},
|
||||
{_, Reductions}
|
||||
] = infos(?SIMPLE_METRICS, State),
|
||||
[{_, Pid},
|
||||
{_, RecvOct},
|
||||
{_, SendOct},
|
||||
{_, Reductions}] = infos(?SIMPLE_METRICS, State),
|
||||
Infos = infos(?OTHER_METRICS, State),
|
||||
rabbit_core_metrics:connection_stats(Pid, Infos),
|
||||
rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
|
||||
|
@ -539,13 +465,12 @@ ensure_stats_timer(State = #state{}) ->
|
|||
infos(Items, State) ->
|
||||
[{Item, i(Item, State)} || Item <- Items].
|
||||
|
||||
i(SockStat, #state{socket = Sock}) when
|
||||
SockStat =:= recv_oct;
|
||||
SockStat =:= recv_cnt;
|
||||
SockStat =:= send_oct;
|
||||
SockStat =:= send_cnt;
|
||||
SockStat =:= send_pend
|
||||
->
|
||||
i(SockStat, #state{socket = Sock})
|
||||
when SockStat =:= recv_oct;
|
||||
SockStat =:= recv_cnt;
|
||||
SockStat =:= send_oct;
|
||||
SockStat =:= send_cnt;
|
||||
SockStat =:= send_pend ->
|
||||
case rabbit_net:getstat(Sock, [SockStat]) of
|
||||
{ok, [{_, N}]} when is_number(N) ->
|
||||
N;
|
||||
|
@ -569,19 +494,17 @@ i(connection_state, #state{connection_state = Val}) ->
|
|||
Val;
|
||||
i(pid, _) ->
|
||||
self();
|
||||
i(SSL, #state{socket = Sock, proxy_socket = ProxySock}) when
|
||||
SSL =:= ssl;
|
||||
SSL =:= ssl_protocol;
|
||||
SSL =:= ssl_key_exchange;
|
||||
SSL =:= ssl_cipher;
|
||||
SSL =:= ssl_hash
|
||||
->
|
||||
i(SSL, #state{socket = Sock, proxy_socket = ProxySock})
|
||||
when SSL =:= ssl;
|
||||
SSL =:= ssl_protocol;
|
||||
SSL =:= ssl_key_exchange;
|
||||
SSL =:= ssl_cipher;
|
||||
SSL =:= ssl_hash ->
|
||||
rabbit_ssl:info(SSL, {Sock, ProxySock});
|
||||
i(Cert, #state{socket = Sock}) when
|
||||
Cert =:= peer_cert_issuer;
|
||||
Cert =:= peer_cert_subject;
|
||||
Cert =:= peer_cert_validity
|
||||
->
|
||||
i(Cert, #state{socket = Sock})
|
||||
when Cert =:= peer_cert_issuer;
|
||||
Cert =:= peer_cert_subject;
|
||||
Cert =:= peer_cert_validity ->
|
||||
rabbit_ssl:cert_info(Cert, Sock);
|
||||
i(timeout, #state{keepalive = KState}) ->
|
||||
rabbit_mqtt_keepalive:interval_secs(KState);
|
||||
|
@ -591,48 +514,40 @@ i(Key, #state{proc_state = ProcState}) ->
|
|||
rabbit_mqtt_processor:info(Key, ProcState).
|
||||
|
||||
-spec format_status(Status) -> Status when
|
||||
Status :: #{
|
||||
state => term(),
|
||||
message => term(),
|
||||
reason => term(),
|
||||
log => [sys:system_event()]
|
||||
}.
|
||||
Status :: #{state => term(),
|
||||
message => term(),
|
||||
reason => term(),
|
||||
log => [sys:system_event()]}.
|
||||
format_status(Status) ->
|
||||
maps:map(
|
||||
fun
|
||||
(state, State) ->
|
||||
format_state(State);
|
||||
(_, Value) ->
|
||||
Value
|
||||
end,
|
||||
Status
|
||||
).
|
||||
fun(state, State) ->
|
||||
format_state(State);
|
||||
(_, Value) ->
|
||||
Value
|
||||
end, Status).
|
||||
|
||||
-spec format_state(state()) -> map().
|
||||
format_state(#state{
|
||||
socket = Socket,
|
||||
proxy_socket = ProxySock,
|
||||
await_recv = AwaitRecv,
|
||||
deferred_recv = DeferredRecv,
|
||||
parse_state = _,
|
||||
proc_state = PState,
|
||||
connection_state = ConnectionState,
|
||||
conserve = Conserve,
|
||||
stats_timer = StatsTimer,
|
||||
keepalive = Keepalive,
|
||||
conn_name = ConnName,
|
||||
received_connect_packet = ReceivedConnectPacket
|
||||
}) ->
|
||||
#{
|
||||
socket => Socket,
|
||||
proxy_socket => ProxySock,
|
||||
await_recv => AwaitRecv,
|
||||
deferred_recv => DeferredRecv =/= undefined,
|
||||
proc_state => rabbit_mqtt_processor:format_status(PState),
|
||||
connection_state => ConnectionState,
|
||||
conserve => Conserve,
|
||||
stats_timer => StatsTimer,
|
||||
keepalive => Keepalive,
|
||||
conn_name => ConnName,
|
||||
received_connect_packet => ReceivedConnectPacket
|
||||
}.
|
||||
format_state(#state{socket = Socket,
|
||||
proxy_socket = ProxySock,
|
||||
await_recv = AwaitRecv,
|
||||
deferred_recv = DeferredRecv,
|
||||
parse_state = _,
|
||||
proc_state = PState,
|
||||
connection_state = ConnectionState,
|
||||
conserve = Conserve,
|
||||
stats_timer = StatsTimer,
|
||||
keepalive = Keepalive,
|
||||
conn_name = ConnName,
|
||||
received_connect_packet = ReceivedConnectPacket
|
||||
}) ->
|
||||
#{socket => Socket,
|
||||
proxy_socket => ProxySock,
|
||||
await_recv => AwaitRecv,
|
||||
deferred_recv => DeferredRecv =/= undefined,
|
||||
proc_state => rabbit_mqtt_processor:format_status(PState),
|
||||
connection_state => ConnectionState,
|
||||
conserve => Conserve,
|
||||
stats_timer => StatsTimer,
|
||||
keepalive => Keepalive,
|
||||
conn_name => ConnName,
|
||||
received_connect_packet => ReceivedConnectPacket}.
|
||||
|
|
|
@ -13,57 +13,53 @@
|
|||
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
|
||||
|
||||
-record(store_state, {
|
||||
%% DETS table name
|
||||
table
|
||||
%% DETS table name
|
||||
table
|
||||
}).
|
||||
|
||||
-type store_state() :: #store_state{}.
|
||||
|
||||
-spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
|
||||
new(Dir, VHost) ->
|
||||
Tid = open_table(Dir, VHost),
|
||||
#store_state{table = Tid}.
|
||||
Tid = open_table(Dir, VHost),
|
||||
#store_state{table = Tid}.
|
||||
|
||||
-spec recover(file:name_all(), rabbit_types:vhost()) ->
|
||||
{error, uninitialized} | {ok, store_state()}.
|
||||
{error, uninitialized} | {ok, store_state()}.
|
||||
recover(Dir, VHost) ->
|
||||
case open_table(Dir, VHost) of
|
||||
{error, _} -> {error, uninitialized};
|
||||
{ok, Tid} -> {ok, #store_state{table = Tid}}
|
||||
end.
|
||||
case open_table(Dir, VHost) of
|
||||
{error, _} -> {error, uninitialized};
|
||||
{ok, Tid} -> {ok, #store_state{table = Tid}}
|
||||
end.
|
||||
|
||||
-spec insert(binary(), mqtt_msg(), store_state()) -> ok.
|
||||
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.
|
||||
lookup(Topic, #store_state{table = T}) ->
|
||||
case dets:lookup(T, Topic) of
|
||||
[] -> not_found;
|
||||
[Entry] -> Entry
|
||||
end.
|
||||
case dets:lookup(T, Topic) of
|
||||
[] -> not_found;
|
||||
[Entry] -> Entry
|
||||
end.
|
||||
|
||||
-spec delete(binary(), store_state()) -> ok.
|
||||
delete(Topic, #store_state{table = T}) ->
|
||||
ok = dets:delete(T, Topic).
|
||||
ok = dets:delete(T, Topic).
|
||||
|
||||
-spec terminate(store_state()) -> ok.
|
||||
terminate(#store_state{table = T}) ->
|
||||
ok = dets:close(T).
|
||||
ok = dets:close(T).
|
||||
|
||||
open_table(Dir, VHost) ->
|
||||
dets:open_file(
|
||||
rabbit_mqtt_util:vhost_name_to_table_name(VHost),
|
||||
table_options(rabbit_mqtt_util:path_for(Dir, VHost, ".dets"))
|
||||
).
|
||||
dets:open_file(rabbit_mqtt_util:vhost_name_to_table_name(VHost),
|
||||
table_options(rabbit_mqtt_util:path_for(Dir, VHost, ".dets"))).
|
||||
|
||||
table_options(Path) ->
|
||||
[
|
||||
{type, set},
|
||||
{keypos, #retained_message.topic},
|
||||
{file, Path},
|
||||
{ram_file, true},
|
||||
{repair, true},
|
||||
{auto_save,
|
||||
rabbit_misc:get_env(rabbit_mqtt, retained_message_store_dets_sync_interval, 2000)}
|
||||
[{type, set},
|
||||
{keypos, #retained_message.topic},
|
||||
{file, Path},
|
||||
{ram_file, true},
|
||||
{repair, true},
|
||||
{auto_save, rabbit_misc:get_env(rabbit_mqtt, retained_message_store_dets_sync_interval, 2000)}
|
||||
].
|
||||
|
|
|
@ -13,51 +13,49 @@
|
|||
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
|
||||
|
||||
-record(store_state, {
|
||||
%% ETS table ID
|
||||
table,
|
||||
%% where the table is stored on disk
|
||||
filename
|
||||
%% ETS table ID
|
||||
table,
|
||||
%% where the table is stored on disk
|
||||
filename
|
||||
}).
|
||||
|
||||
-type store_state() :: #store_state{}.
|
||||
|
||||
-spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
|
||||
new(Dir, VHost) ->
|
||||
Path = rabbit_mqtt_util:path_for(Dir, VHost),
|
||||
TableName = rabbit_mqtt_util:vhost_name_to_table_name(VHost),
|
||||
_ = file:delete(Path),
|
||||
Tid = ets:new(TableName, [set, public, {keypos, #retained_message.topic}]),
|
||||
#store_state{table = Tid, filename = Path}.
|
||||
Path = rabbit_mqtt_util:path_for(Dir, VHost),
|
||||
TableName = rabbit_mqtt_util:vhost_name_to_table_name(VHost),
|
||||
_ = file:delete(Path),
|
||||
Tid = ets:new(TableName, [set, public, {keypos, #retained_message.topic}]),
|
||||
#store_state{table = Tid, filename = Path}.
|
||||
|
||||
-spec recover(file:name_all(), rabbit_types:vhost()) ->
|
||||
{error, uninitialized} | {ok, store_state()}.
|
||||
{error, uninitialized} | {ok, store_state()}.
|
||||
recover(Dir, VHost) ->
|
||||
Path = rabbit_mqtt_util:path_for(Dir, VHost),
|
||||
case ets:file2tab(Path) of
|
||||
{ok, Tid} ->
|
||||
_ = file:delete(Path),
|
||||
{ok, #store_state{table = Tid, filename = Path}};
|
||||
{error, _} ->
|
||||
{error, uninitialized}
|
||||
end.
|
||||
Path = rabbit_mqtt_util:path_for(Dir, VHost),
|
||||
case ets:file2tab(Path) of
|
||||
{ok, Tid} -> _ = file:delete(Path),
|
||||
{ok, #store_state{table = Tid, filename = Path}};
|
||||
{error, _} -> {error, uninitialized}
|
||||
end.
|
||||
|
||||
-spec insert(binary(), mqtt_msg(), store_state()) -> ok.
|
||||
insert(Topic, Msg, #store_state{table = T}) ->
|
||||
true = ets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}),
|
||||
ok.
|
||||
true = ets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}),
|
||||
ok.
|
||||
|
||||
-spec lookup(binary(), store_state()) -> retained_message() | not_found.
|
||||
lookup(Topic, #store_state{table = T}) ->
|
||||
case ets:lookup(T, Topic) of
|
||||
[] -> not_found;
|
||||
[Entry] -> Entry
|
||||
end.
|
||||
case ets:lookup(T, Topic) of
|
||||
[] -> not_found;
|
||||
[Entry] -> Entry
|
||||
end.
|
||||
|
||||
-spec delete(binary(), store_state()) -> ok.
|
||||
delete(Topic, #store_state{table = T}) ->
|
||||
true = ets:delete(T, Topic),
|
||||
ok.
|
||||
true = ets:delete(T, Topic),
|
||||
ok.
|
||||
|
||||
-spec terminate(store_state()) -> ok.
|
||||
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]}]).
|
||||
|
|
|
@ -12,19 +12,19 @@
|
|||
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
|
||||
|
||||
new(_Dir, _VHost) ->
|
||||
ok.
|
||||
ok.
|
||||
|
||||
recover(_Dir, _VHost) ->
|
||||
{ok, ok}.
|
||||
{ok, ok}.
|
||||
|
||||
insert(_Topic, _Msg, _State) ->
|
||||
ok.
|
||||
ok.
|
||||
|
||||
lookup(_Topic, _State) ->
|
||||
not_found.
|
||||
not_found.
|
||||
|
||||
delete(_Topic, _State) ->
|
||||
ok.
|
||||
ok.
|
||||
|
||||
terminate(_State) ->
|
||||
ok.
|
||||
ok.
|
||||
|
|
|
@ -12,23 +12,15 @@
|
|||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([
|
||||
init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
terminate/2,
|
||||
start_link/2
|
||||
]).
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, start_link/2]).
|
||||
|
||||
-export([retain/3, fetch/2, clear/2, store_module/0]).
|
||||
|
||||
-define(TIMEOUT, 30_000).
|
||||
|
||||
-record(retainer_state, {
|
||||
store_mod,
|
||||
store
|
||||
}).
|
||||
-record(retainer_state, {store_mod,
|
||||
store}).
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
|
@ -54,19 +46,12 @@ clear(Pid, Topic) ->
|
|||
|
||||
init([StoreMod, VHost]) ->
|
||||
process_flag(trap_exit, true),
|
||||
State =
|
||||
case StoreMod:recover(store_dir(), VHost) of
|
||||
{ok, Store} ->
|
||||
#retainer_state{
|
||||
store = Store,
|
||||
store_mod = StoreMod
|
||||
};
|
||||
{error, _} ->
|
||||
#retainer_state{
|
||||
store = StoreMod:new(store_dir(), VHost),
|
||||
store_mod = StoreMod
|
||||
}
|
||||
end,
|
||||
State = case StoreMod:recover(store_dir(), VHost) of
|
||||
{ok, Store} -> #retainer_state{store = Store,
|
||||
store_mod = StoreMod};
|
||||
{error, _} -> #retainer_state{store = StoreMod:new(store_dir(), VHost),
|
||||
store_mod = StoreMod}
|
||||
end,
|
||||
{ok, State}.
|
||||
|
||||
-spec store_module() -> undefined | module().
|
||||
|
@ -78,33 +63,26 @@ store_module() ->
|
|||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
handle_cast(
|
||||
{retain, Topic, Msg},
|
||||
State = #retainer_state{store = Store, store_mod = Mod}
|
||||
) ->
|
||||
handle_cast({retain, Topic, Msg},
|
||||
State = #retainer_state{store = Store, store_mod = Mod}) ->
|
||||
ok = Mod:insert(Topic, Msg, Store),
|
||||
{noreply, State};
|
||||
handle_cast(
|
||||
{clear, Topic},
|
||||
State = #retainer_state{store = Store, store_mod = Mod}
|
||||
) ->
|
||||
handle_cast({clear, Topic},
|
||||
State = #retainer_state{store = Store, store_mod = Mod}) ->
|
||||
ok = Mod:delete(Topic, Store),
|
||||
{noreply, State}.
|
||||
|
||||
handle_call(
|
||||
{fetch, Topic},
|
||||
_From,
|
||||
State = #retainer_state{store = Store, store_mod = Mod}
|
||||
) ->
|
||||
Reply =
|
||||
case Mod:lookup(Topic, Store) of
|
||||
#retained_message{mqtt_msg = Msg} -> Msg;
|
||||
not_found -> undefined
|
||||
end,
|
||||
handle_call({fetch, Topic}, _From,
|
||||
State = #retainer_state{store = Store, store_mod = Mod}) ->
|
||||
Reply = case Mod:lookup(Topic, Store) of
|
||||
#retained_message{mqtt_msg = Msg} -> Msg;
|
||||
not_found -> undefined
|
||||
end,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_info(stop, State) ->
|
||||
{stop, normal, State};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
{stop, {unknown_info, Info}, State}.
|
||||
|
||||
|
|
|
@ -8,13 +8,8 @@
|
|||
-module(rabbit_mqtt_retainer_sup).
|
||||
-behaviour(supervisor).
|
||||
|
||||
-export([
|
||||
start_link/1,
|
||||
init/1,
|
||||
start_child/2, start_child/1,
|
||||
child_for_vhost/1,
|
||||
delete_child/1
|
||||
]).
|
||||
-export([start_link/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(term(), binary()) -> supervisor:startchild_ret().
|
||||
|
@ -24,13 +19,13 @@ start_link(SupName) ->
|
|||
|
||||
-spec child_for_vhost(rabbit_types:vhost()) -> pid().
|
||||
child_for_vhost(VHost) when is_binary(VHost) ->
|
||||
case rabbit_mqtt_retainer_sup:start_child(VHost) of
|
||||
{ok, Pid} -> Pid;
|
||||
{error, {already_started, Pid}} -> Pid
|
||||
end.
|
||||
case rabbit_mqtt_retainer_sup:start_child(VHost) of
|
||||
{ok, Pid} -> Pid;
|
||||
{error, {already_started, Pid}} -> Pid
|
||||
end.
|
||||
|
||||
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) ->
|
||||
supervisor:start_child(
|
||||
|
@ -46,20 +41,18 @@ start_child(RetainStoreMod, VHost) ->
|
|||
).
|
||||
|
||||
delete_child(VHost) ->
|
||||
Id = vhost_to_atom(VHost),
|
||||
ok = supervisor:terminate_child(?MODULE, Id),
|
||||
ok = supervisor:delete_child(?MODULE, Id).
|
||||
Id = vhost_to_atom(VHost),
|
||||
ok = supervisor:terminate_child(?MODULE, Id),
|
||||
ok = supervisor:delete_child(?MODULE, Id).
|
||||
|
||||
init([]) ->
|
||||
Mod = rabbit_mqtt_retainer:store_module(),
|
||||
rabbit_log:info(
|
||||
"MQTT retained message store: ~tp",
|
||||
[Mod]
|
||||
),
|
||||
{ok, {
|
||||
#{strategy => one_for_one, intensity => 5, period => 5},
|
||||
child_specs(Mod, rabbit_vhost:list_names())
|
||||
}}.
|
||||
Mod = rabbit_mqtt_retainer:store_module(),
|
||||
rabbit_log:info("MQTT retained message store: ~tp",
|
||||
[Mod]),
|
||||
{ok, {
|
||||
#{strategy => one_for_one, intensity => 5, period => 5},
|
||||
child_specs(Mod, rabbit_vhost:list_names())
|
||||
}}.
|
||||
|
||||
child_specs(Mod, VHosts) ->
|
||||
%% see start_child/2
|
||||
|
|
|
@ -23,61 +23,52 @@ init([{Listeners, SslListeners0}]) ->
|
|||
NumTcpAcceptors = application:get_env(?APP_NAME, num_tcp_acceptors, 10),
|
||||
ConcurrentConnsSups = application:get_env(?APP_NAME, num_conns_sups, 1),
|
||||
{ok, SocketOpts} = application:get_env(?APP_NAME, tcp_listen_options),
|
||||
{SslOpts, NumSslAcceptors, SslListeners} =
|
||||
case SslListeners0 of
|
||||
[] ->
|
||||
{none, 0, []};
|
||||
_ ->
|
||||
{
|
||||
rabbit_networking:ensure_ssl(),
|
||||
application:get_env(?APP_NAME, num_ssl_acceptors, 10),
|
||||
case rabbit_networking:poodle_check('MQTT') of
|
||||
ok -> SslListeners0;
|
||||
danger -> []
|
||||
end
|
||||
}
|
||||
end,
|
||||
{SslOpts, NumSslAcceptors, SslListeners}
|
||||
= case SslListeners0 of
|
||||
[] -> {none, 0, []};
|
||||
_ -> {rabbit_networking:ensure_ssl(),
|
||||
application:get_env(?APP_NAME, num_ssl_acceptors, 10),
|
||||
case rabbit_networking:poodle_check('MQTT') of
|
||||
ok -> SslListeners0;
|
||||
danger -> []
|
||||
end}
|
||||
end,
|
||||
%% Use separate process group scope per RabbitMQ node. This achieves a local-only
|
||||
%% process group which requires less memory with millions of connections.
|
||||
PgScope = list_to_atom(io_lib:format("~s_~s", [?PG_SCOPE, node()])),
|
||||
persistent_term:put(?PG_SCOPE, PgScope),
|
||||
{ok,
|
||||
{
|
||||
#{
|
||||
strategy => one_for_all,
|
||||
intensity => 10,
|
||||
period => 10
|
||||
},
|
||||
[
|
||||
#{
|
||||
id => PgScope,
|
||||
start => {pg, start_link, [PgScope]},
|
||||
restart => transient,
|
||||
shutdown => ?WORKER_WAIT,
|
||||
type => worker,
|
||||
modules => [pg]
|
||||
},
|
||||
#{
|
||||
id => rabbit_mqtt_retainer_sup,
|
||||
start =>
|
||||
{rabbit_mqtt_retainer_sup, start_link, [{local, rabbit_mqtt_retainer_sup}]},
|
||||
restart => transient,
|
||||
shutdown => ?SUPERVISOR_WAIT,
|
||||
type => supervisor,
|
||||
modules => [rabbit_mqtt_retainer_sup]
|
||||
}
|
||||
| listener_specs(
|
||||
fun tcp_listener_spec/1,
|
||||
[SocketOpts, NumTcpAcceptors, ConcurrentConnsSups],
|
||||
Listeners
|
||||
) ++
|
||||
listener_specs(
|
||||
fun ssl_listener_spec/1,
|
||||
[SocketOpts, SslOpts, NumSslAcceptors, ConcurrentConnsSups],
|
||||
SslListeners
|
||||
)
|
||||
]
|
||||
}}.
|
||||
{#{strategy => one_for_all,
|
||||
intensity => 10,
|
||||
period => 10},
|
||||
[
|
||||
#{id => PgScope,
|
||||
start => {pg, start_link, [PgScope]},
|
||||
restart => transient,
|
||||
shutdown => ?WORKER_WAIT,
|
||||
type => worker,
|
||||
modules => [pg]
|
||||
},
|
||||
#{
|
||||
id => rabbit_mqtt_retainer_sup,
|
||||
start => {rabbit_mqtt_retainer_sup, start_link,
|
||||
[{local, rabbit_mqtt_retainer_sup}]},
|
||||
restart => transient,
|
||||
shutdown => ?SUPERVISOR_WAIT,
|
||||
type => supervisor,
|
||||
modules => [rabbit_mqtt_retainer_sup]
|
||||
}
|
||||
| listener_specs(
|
||||
fun tcp_listener_spec/1,
|
||||
[SocketOpts, NumTcpAcceptors, ConcurrentConnsSups],
|
||||
Listeners
|
||||
) ++
|
||||
listener_specs(
|
||||
fun ssl_listener_spec/1,
|
||||
[SocketOpts, SslOpts, NumSslAcceptors, ConcurrentConnsSups],
|
||||
SslListeners
|
||||
)
|
||||
]}}.
|
||||
|
||||
-spec stop_listeners() -> ok.
|
||||
stop_listeners() ->
|
||||
|
@ -98,33 +89,33 @@ listener_specs(Fun, Args, Listeners) ->
|
|||
|
||||
tcp_listener_spec([Address, SocketOpts, NumAcceptors, ConcurrentConnsSups]) ->
|
||||
rabbit_networking:tcp_listener_spec(
|
||||
rabbit_mqtt_listener_sup,
|
||||
Address,
|
||||
SocketOpts,
|
||||
transport(?TCP_PROTOCOL),
|
||||
rabbit_mqtt_reader,
|
||||
[],
|
||||
mqtt,
|
||||
NumAcceptors,
|
||||
ConcurrentConnsSups,
|
||||
worker,
|
||||
"MQTT TCP listener"
|
||||
).
|
||||
rabbit_mqtt_listener_sup,
|
||||
Address,
|
||||
SocketOpts,
|
||||
transport(?TCP_PROTOCOL),
|
||||
rabbit_mqtt_reader,
|
||||
[],
|
||||
mqtt,
|
||||
NumAcceptors,
|
||||
ConcurrentConnsSups,
|
||||
worker,
|
||||
"MQTT TCP listener"
|
||||
).
|
||||
|
||||
ssl_listener_spec([Address, SocketOpts, SslOpts, NumAcceptors, ConcurrentConnsSups]) ->
|
||||
rabbit_networking:tcp_listener_spec(
|
||||
rabbit_mqtt_listener_sup,
|
||||
Address,
|
||||
SocketOpts ++ SslOpts,
|
||||
transport(?TLS_PROTOCOL),
|
||||
rabbit_mqtt_reader,
|
||||
[],
|
||||
'mqtt/ssl',
|
||||
NumAcceptors,
|
||||
ConcurrentConnsSups,
|
||||
worker,
|
||||
"MQTT TLS listener"
|
||||
).
|
||||
rabbit_mqtt_listener_sup,
|
||||
Address,
|
||||
SocketOpts ++ SslOpts,
|
||||
transport(?TLS_PROTOCOL),
|
||||
rabbit_mqtt_reader,
|
||||
[],
|
||||
'mqtt/ssl',
|
||||
NumAcceptors,
|
||||
ConcurrentConnsSups,
|
||||
worker,
|
||||
"MQTT TLS listener"
|
||||
).
|
||||
|
||||
transport(?TCP_PROTOCOL) ->
|
||||
ranch_tcp;
|
||||
|
|
|
@ -11,21 +11,20 @@
|
|||
-include("rabbit_mqtt.hrl").
|
||||
-include("rabbit_mqtt_packet.hrl").
|
||||
|
||||
-export([
|
||||
queue_name_bin/2,
|
||||
qos_from_queue_name/2,
|
||||
env/1,
|
||||
table_lookup/2,
|
||||
path_for/2,
|
||||
path_for/3,
|
||||
vhost_name_to_table_name/1,
|
||||
register_clientid/2,
|
||||
remove_duplicate_clientid_connections/2,
|
||||
init_sparkplug/0,
|
||||
mqtt_to_amqp/1,
|
||||
amqp_to_mqtt/1,
|
||||
truncate_binary/2
|
||||
]).
|
||||
-export([queue_name_bin/2,
|
||||
qos_from_queue_name/2,
|
||||
env/1,
|
||||
table_lookup/2,
|
||||
path_for/2,
|
||||
path_for/3,
|
||||
vhost_name_to_table_name/1,
|
||||
register_clientid/2,
|
||||
remove_duplicate_clientid_connections/2,
|
||||
init_sparkplug/0,
|
||||
mqtt_to_amqp/1,
|
||||
amqp_to_mqtt/1,
|
||||
truncate_binary/2
|
||||
]).
|
||||
|
||||
-define(MAX_TOPIC_TRANSLATION_CACHE_SIZE, 12).
|
||||
-define(SPARKPLUG_MP_MQTT_TO_AMQP, sparkplug_mp_mqtt_to_amqp).
|
||||
|
@ -73,8 +72,7 @@ init_sparkplug() ->
|
|||
|
||||
-spec mqtt_to_amqp(binary()) -> binary().
|
||||
mqtt_to_amqp(Topic) ->
|
||||
T =
|
||||
case persistent_term:get(?SPARKPLUG_MP_MQTT_TO_AMQP, no_sparkplug) of
|
||||
T = case persistent_term:get(?SPARKPLUG_MP_MQTT_TO_AMQP, no_sparkplug) of
|
||||
no_sparkplug ->
|
||||
Topic;
|
||||
M2A_SpRe ->
|
||||
|
@ -104,13 +102,12 @@ amqp_to_mqtt(Topic) ->
|
|||
end.
|
||||
|
||||
cached(CacheName, Fun, Arg) ->
|
||||
Cache =
|
||||
case get(CacheName) of
|
||||
undefined ->
|
||||
[];
|
||||
Other ->
|
||||
Other
|
||||
end,
|
||||
Cache = case get(CacheName) of
|
||||
undefined ->
|
||||
[];
|
||||
Other ->
|
||||
Other
|
||||
end,
|
||||
case lists:keyfind(Arg, 1, Cache) of
|
||||
{_, V} ->
|
||||
V;
|
||||
|
@ -145,11 +142,11 @@ env(Key) ->
|
|||
|
||||
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(exchange, 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(exchange, Val) -> rabbit_data_coercion:to_binary(Val);
|
||||
coerce_env_value(vhost, Val) -> rabbit_data_coercion:to_binary(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.
|
||||
table_lookup(undefined, _Key) ->
|
||||
undefined;
|
||||
|
@ -164,11 +161,11 @@ vhost_name_to_dir_name(VHost, Suffix) ->
|
|||
|
||||
-spec path_for(file:name_all(), rabbit_types:vhost()) -> file:filename_all().
|
||||
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().
|
||||
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()) ->
|
||||
atom().
|
||||
|
@ -177,9 +174,8 @@ vhost_name_to_table_name(VHost) ->
|
|||
list_to_atom("rabbit_mqtt_retained_" ++ rabbit_misc:format("~36.16.0b", [Num])).
|
||||
|
||||
-spec register_clientid(rabbit_types:vhost(), binary()) -> ok.
|
||||
register_clientid(Vhost, ClientId) when
|
||||
is_binary(Vhost), is_binary(ClientId)
|
||||
->
|
||||
register_clientid(Vhost, ClientId)
|
||||
when is_binary(Vhost), is_binary(ClientId) ->
|
||||
PgGroup = {Vhost, ClientId},
|
||||
ok = pg:join(persistent_term:get(?PG_SCOPE), PgGroup, self()),
|
||||
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.
|
||||
ok;
|
||||
false ->
|
||||
ok = erpc:multicast(
|
||||
[node() | nodes()],
|
||||
?MODULE,
|
||||
remove_duplicate_clientid_connections,
|
||||
[PgGroup, self()]
|
||||
)
|
||||
ok = erpc:multicast([node() | nodes()],
|
||||
?MODULE,
|
||||
remove_duplicate_clientid_connections,
|
||||
[PgGroup, self()])
|
||||
end.
|
||||
|
||||
-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
|
||||
PgScope ->
|
||||
Pids = pg:get_local_members(PgScope, PgGroup),
|
||||
lists:foreach(
|
||||
fun(Pid) ->
|
||||
gen_server:cast(Pid, duplicate_id)
|
||||
end,
|
||||
Pids -- [PidToKeep]
|
||||
)
|
||||
catch
|
||||
_:badarg ->
|
||||
%% MQTT supervision tree on this node not fully started
|
||||
ok
|
||||
lists:foreach(fun(Pid) ->
|
||||
gen_server:cast(Pid, duplicate_id)
|
||||
end, Pids -- [PidToKeep])
|
||||
catch _:badarg ->
|
||||
%% MQTT supervision tree on this node not fully started
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec truncate_binary(binary(), non_neg_integer()) -> binary().
|
||||
truncate_binary(Bin, Size) when
|
||||
is_binary(Bin) andalso byte_size(Bin) =< Size
|
||||
->
|
||||
truncate_binary(Bin, Size)
|
||||
when is_binary(Bin) andalso byte_size(Bin) =< Size ->
|
||||
Bin;
|
||||
truncate_binary(Bin, Size) when
|
||||
is_binary(Bin)
|
||||
->
|
||||
truncate_binary(Bin, Size)
|
||||
when is_binary(Bin) ->
|
||||
binary:part(Bin, 0, Size).
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,42 +9,36 @@
|
|||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-import(util, [
|
||||
expect_publishes/3,
|
||||
connect/3,
|
||||
connect/4,
|
||||
await_exit/1
|
||||
]).
|
||||
-import(util, [expect_publishes/3,
|
||||
connect/3,
|
||||
connect/4,
|
||||
await_exit/1]).
|
||||
|
||||
-import(
|
||||
rabbit_ct_broker_helpers,
|
||||
[
|
||||
setup_steps/0,
|
||||
teardown_steps/0,
|
||||
get_node_config/3,
|
||||
rabbitmqctl/3,
|
||||
rpc/4,
|
||||
stop_node/2
|
||||
]
|
||||
).
|
||||
-import(rabbit_ct_broker_helpers,
|
||||
[setup_steps/0,
|
||||
teardown_steps/0,
|
||||
get_node_config/3,
|
||||
rabbitmqctl/3,
|
||||
rpc/4,
|
||||
stop_node/2
|
||||
]).
|
||||
|
||||
-define(OPTS, [
|
||||
{connect_timeout, 1},
|
||||
{ack_timeout, 1}
|
||||
]).
|
||||
-define(OPTS, [{connect_timeout, 1},
|
||||
{ack_timeout, 1}]).
|
||||
|
||||
all() ->
|
||||
[
|
||||
{group, cluster_size_5}
|
||||
{group, cluster_size_5}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{cluster_size_5, [], [
|
||||
connection_id_tracking,
|
||||
connection_id_tracking_on_nodedown,
|
||||
connection_id_tracking_with_decommissioned_node
|
||||
]}
|
||||
{cluster_size_5, [],
|
||||
[
|
||||
connection_id_tracking,
|
||||
connection_id_tracking_on_nodedown,
|
||||
connection_id_tracking_with_decommissioned_node
|
||||
]}
|
||||
].
|
||||
|
||||
suite() ->
|
||||
|
@ -56,12 +50,11 @@ suite() ->
|
|||
|
||||
merge_app_env(Config) ->
|
||||
rabbit_ct_helpers:merge_app_env(
|
||||
Config,
|
||||
{rabbit, [
|
||||
{collect_statistics, basic},
|
||||
{collect_statistics_interval, 100}
|
||||
]}
|
||||
).
|
||||
Config,
|
||||
{rabbit, [
|
||||
{collect_statistics, basic},
|
||||
{collect_statistics_interval, 100}
|
||||
]}).
|
||||
|
||||
init_per_suite(Config) ->
|
||||
rabbit_ct_helpers:log_environment(),
|
||||
|
@ -72,8 +65,7 @@ end_per_suite(Config) ->
|
|||
|
||||
init_per_group(cluster_size_5, Config) ->
|
||||
rabbit_ct_helpers:set_config(
|
||||
Config, [{rmq_nodes_count, 5}]
|
||||
).
|
||||
Config, [{rmq_nodes_count, 5}]).
|
||||
|
||||
end_per_group(_, Config) ->
|
||||
Config.
|
||||
|
@ -83,25 +75,19 @@ init_per_testcase(Testcase, Config) ->
|
|||
rabbit_ct_helpers:log_environment(),
|
||||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||
{rmq_nodename_suffix, Testcase},
|
||||
{rmq_extra_tcp_ports, [
|
||||
tcp_port_mqtt_extra,
|
||||
tcp_port_mqtt_tls_extra
|
||||
]},
|
||||
{rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
|
||||
tcp_port_mqtt_tls_extra]},
|
||||
{rmq_nodes_clustered, true}
|
||||
]),
|
||||
rabbit_ct_helpers:run_setup_steps(
|
||||
Config1,
|
||||
[fun merge_app_env/1] ++
|
||||
setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()
|
||||
).
|
||||
]),
|
||||
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||
[ fun merge_app_env/1 ] ++
|
||||
setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()).
|
||||
|
||||
end_per_testcase(Testcase, Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
teardown_steps()
|
||||
),
|
||||
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
teardown_steps()),
|
||||
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
|
@ -179,15 +165,14 @@ connection_id_tracking_with_decommissioned_node(Config) ->
|
|||
%% Helpers
|
||||
%%
|
||||
|
||||
assert_connection_count(_Config, 0, _, NumElements) ->
|
||||
assert_connection_count(_Config, 0, _, NumElements) ->
|
||||
ct:fail("failed to match connection count ~b", [NumElements]);
|
||||
assert_connection_count(Config, Retries, NodeId, NumElements) ->
|
||||
case util:all_connection_pids(Config) of
|
||||
Pids when
|
||||
length(Pids) =:= NumElements
|
||||
->
|
||||
Pids
|
||||
when length(Pids) =:= NumElements ->
|
||||
ok;
|
||||
_ ->
|
||||
timer:sleep(500),
|
||||
assert_connection_count(Config, Retries - 1, NodeId, NumElements)
|
||||
assert_connection_count(Config, Retries-1, NodeId, NumElements)
|
||||
end.
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
%%
|
||||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
||||
|
||||
|
||||
-module(command_SUITE).
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
|
@ -16,45 +17,39 @@
|
|||
|
||||
all() ->
|
||||
[
|
||||
{group, non_parallel_tests}
|
||||
{group, non_parallel_tests}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{non_parallel_tests, [], [
|
||||
merge_defaults,
|
||||
run
|
||||
]}
|
||||
{non_parallel_tests, [], [
|
||||
merge_defaults,
|
||||
run
|
||||
]}
|
||||
].
|
||||
|
||||
suite() ->
|
||||
[
|
||||
{timetrap, {minutes, 3}}
|
||||
{timetrap, {minutes, 3}}
|
||||
].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
rabbit_ct_helpers:log_environment(),
|
||||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||
{rmq_nodename_suffix, ?MODULE},
|
||||
{rmq_extra_tcp_ports, [
|
||||
tcp_port_mqtt_extra,
|
||||
tcp_port_mqtt_tls_extra
|
||||
]},
|
||||
{rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
|
||||
tcp_port_mqtt_tls_extra]},
|
||||
{rmq_nodes_clustered, true},
|
||||
{rmq_nodes_count, 3}
|
||||
]),
|
||||
rabbit_ct_helpers:run_setup_steps(
|
||||
Config1,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()
|
||||
).
|
||||
]),
|
||||
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()).
|
||||
|
||||
end_per_suite(Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()
|
||||
).
|
||||
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()).
|
||||
|
||||
init_per_group(_, Config) ->
|
||||
Config.
|
||||
|
@ -78,6 +73,7 @@ merge_defaults(_Config) ->
|
|||
{[<<"other_key">>], #{verbose := false}} =
|
||||
?COMMAND:merge_defaults([<<"other_key">>], #{verbose => false}).
|
||||
|
||||
|
||||
run(Config) ->
|
||||
Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
|
||||
Opts = #{node => Node, timeout => 10_000, verbose => false},
|
||||
|
@ -95,27 +91,18 @@ run(Config) ->
|
|||
C2 = connect(<<"simpleClient1">>, Config, [{ack_timeout, 1}]),
|
||||
timer:sleep(200),
|
||||
|
||||
[
|
||||
[{client_id, <<"simpleClient">>}, {user, <<"guest">>}],
|
||||
[{client_id, <<"simpleClient1">>}, {user, <<"guest">>}]
|
||||
] =
|
||||
[[{client_id, <<"simpleClient">>}, {user, <<"guest">>}],
|
||||
[{client_id, <<"simpleClient1">>}, {user, <<"guest">>}]] =
|
||||
lists:sort(
|
||||
'Elixir.Enum':to_list(
|
||||
?COMMAND:run(
|
||||
[<<"client_id">>, <<"user">>],
|
||||
Opts
|
||||
)
|
||||
)
|
||||
),
|
||||
'Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>, <<"user">>],
|
||||
Opts))),
|
||||
|
||||
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
|
||||
start_amqp_connection(network, Node, Port),
|
||||
|
||||
%% There are still just two MQTT connections
|
||||
[
|
||||
[{client_id, <<"simpleClient">>}],
|
||||
[{client_id, <<"simpleClient1">>}]
|
||||
] =
|
||||
[[{client_id, <<"simpleClient">>}],
|
||||
[{client_id, <<"simpleClient1">>}]] =
|
||||
lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts))),
|
||||
|
||||
start_amqp_connection(direct, Node, Port),
|
||||
|
@ -123,19 +110,14 @@ run(Config) ->
|
|||
|
||||
%% Still two MQTT connections
|
||||
?assertEqual(
|
||||
[
|
||||
[{client_id, <<"simpleClient">>}],
|
||||
[{client_id, <<"simpleClient1">>}]
|
||||
],
|
||||
lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts)))
|
||||
),
|
||||
[[{client_id, <<"simpleClient">>}],
|
||||
[{client_id, <<"simpleClient1">>}]],
|
||||
lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts)))),
|
||||
|
||||
%% Verbose returns all keys
|
||||
AllKeys = lists:map(fun(I) -> atom_to_binary(I) end, ?INFO_ITEMS),
|
||||
[AllInfos1Con1, _AllInfos1Con2] = 'Elixir.Enum':to_list(?COMMAND:run(AllKeys, Opts)),
|
||||
[AllInfos2Con1, _AllInfos2Con2] = 'Elixir.Enum':to_list(
|
||||
?COMMAND:run([], Opts#{verbose => true})
|
||||
),
|
||||
[AllInfos2Con1, _AllInfos2Con2] = 'Elixir.Enum':to_list(?COMMAND:run([], Opts#{verbose => true})),
|
||||
|
||||
%% Keys are INFO_ITEMS
|
||||
InfoItemsSorted = lists:sort(?INFO_ITEMS),
|
||||
|
|
|
@ -5,10 +5,8 @@
|
|||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
||||
|
||||
-module(config_SUITE).
|
||||
-compile([
|
||||
export_all,
|
||||
nowarn_export_all
|
||||
]).
|
||||
-compile([export_all,
|
||||
nowarn_export_all]).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
|
@ -16,16 +14,17 @@
|
|||
|
||||
all() ->
|
||||
[
|
||||
{group, mnesia}
|
||||
{group, mnesia}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{mnesia, [shuffle], [
|
||||
rabbitmq_default,
|
||||
environment_set,
|
||||
flag_set
|
||||
]}
|
||||
{mnesia, [shuffle],
|
||||
[
|
||||
rabbitmq_default,
|
||||
environment_set,
|
||||
flag_set
|
||||
]}
|
||||
].
|
||||
|
||||
suite() ->
|
||||
|
@ -46,12 +45,8 @@ init_per_testcase(rabbitmq_default = Test, Config) ->
|
|||
init_per_testcase0(Test, Config);
|
||||
init_per_testcase(environment_set = Test, Config0) ->
|
||||
Config = rabbit_ct_helpers:merge_app_env(
|
||||
Config0,
|
||||
{mnesia, [
|
||||
{dump_log_write_threshold, 25000},
|
||||
{dump_log_time_threshold, 60000}
|
||||
]}
|
||||
),
|
||||
Config0, {mnesia, [{dump_log_write_threshold, 25000},
|
||||
{dump_log_time_threshold, 60000}]}),
|
||||
init_per_testcase0(Test, Config);
|
||||
init_per_testcase(flag_set = Test, 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) ->
|
||||
Config1 = rabbit_ct_helpers:set_config(Config0, {rmq_nodename_suffix, Testcase}),
|
||||
Config = rabbit_ct_helpers:run_steps(
|
||||
Config1,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()
|
||||
),
|
||||
Config1,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()),
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase).
|
||||
|
||||
end_per_testcase(Testcase, Config0) ->
|
||||
Config = rabbit_ct_helpers:testcase_finished(Config0, Testcase),
|
||||
rabbit_ct_helpers:run_teardown_steps(
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()
|
||||
).
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()).
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Testsuite cases
|
||||
|
@ -81,33 +74,21 @@ end_per_testcase(Testcase, Config0) ->
|
|||
%% The MQTT plugin expects Mnesia dump_log_write_threshold to be increased
|
||||
%% from 1000 (Mnesia default) to 5000 (RabbitMQ default).
|
||||
rabbitmq_default(Config) ->
|
||||
?assertEqual(
|
||||
5_000,
|
||||
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])
|
||||
),
|
||||
?assertEqual(
|
||||
90_000,
|
||||
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
|
||||
).
|
||||
?assertEqual(5_000,
|
||||
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])),
|
||||
?assertEqual(90_000,
|
||||
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])).
|
||||
|
||||
%% User configured setting in advanced.config should be respected.
|
||||
environment_set(Config) ->
|
||||
?assertEqual(
|
||||
25_000,
|
||||
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])
|
||||
),
|
||||
?assertEqual(
|
||||
60_000,
|
||||
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
|
||||
).
|
||||
?assertEqual(25_000,
|
||||
rpc(Config, 0, mnesia, system_info, [dump_log_write_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.
|
||||
flag_set(Config) ->
|
||||
?assertEqual(
|
||||
15_000,
|
||||
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])
|
||||
),
|
||||
?assertEqual(
|
||||
90_000,
|
||||
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
|
||||
).
|
||||
?assertEqual(15_000,
|
||||
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])),
|
||||
?assertEqual(90_000,
|
||||
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])).
|
||||
|
|
|
@ -23,6 +23,7 @@ init_per_suite(Config) ->
|
|||
Config1 = rabbit_ct_helpers:run_setup_steps(Config),
|
||||
rabbit_ct_config_schema:init_schemas(rabbitmq_mqtt, Config1).
|
||||
|
||||
|
||||
end_per_suite(Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(Config).
|
||||
|
||||
|
@ -30,19 +31,15 @@ init_per_testcase(Testcase, Config) ->
|
|||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||
{rmq_nodename_suffix, Testcase}
|
||||
]),
|
||||
rabbit_ct_helpers:run_steps(
|
||||
Config1,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()
|
||||
).
|
||||
]),
|
||||
rabbit_ct_helpers:run_steps(Config1,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()).
|
||||
|
||||
end_per_testcase(Testcase, Config) ->
|
||||
Config1 = rabbit_ct_helpers:run_steps(
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()
|
||||
),
|
||||
Config1 = rabbit_ct_helpers:run_steps(Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()),
|
||||
rabbit_ct_helpers:testcase_finished(Config1, Testcase).
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
|
@ -50,13 +47,9 @@ end_per_testcase(Testcase, Config) ->
|
|||
%% -------------------------------------------------------------------
|
||||
|
||||
run_snippets(Config) ->
|
||||
ok = rabbit_ct_broker_helpers:rpc(
|
||||
Config,
|
||||
0,
|
||||
?MODULE,
|
||||
run_snippets1,
|
||||
[Config]
|
||||
).
|
||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0,
|
||||
?MODULE, run_snippets1, [Config]).
|
||||
|
||||
run_snippets1(Config) ->
|
||||
rabbit_ct_config_schema:run_snippets(Config).
|
||||
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
init(_) ->
|
||||
{ok, ?INIT_STATE}.
|
||||
|
||||
handle_event(#event{type = T}, State) when
|
||||
T =:= node_stats orelse
|
||||
T =:= node_node_stats orelse
|
||||
T =:= node_node_deleted
|
||||
->
|
||||
handle_event(#event{type = T}, State)
|
||||
when T =:= node_stats orelse
|
||||
T =:= node_node_stats orelse
|
||||
T =:= node_node_deleted ->
|
||||
{ok, State};
|
||||
handle_event(Event, State) ->
|
||||
{ok, [Event | State]}.
|
||||
|
|
|
@ -13,31 +13,27 @@
|
|||
|
||||
-import(rabbit_ct_broker_helpers, [rpc/5]).
|
||||
-import(rabbit_ct_helpers, [eventually/1]).
|
||||
-import(util, [
|
||||
expect_publishes/3,
|
||||
get_global_counters/4,
|
||||
connect/2,
|
||||
connect/4
|
||||
]).
|
||||
-import(util, [expect_publishes/3,
|
||||
get_global_counters/4,
|
||||
connect/2,
|
||||
connect/4]).
|
||||
|
||||
-define(PROTO_VER, v4).
|
||||
|
||||
all() ->
|
||||
[
|
||||
{group, cluster_size_3}
|
||||
{group, cluster_size_3}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{cluster_size_3, [], [
|
||||
delete_ra_cluster_mqtt_node,
|
||||
rabbit_mqtt_qos0_queue
|
||||
]}
|
||||
{cluster_size_3, [], [delete_ra_cluster_mqtt_node,
|
||||
rabbit_mqtt_qos0_queue]}
|
||||
].
|
||||
|
||||
suite() ->
|
||||
[
|
||||
{timetrap, {minutes, 2}}
|
||||
{timetrap, {minutes, 2}}
|
||||
].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
|
@ -48,36 +44,26 @@ end_per_suite(Config) ->
|
|||
rabbit_ct_helpers:run_teardown_steps(Config).
|
||||
|
||||
init_per_group(Group = cluster_size_3, Config0) ->
|
||||
Config1 = rabbit_ct_helpers:set_config(Config0, [
|
||||
{rmq_nodes_count, 3},
|
||||
{rmq_nodename_suffix, Group}
|
||||
]),
|
||||
Config1 = rabbit_ct_helpers:set_config(Config0, [{rmq_nodes_count, 3},
|
||||
{rmq_nodename_suffix, Group}]),
|
||||
Config = rabbit_ct_helpers:merge_app_env(
|
||||
Config1, {rabbit, [{forced_feature_flags_on_init, []}]}
|
||||
),
|
||||
rabbit_ct_helpers:run_steps(
|
||||
Config,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()
|
||||
).
|
||||
Config1, {rabbit, [{forced_feature_flags_on_init, []}]}),
|
||||
rabbit_ct_helpers:run_steps(Config,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()).
|
||||
|
||||
end_per_group(_Group, Config) ->
|
||||
rabbit_ct_helpers:run_steps(
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()
|
||||
).
|
||||
rabbit_ct_helpers:run_steps(Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()).
|
||||
|
||||
init_per_testcase(TestCase, Config) ->
|
||||
case rabbit_ct_broker_helpers:is_feature_flag_supported(Config, TestCase) of
|
||||
true ->
|
||||
Config;
|
||||
false ->
|
||||
{skip,
|
||||
io_lib:format(
|
||||
"feature flag ~s is unsupported",
|
||||
[TestCase]
|
||||
)}
|
||||
{skip, io_lib:format("feature flag ~s is unsupported",
|
||||
[TestCase])}
|
||||
end.
|
||||
|
||||
end_per_testcase(_TestCase, Config) ->
|
||||
|
@ -90,27 +76,16 @@ delete_ra_cluster_mqtt_node(Config) ->
|
|||
%% old client ID tracking works
|
||||
?assertEqual(1, length(util:all_connection_pids(Config))),
|
||||
%% Ra processes are alive
|
||||
?assert(
|
||||
lists:all(
|
||||
fun erlang:is_pid/1,
|
||||
rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])
|
||||
)
|
||||
),
|
||||
?assert(lists:all(fun erlang:is_pid/1,
|
||||
rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node]))),
|
||||
|
||||
?assertEqual(
|
||||
ok,
|
||||
rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)
|
||||
),
|
||||
?assertEqual(ok,
|
||||
rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)),
|
||||
|
||||
%% Ra processes should be gone
|
||||
rabbit_ct_helpers:eventually(
|
||||
?_assert(
|
||||
lists:all(
|
||||
fun(Pid) -> Pid =:= undefined end,
|
||||
rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])
|
||||
)
|
||||
)
|
||||
),
|
||||
?_assert(lists:all(fun(Pid) -> Pid =:= undefined end,
|
||||
rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])))),
|
||||
%% new client ID tracking works
|
||||
?assertEqual(1, length(util:all_connection_pids(Config))),
|
||||
?assert(erlang:is_process_alive(C)),
|
||||
|
@ -124,30 +99,20 @@ rabbit_mqtt_qos0_queue(Config) ->
|
|||
{ok, _, [0]} = emqtt:subscribe(C1, Topic, qos0),
|
||||
ok = emqtt:publish(C1, Topic, Msg, qos0),
|
||||
ok = expect_publishes(C1, Topic, [Msg]),
|
||||
?assertEqual(
|
||||
1,
|
||||
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))
|
||||
),
|
||||
?assertEqual(1,
|
||||
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))),
|
||||
|
||||
?assertEqual(
|
||||
ok,
|
||||
rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)
|
||||
),
|
||||
?assertEqual(ok,
|
||||
rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)),
|
||||
|
||||
%% Queue type does not chanage for existing connection.
|
||||
?assertEqual(
|
||||
1,
|
||||
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))
|
||||
),
|
||||
?assertEqual(1,
|
||||
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))),
|
||||
ok = emqtt:publish(C1, Topic, Msg, qos0),
|
||||
ok = expect_publishes(C1, Topic, [Msg]),
|
||||
?assertMatch(
|
||||
#{
|
||||
messages_delivered_total := 2,
|
||||
messages_delivered_consume_auto_ack_total := 2
|
||||
},
|
||||
get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, rabbit_classic_queue}])
|
||||
),
|
||||
?assertMatch(#{messages_delivered_total := 2,
|
||||
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.
|
||||
true = unlink(C1),
|
||||
|
@ -155,22 +120,13 @@ rabbit_mqtt_qos0_queue(Config) ->
|
|||
{ok, _, [0]} = emqtt:subscribe(C2, Topic, qos0),
|
||||
%% This time, we get the new queue type.
|
||||
eventually(
|
||||
?_assertEqual(
|
||||
0,
|
||||
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(0,
|
||||
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue])))),
|
||||
?assertEqual(1,
|
||||
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [FeatureFlag]))),
|
||||
ok = emqtt:publish(C2, Topic, Msg, qos0),
|
||||
ok = expect_publishes(C2, Topic, [Msg]),
|
||||
?assertMatch(
|
||||
#{
|
||||
messages_delivered_total := 1,
|
||||
messages_delivered_consume_auto_ack_total := 1
|
||||
},
|
||||
get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, FeatureFlag}])
|
||||
),
|
||||
?assertMatch(#{messages_delivered_total := 1,
|
||||
messages_delivered_consume_auto_ack_total := 1},
|
||||
get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, FeatureFlag}])),
|
||||
ok = emqtt:disconnect(C2).
|
||||
|
|
|
@ -12,25 +12,24 @@
|
|||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-define(BASE_CONF_MQTT,
|
||||
{rabbitmq_mqtt, [
|
||||
{ssl_cert_login, true},
|
||||
{allow_anonymous, false},
|
||||
{sparkplug, true},
|
||||
{tcp_listeners, []},
|
||||
{ssl_listeners, []}
|
||||
]}
|
||||
).
|
||||
{rabbitmq_mqtt, [
|
||||
{ssl_cert_login, true},
|
||||
{allow_anonymous, false},
|
||||
{sparkplug, true},
|
||||
{tcp_listeners, []},
|
||||
{ssl_listeners, []}
|
||||
]}).
|
||||
|
||||
all() ->
|
||||
[
|
||||
{group, non_parallel_tests}
|
||||
{group, non_parallel_tests}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{non_parallel_tests, [], [
|
||||
java
|
||||
]}
|
||||
{non_parallel_tests, [], [
|
||||
java
|
||||
]}
|
||||
].
|
||||
|
||||
suite() ->
|
||||
|
@ -53,20 +52,16 @@ init_per_suite(Config) ->
|
|||
{rmq_certspwd, "bunnychow"},
|
||||
{rmq_nodes_clustered, true},
|
||||
{rmq_nodes_count, 3}
|
||||
]),
|
||||
rabbit_ct_helpers:run_setup_steps(
|
||||
Config1,
|
||||
[fun merge_app_env/1] ++
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()
|
||||
).
|
||||
]),
|
||||
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||
[ fun merge_app_env/1 ] ++
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()).
|
||||
|
||||
end_per_suite(Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()
|
||||
).
|
||||
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()).
|
||||
|
||||
init_per_group(_, Config) ->
|
||||
Config.
|
||||
|
@ -79,38 +74,25 @@ init_per_testcase(Testcase, Config) ->
|
|||
CertFile = filename:join([CertsDir, "client", "cert.pem"]),
|
||||
{ok, CertBin} = file:read_file(CertFile),
|
||||
[{'Certificate', Cert, not_encrypted}] = public_key:pem_decode(CertBin),
|
||||
UserBin = rabbit_ct_broker_helpers:rpc(
|
||||
Config,
|
||||
0,
|
||||
rabbit_ssl,
|
||||
peer_cert_auth_name,
|
||||
[Cert]
|
||||
),
|
||||
UserBin = rabbit_ct_broker_helpers:rpc(Config, 0,
|
||||
rabbit_ssl,
|
||||
peer_cert_auth_name,
|
||||
[Cert]),
|
||||
User = binary_to_list(UserBin),
|
||||
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["add_user", User, ""]),
|
||||
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, [
|
||||
"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, ["add_user", User, ""]),
|
||||
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["set_permissions", "-p", "/", User, ".*", ".*", ".*"]),
|
||||
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0,
|
||||
["set_topic_permissions", "-p", "/", "guest", "amq.topic",
|
||||
% Write permission
|
||||
"test-topic|test-retained-topic|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+",
|
||||
% 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).
|
||||
|
||||
end_per_testcase(Testcase, Config) ->
|
||||
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
||||
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Testsuite cases
|
||||
%% -------------------------------------------------------------------
|
||||
|
@ -140,5 +122,5 @@ q(P, [K | Rem]) ->
|
|||
undefined -> undefined;
|
||||
V -> q(V, Rem)
|
||||
end;
|
||||
q(P, []) ->
|
||||
{ok, P}.
|
||||
q(P, []) -> {ok, P}.
|
||||
|
||||
|
|
|
@ -12,19 +12,20 @@
|
|||
|
||||
all() ->
|
||||
[
|
||||
{group, tests}
|
||||
{group, tests}
|
||||
].
|
||||
|
||||
|
||||
all_tests() ->
|
||||
[
|
||||
basics,
|
||||
machine_upgrade,
|
||||
many_downs
|
||||
basics,
|
||||
machine_upgrade,
|
||||
many_downs
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{tests, [], all_tests()}
|
||||
{tests, [], all_tests()}
|
||||
].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
|
@ -52,16 +53,13 @@ end_per_testcase(_TestCase, _Config) ->
|
|||
basics(_Config) ->
|
||||
S0 = mqtt_machine:init(#{}),
|
||||
ClientId = <<"id1">>,
|
||||
OthPid = spawn(fun() -> ok end),
|
||||
OthPid = spawn(fun () -> ok end),
|
||||
{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{pids = Pids} when map_size(Pids) == 1, S1),
|
||||
{S2, ok, _} = mqtt_machine:apply(meta(2), {register, ClientId, OthPid}, S1),
|
||||
?assertMatch(
|
||||
#machine_state{client_ids = #{ClientId := OthPid} = Ids} when
|
||||
map_size(Ids) == 1,
|
||||
S2
|
||||
),
|
||||
?assertMatch(#machine_state{client_ids = #{ClientId := OthPid} = Ids}
|
||||
when map_size(Ids) == 1, S2),
|
||||
{S3, ok, _} = mqtt_machine:apply(meta(3), {down, OthPid, noproc}, S2),
|
||||
?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 0, S3),
|
||||
{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),
|
||||
?assertMatch({machine_state, Ids} when map_size(Ids) == 1, S1),
|
||||
{S2, ok, _} = mqtt_machine:apply(meta(2), {machine_version, 0, 1}, S1),
|
||||
?assertMatch(
|
||||
#machine_state{
|
||||
client_ids = #{ClientId := Self},
|
||||
pids = #{Self := [ClientId]} = Pids
|
||||
} when
|
||||
map_size(Pids) == 1,
|
||||
S2
|
||||
),
|
||||
?assertMatch(#machine_state{client_ids = #{ClientId := Self},
|
||||
pids = #{Self := [ClientId]} = Pids}
|
||||
when map_size(Pids) == 1, S2),
|
||||
{S3, ok, _} = mqtt_machine:apply(meta(3), {down, self(), noproc}, S2),
|
||||
?assertMatch(
|
||||
#machine_state{
|
||||
client_ids = Ids,
|
||||
pids = Pids
|
||||
} when
|
||||
map_size(Ids) == 0 andalso map_size(Pids) == 0,
|
||||
S3
|
||||
),
|
||||
?assertMatch(#machine_state{client_ids = Ids,
|
||||
pids = Pids}
|
||||
when map_size(Ids) == 0 andalso map_size(Pids) == 0, S3),
|
||||
|
||||
ok.
|
||||
|
||||
many_downs(_Config) ->
|
||||
S0 = mqtt_machine:init(#{}),
|
||||
Clients = [
|
||||
{list_to_binary(integer_to_list(I)), spawn(fun() -> ok end)}
|
||||
|| I <- lists:seq(1, 10000)
|
||||
],
|
||||
Clients = [{list_to_binary(integer_to_list(I)), spawn(fun() -> ok end)}
|
||||
|| I <- lists:seq(1, 10000)],
|
||||
S1 = lists:foldl(
|
||||
fun({ClientId, Pid}, Acc0) ->
|
||||
{Acc, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, Pid}, Acc0),
|
||||
Acc
|
||||
end,
|
||||
S0,
|
||||
Clients
|
||||
),
|
||||
fun ({ClientId, Pid}, Acc0) ->
|
||||
{Acc, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, Pid}, Acc0),
|
||||
Acc
|
||||
end, S0, Clients),
|
||||
_ = lists:foldl(
|
||||
fun({_ClientId, Pid}, Acc0) ->
|
||||
{Acc, ok, _} = mqtt_machine:apply(meta(1), {down, Pid, noproc}, Acc0),
|
||||
Acc
|
||||
end,
|
||||
S1,
|
||||
Clients
|
||||
),
|
||||
fun ({_ClientId, Pid}, Acc0) ->
|
||||
{Acc, ok, _} = mqtt_machine:apply(meta(1), {down, Pid, noproc}, Acc0),
|
||||
Acc
|
||||
end, S1, Clients),
|
||||
_ = lists:foldl(
|
||||
fun({ClientId, Pid}, Acc0) ->
|
||||
{Acc, ok, _} = mqtt_machine:apply(meta(1), {unregister, ClientId, Pid}, Acc0),
|
||||
Acc
|
||||
end,
|
||||
S0,
|
||||
Clients
|
||||
),
|
||||
fun ({ClientId, Pid}, Acc0) ->
|
||||
{Acc, ok, _} = mqtt_machine:apply(meta(1), {unregister, ClientId,
|
||||
Pid}, Acc0),
|
||||
Acc
|
||||
end, S0, Clients),
|
||||
|
||||
ok.
|
||||
%% Utility
|
||||
|
||||
meta(Idx) ->
|
||||
#{
|
||||
index => Idx,
|
||||
term => 1,
|
||||
ts => erlang:system_time(millisecond)
|
||||
}.
|
||||
#{index => Idx,
|
||||
term => 1,
|
||||
ts => erlang:system_time(millisecond)}.
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
%%
|
||||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
||||
|
||||
|
||||
-module(processor_SUITE).
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
|
@ -13,17 +14,17 @@
|
|||
|
||||
all() ->
|
||||
[
|
||||
{group, non_parallel_tests}
|
||||
{group, non_parallel_tests}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{non_parallel_tests, [], [
|
||||
ignores_colons_in_username_if_option_set,
|
||||
interprets_colons_in_username_if_option_not_set,
|
||||
get_vhosts_from_global_runtime_parameter,
|
||||
get_vhost
|
||||
]}
|
||||
{non_parallel_tests, [], [
|
||||
ignores_colons_in_username_if_option_set,
|
||||
interprets_colons_in_username_if_option_not_set,
|
||||
get_vhosts_from_global_runtime_parameter,
|
||||
get_vhost
|
||||
]}
|
||||
].
|
||||
|
||||
suite() ->
|
||||
|
@ -41,50 +42,35 @@ init_per_testcase(get_vhost, Config) ->
|
|||
mnesia:start(),
|
||||
mnesia:create_table(rabbit_runtime_parameters, [
|
||||
{attributes, record_info(fields, runtime_parameters)},
|
||||
{record_name, runtime_parameters}
|
||||
]),
|
||||
{record_name, runtime_parameters}]),
|
||||
Config;
|
||||
init_per_testcase(_, Config) ->
|
||||
Config.
|
||||
init_per_testcase(_, Config) -> Config.
|
||||
end_per_testcase(get_vhost, Config) ->
|
||||
mnesia:stop(),
|
||||
Config;
|
||||
end_per_testcase(_, Config) ->
|
||||
Config.
|
||||
end_per_testcase(_, Config) -> Config.
|
||||
|
||||
ignore_colons(B) -> application:set_env(rabbitmq_mqtt, ignore_colons_in_username, B).
|
||||
|
||||
ignores_colons_in_username_if_option_set(_Config) ->
|
||||
ignore_colons(true),
|
||||
?assertEqual(
|
||||
{rabbit_mqtt_util:env(vhost), <<"a:b:c">>},
|
||||
rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)
|
||||
).
|
||||
?assertEqual({rabbit_mqtt_util:env(vhost), <<"a:b:c">>},
|
||||
rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)).
|
||||
|
||||
interprets_colons_in_username_if_option_not_set(_Config) ->
|
||||
ignore_colons(false),
|
||||
?assertEqual(
|
||||
{<<"a:b">>, <<"c">>},
|
||||
rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)
|
||||
).
|
||||
ignore_colons(false),
|
||||
?assertEqual({<<"a:b">>, <<"c">>},
|
||||
rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)).
|
||||
|
||||
get_vhosts_from_global_runtime_parameter(_Config) ->
|
||||
MappingParameter = [
|
||||
{<<"O=client,CN=dummy1">>, <<"vhost1">>},
|
||||
{<<"O=client,CN=dummy2">>, <<"vhost2">>}
|
||||
],
|
||||
<<"vhost1">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(
|
||||
<<"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
|
||||
),
|
||||
undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(
|
||||
<<"O=client,CN=dummy3">>, not_found
|
||||
).
|
||||
<<"vhost1">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"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),
|
||||
undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy3">>, not_found).
|
||||
|
||||
get_vhost(_Config) ->
|
||||
clear_vhost_global_parameters(),
|
||||
|
@ -97,35 +83,27 @@ get_vhost(_Config) ->
|
|||
|
||||
%% not a certificate user, no cert/vhost mapping, vhost in user
|
||||
%% should use vhost in user
|
||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
||||
<<"somevhost:guest">>, none, 1883
|
||||
),
|
||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"somevhost:guest">>, none, 1883),
|
||||
clear_vhost_global_parameters(),
|
||||
|
||||
%% certificate user, no cert/vhost mapping
|
||||
%% should use default vhost
|
||||
{_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
||||
),
|
||||
{_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||
clear_vhost_global_parameters(),
|
||||
|
||||
%% certificate user, cert/vhost mapping with global runtime parameter
|
||||
%% should use mapping
|
||||
set_global_parameter(mqtt_default_vhosts, [
|
||||
{<<"O=client,CN=dummy">>, <<"somevhost">>},
|
||||
{<<"O=client,CN=dummy">>, <<"somevhost">>},
|
||||
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
||||
]),
|
||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
||||
),
|
||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||
clear_vhost_global_parameters(),
|
||||
|
||||
%% certificate user, cert/vhost mapping with global runtime parameter, but no key for the user
|
||||
%% should use default vhost
|
||||
set_global_parameter(mqtt_default_vhosts, [{<<"O=client,CN=otheruser">>, <<"somevhost">>}]),
|
||||
{_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
||||
),
|
||||
{_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||
clear_vhost_global_parameters(),
|
||||
|
||||
%% not a certificate user, port/vhost mapping
|
||||
|
@ -143,9 +121,7 @@ get_vhost(_Config) ->
|
|||
{<<"1883">>, <<"somevhost">>},
|
||||
{<<"1884">>, <<"othervhost">>}
|
||||
]),
|
||||
{_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
||||
<<"vhostinusername:guest">>, none, 1883
|
||||
),
|
||||
{_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"vhostinusername:guest">>, none, 1883),
|
||||
clear_vhost_global_parameters(),
|
||||
|
||||
%% not a certificate user, port/vhost mapping, but no mapping for this port
|
||||
|
@ -162,50 +138,42 @@ get_vhost(_Config) ->
|
|||
{<<"1883">>, <<"somevhost">>},
|
||||
{<<"1884">>, <<"othervhost">>}
|
||||
]),
|
||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
||||
),
|
||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||
clear_vhost_global_parameters(),
|
||||
|
||||
%% certificate user, port/vhost parameter but no mapping, cert/vhost mapping
|
||||
%% should use cert/vhost mapping
|
||||
set_global_parameter(mqtt_default_vhosts, [
|
||||
{<<"O=client,CN=dummy">>, <<"somevhost">>},
|
||||
{<<"O=client,CN=dummy">>, <<"somevhost">>},
|
||||
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
||||
]),
|
||||
set_global_parameter(mqtt_port_to_vhost_mapping, [
|
||||
{<<"1884">>, <<"othervhost">>}
|
||||
]),
|
||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
||||
),
|
||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||
clear_vhost_global_parameters(),
|
||||
|
||||
%% certificate user, port/vhost parameter, cert/vhost parameter
|
||||
%% cert/vhost parameter takes precedence
|
||||
set_global_parameter(mqtt_default_vhosts, [
|
||||
{<<"O=client,CN=dummy">>, <<"cert-somevhost">>},
|
||||
{<<"O=client,CN=dummy">>, <<"cert-somevhost">>},
|
||||
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
||||
]),
|
||||
set_global_parameter(mqtt_port_to_vhost_mapping, [
|
||||
{<<"1883">>, <<"port-vhost">>},
|
||||
{<<"1884">>, <<"othervhost">>}
|
||||
]),
|
||||
{_, {<<"cert-somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
||||
),
|
||||
{_, {<<"cert-somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||
clear_vhost_global_parameters(),
|
||||
|
||||
%% certificate user, no port/vhost or cert/vhost mapping, vhost in username
|
||||
%% should use vhost in username
|
||||
{_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
||||
<<"vhostinusername:guest">>, <<"O=client,CN=dummy">>, 1883
|
||||
),
|
||||
{_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"vhostinusername:guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||
|
||||
%% not a certificate user, port/vhost parameter, cert/vhost parameter
|
||||
%% port/vhost mapping is used, as cert/vhost should not be used
|
||||
set_global_parameter(mqtt_default_vhosts, [
|
||||
{<<"O=cert">>, <<"cert-somevhost">>},
|
||||
{<<"O=cert">>, <<"cert-somevhost">>},
|
||||
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
||||
]),
|
||||
set_global_parameter(mqtt_port_to_vhost_mapping, [
|
||||
|
@ -217,15 +185,15 @@ get_vhost(_Config) ->
|
|||
ok.
|
||||
|
||||
set_global_parameter(Key, Term) ->
|
||||
InsertParameterFun = fun() ->
|
||||
InsertParameterFun = fun () ->
|
||||
mnesia:write(rabbit_runtime_parameters, #runtime_parameters{key = Key, value = Term}, write)
|
||||
end,
|
||||
end,
|
||||
|
||||
{atomic, ok} = mnesia:transaction(InsertParameterFun).
|
||||
|
||||
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_port_to_vhost_mapping, write)
|
||||
end,
|
||||
end,
|
||||
{atomic, ok} = mnesia:transaction(DeleteParameterFun).
|
||||
|
|
|
@ -34,26 +34,21 @@ init_per_suite(Config) ->
|
|||
{rabbitmq_ct_tls_verify, verify_none}
|
||||
]),
|
||||
MqttConfig = mqtt_config(),
|
||||
rabbit_ct_helpers:run_setup_steps(
|
||||
Config1,
|
||||
[fun(Conf) -> merge_app_env(MqttConfig, Conf) end] ++
|
||||
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||
[ fun(Conf) -> merge_app_env(MqttConfig, Conf) end ] ++
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()
|
||||
).
|
||||
rabbit_ct_client_helpers:setup_steps()).
|
||||
|
||||
mqtt_config() ->
|
||||
{rabbitmq_mqtt, [
|
||||
{proxy_protocol, true},
|
||||
{ssl_cert_login, true},
|
||||
{allow_anonymous, true}
|
||||
]}.
|
||||
{proxy_protocol, true},
|
||||
{ssl_cert_login, true},
|
||||
{allow_anonymous, true}]}.
|
||||
|
||||
end_per_suite(Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(
|
||||
Config,
|
||||
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()
|
||||
).
|
||||
rabbit_ct_broker_helpers:teardown_steps()).
|
||||
|
||||
init_per_group(_, Config) -> Config.
|
||||
end_per_group(_, Config) -> Config.
|
||||
|
@ -66,11 +61,8 @@ end_per_testcase(Testcase, Config) ->
|
|||
|
||||
proxy_protocol(Config) ->
|
||||
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt),
|
||||
{ok, Socket} = gen_tcp:connect(
|
||||
{127, 0, 0, 1},
|
||||
Port,
|
||||
[binary, {active, false}, {packet, raw}]
|
||||
),
|
||||
{ok, Socket} = gen_tcp:connect({127,0,0,1}, 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, mqtt_3_1_1_connect_packet()),
|
||||
{ok, _Packet} = gen_tcp:recv(Socket, 0, ?TIMEOUT),
|
||||
|
@ -83,11 +75,8 @@ proxy_protocol(Config) ->
|
|||
proxy_protocol_tls(Config) ->
|
||||
app_utils:start_applications([asn1, crypto, public_key, ssl]),
|
||||
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt_tls),
|
||||
{ok, Socket} = gen_tcp:connect(
|
||||
{127, 0, 0, 1},
|
||||
Port,
|
||||
[binary, {active, false}, {packet, raw}]
|
||||
),
|
||||
{ok, Socket} = gen_tcp:connect({127,0,0,1}, 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, SslSocket} = ssl:connect(Socket, [], ?TIMEOUT),
|
||||
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).
|
||||
|
||||
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,
|
||||
109, 101, 114>>.
|
||||
<<16,
|
||||
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>>.
|
||||
|
|
|
@ -13,16 +13,11 @@
|
|||
-behaviour(rabbit_authn_backend).
|
||||
-behaviour(rabbit_authz_backend).
|
||||
|
||||
-export([
|
||||
setup/1,
|
||||
user_login_authentication/2,
|
||||
user_login_authorization/2,
|
||||
check_vhost_access/3,
|
||||
check_resource_access/4,
|
||||
check_topic_access/4,
|
||||
state_can_expire/0,
|
||||
get/1
|
||||
]).
|
||||
-export([setup/1,
|
||||
user_login_authentication/2, user_login_authorization/2,
|
||||
check_vhost_access/3, check_resource_access/4, check_topic_access/4,
|
||||
state_can_expire/0,
|
||||
get/1]).
|
||||
|
||||
setup(CallerPid) ->
|
||||
ets:new(?MODULE, [set, public, named_table]),
|
||||
|
@ -31,13 +26,12 @@ setup(CallerPid) ->
|
|||
stop -> ok
|
||||
end.
|
||||
|
||||
|
||||
user_login_authentication(_, AuthProps) ->
|
||||
ets:insert(?MODULE, {authentication, AuthProps}),
|
||||
{ok, #auth_user{
|
||||
username = <<"dummy">>,
|
||||
tags = [],
|
||||
impl = none
|
||||
}}.
|
||||
{ok, #auth_user{username = <<"dummy">>,
|
||||
tags = [],
|
||||
impl = none}}.
|
||||
|
||||
user_login_authorization(_, _) ->
|
||||
io:format("login authorization"),
|
||||
|
|
|
@ -5,45 +5,42 @@
|
|||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
||||
%%
|
||||
-module(reader_SUITE).
|
||||
-compile([
|
||||
export_all,
|
||||
nowarn_export_all
|
||||
]).
|
||||
-compile([export_all,
|
||||
nowarn_export_all]).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-import(rabbit_ct_broker_helpers, [rpc/4]).
|
||||
-import(rabbit_ct_helpers, [eventually/3]).
|
||||
-import(util, [
|
||||
all_connection_pids/1,
|
||||
publish_qos1_timeout/4,
|
||||
expect_publishes/3,
|
||||
connect/2,
|
||||
connect/3,
|
||||
await_exit/1
|
||||
]).
|
||||
-import(util, [all_connection_pids/1,
|
||||
publish_qos1_timeout/4,
|
||||
expect_publishes/3,
|
||||
connect/2,
|
||||
connect/3,
|
||||
await_exit/1]).
|
||||
|
||||
all() ->
|
||||
[
|
||||
{group, tests}
|
||||
{group, tests}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{tests, [], [
|
||||
block_connack_timeout,
|
||||
handle_invalid_packets,
|
||||
login_timeout,
|
||||
stats,
|
||||
quorum_clean_session_false,
|
||||
quorum_clean_session_true,
|
||||
classic_clean_session_true,
|
||||
classic_clean_session_false,
|
||||
non_clean_sess_empty_client_id,
|
||||
event_authentication_failure,
|
||||
rabbit_mqtt_qos0_queue_overflow
|
||||
]}
|
||||
{tests, [],
|
||||
[
|
||||
block_connack_timeout,
|
||||
handle_invalid_packets,
|
||||
login_timeout,
|
||||
stats,
|
||||
quorum_clean_session_false,
|
||||
quorum_clean_session_true,
|
||||
classic_clean_session_true,
|
||||
classic_clean_session_false,
|
||||
non_clean_sess_empty_client_id,
|
||||
event_authentication_failure,
|
||||
rabbit_mqtt_qos0_queue_overflow
|
||||
]}
|
||||
].
|
||||
|
||||
suite() ->
|
||||
|
@ -54,36 +51,28 @@ suite() ->
|
|||
%% -------------------------------------------------------------------
|
||||
|
||||
merge_app_env(Config) ->
|
||||
rabbit_ct_helpers:merge_app_env(
|
||||
Config,
|
||||
{rabbit, [
|
||||
{collect_statistics, basic},
|
||||
{collect_statistics_interval, 100}
|
||||
]}
|
||||
).
|
||||
rabbit_ct_helpers:merge_app_env(Config,
|
||||
{rabbit, [
|
||||
{collect_statistics, basic},
|
||||
{collect_statistics_interval, 100}
|
||||
]}).
|
||||
|
||||
init_per_suite(Config) ->
|
||||
rabbit_ct_helpers:log_environment(),
|
||||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||
{rmq_nodename_suffix, ?MODULE},
|
||||
{rmq_extra_tcp_ports, [
|
||||
tcp_port_mqtt_extra,
|
||||
tcp_port_mqtt_tls_extra
|
||||
]}
|
||||
]),
|
||||
rabbit_ct_helpers:run_setup_steps(
|
||||
Config1,
|
||||
[fun merge_app_env/1] ++
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()
|
||||
).
|
||||
{rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
|
||||
tcp_port_mqtt_tls_extra]}
|
||||
]),
|
||||
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||
[ fun merge_app_env/1 ] ++
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()).
|
||||
|
||||
end_per_suite(Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()
|
||||
).
|
||||
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()).
|
||||
|
||||
init_per_group(_, Config) ->
|
||||
Config.
|
||||
|
@ -97,6 +86,7 @@ init_per_testcase(Testcase, Config) ->
|
|||
end_per_testcase(Testcase, Config) ->
|
||||
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
||||
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Testsuite cases
|
||||
%% -------------------------------------------------------------------
|
||||
|
@ -110,13 +100,11 @@ block_connack_timeout(Config) ->
|
|||
timer:sleep(100),
|
||||
|
||||
%% We can still connect via TCP, but CONNECT packet will not be processed on the server.
|
||||
{ok, Client} = emqtt:start_link([
|
||||
{host, "localhost"},
|
||||
{port, P},
|
||||
{clientid, atom_to_binary(?FUNCTION_NAME)},
|
||||
{proto_ver, v4},
|
||||
{connect_timeout, 1}
|
||||
]),
|
||||
{ok, Client} = emqtt:start_link([{host, "localhost"},
|
||||
{port, P},
|
||||
{clientid, atom_to_binary(?FUNCTION_NAME)},
|
||||
{proto_ver, v4},
|
||||
{connect_timeout, 1}]),
|
||||
unlink(Client),
|
||||
ClientMRef = monitor(process, Client),
|
||||
{error, connack_timeout} = emqtt:connect(Client),
|
||||
|
@ -124,7 +112,7 @@ block_connack_timeout(Config) ->
|
|||
{'DOWN', ClientMRef, process, Client, connack_timeout} ->
|
||||
ok
|
||||
after 200 ->
|
||||
ct:fail("missing connack_timeout in client")
|
||||
ct:fail("missing connack_timeout in client")
|
||||
end,
|
||||
|
||||
Ports = rpc(Config, erlang, ports, []),
|
||||
|
@ -142,7 +130,7 @@ block_connack_timeout(Config) ->
|
|||
%% because our client already disconnected.
|
||||
ok
|
||||
after 2000 ->
|
||||
ct:fail("missing peername_not_known from server")
|
||||
ct:fail("missing peername_not_known from server")
|
||||
end,
|
||||
%% Ensure that our client is not registered.
|
||||
?assertEqual([], all_connection_pids(Config)),
|
||||
|
@ -182,12 +170,8 @@ stats(Config) ->
|
|||
[{Pid, Props}] = rpc(Config, ets, lookup, [connection_metrics, Pid]),
|
||||
true = proplists:is_defined(garbage_collection, Props),
|
||||
%% If the coarse entry is present, stats were successfully emitted
|
||||
[{Pid, _, _, _, _}] = rpc(
|
||||
Config,
|
||||
ets,
|
||||
lookup,
|
||||
[connection_coarse_metrics, Pid]
|
||||
),
|
||||
[{Pid, _, _, _, _}] = rpc(Config, ets, lookup,
|
||||
[connection_coarse_metrics, Pid]),
|
||||
ok = emqtt:disconnect(C).
|
||||
|
||||
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).
|
||||
|
||||
classic_clean_session_false(Config) ->
|
||||
validate_durable_queue_type(
|
||||
Config, <<"classicCleanSessionFalse">>, false, rabbit_classic_queue
|
||||
).
|
||||
validate_durable_queue_type(Config, <<"classicCleanSessionFalse">>, false, rabbit_classic_queue).
|
||||
|
||||
%% "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
|
||||
%% (Identifier rejected) and then close the Network Connection" [MQTT-3.1.3-8].
|
||||
non_clean_sess_empty_client_id(Config) ->
|
||||
{ok, C} = emqtt:start_link(
|
||||
[
|
||||
{clientid, <<>>},
|
||||
{clean_start, false},
|
||||
{proto_ver, v4},
|
||||
{host, "localhost"},
|
||||
{port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)}
|
||||
]
|
||||
),
|
||||
[{clientid, <<>>},
|
||||
{clean_start, false},
|
||||
{proto_ver, v4},
|
||||
{host, "localhost"},
|
||||
{port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)}
|
||||
]),
|
||||
process_flag(trap_exit, true),
|
||||
?assertMatch(
|
||||
{error, {client_identifier_not_valid, _}},
|
||||
emqtt:connect(C)
|
||||
),
|
||||
?assertMatch({error, {client_identifier_not_valid, _}},
|
||||
emqtt:connect(C)),
|
||||
ok = await_exit(C).
|
||||
|
||||
event_authentication_failure(Config) ->
|
||||
{ok, C} = emqtt:start_link(
|
||||
[
|
||||
{username, <<"Trudy">>},
|
||||
{password, <<"fake-password">>},
|
||||
{host, "localhost"},
|
||||
{port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)},
|
||||
{clientid, atom_to_binary(?FUNCTION_NAME)},
|
||||
{proto_ver, v4}
|
||||
]
|
||||
),
|
||||
[{username, <<"Trudy">>},
|
||||
{password, <<"fake-password">>},
|
||||
{host, "localhost"},
|
||||
{port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)},
|
||||
{clientid, atom_to_binary(?FUNCTION_NAME)},
|
||||
{proto_ver, v4}]),
|
||||
true = unlink(C),
|
||||
|
||||
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),
|
||||
util:assert_event_type(user_authentication_failure, E),
|
||||
util:assert_event_prop(
|
||||
[
|
||||
{name, <<"Trudy">>},
|
||||
{connection_type, network}
|
||||
],
|
||||
E
|
||||
),
|
||||
util:assert_event_prop([{name, <<"Trudy">>},
|
||||
{connection_type, network}],
|
||||
E),
|
||||
|
||||
ok = gen_event:delete_handler({rabbit_event, Server}, event_recorder, []).
|
||||
|
||||
|
@ -295,12 +266,8 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
|
|||
NumMsgs = 10_000,
|
||||
|
||||
%% Provoke TCP back-pressure from client to server by using very small buffers.
|
||||
Opts = [
|
||||
{tcp_opts, [
|
||||
{recbuf, 512},
|
||||
{buffer, 512}
|
||||
]}
|
||||
],
|
||||
Opts = [{tcp_opts, [{recbuf, 512},
|
||||
{buffer, 512}]}],
|
||||
Sub = connect(<<"subscriber">>, Config, Opts),
|
||||
{ok, _, [0]} = emqtt:subscribe(Sub, Topic, qos0),
|
||||
[ServerConnectionPid] = all_connection_pids(Config),
|
||||
|
@ -312,12 +279,9 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
|
|||
%% Let's overflow the receiving server MQTT connection process
|
||||
%% (i.e. the rabbit_mqtt_qos0_queue) by sending many large messages.
|
||||
Pub = connect(<<"publisher">>, Config),
|
||||
lists:foreach(
|
||||
fun(_) ->
|
||||
ok = emqtt:publish(Pub, Topic, Msg, qos0)
|
||||
end,
|
||||
lists:seq(1, NumMsgs)
|
||||
),
|
||||
lists:foreach(fun(_) ->
|
||||
ok = emqtt:publish(Pub, Topic, Msg, qos0)
|
||||
end, lists:seq(1, NumMsgs)),
|
||||
|
||||
%% Give the server some time to process (either send or drop) the messages.
|
||||
timer:sleep(2000),
|
||||
|
@ -354,11 +318,9 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
|
|||
|
||||
num_received(Topic, Payload, N) ->
|
||||
receive
|
||||
{publish, #{
|
||||
topic := Topic,
|
||||
payload := Payload
|
||||
}} ->
|
||||
{publish, #{topic := Topic,
|
||||
payload := Payload}} ->
|
||||
num_received(Topic, Payload, N + 1)
|
||||
after 1000 ->
|
||||
N
|
||||
N
|
||||
end.
|
||||
|
|
|
@ -8,31 +8,29 @@
|
|||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-import(util, [
|
||||
expect_publishes/3,
|
||||
connect/3
|
||||
]).
|
||||
-import(util, [expect_publishes/3,
|
||||
connect/3]).
|
||||
|
||||
all() ->
|
||||
[
|
||||
{group, dets},
|
||||
{group, ets},
|
||||
{group, noop}
|
||||
{group, dets},
|
||||
{group, ets},
|
||||
{group, noop}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{dets, [], tests()},
|
||||
{ets, [], tests()},
|
||||
{noop, [], [does_not_retain]}
|
||||
{dets, [], tests()},
|
||||
{ets, [], tests()},
|
||||
{noop, [], [does_not_retain]}
|
||||
].
|
||||
|
||||
tests() ->
|
||||
[
|
||||
coerce_configuration_data,
|
||||
should_translate_amqp2mqtt_on_publish,
|
||||
should_translate_amqp2mqtt_on_retention,
|
||||
should_translate_amqp2mqtt_on_retention_search
|
||||
coerce_configuration_data,
|
||||
should_translate_amqp2mqtt_on_publish,
|
||||
should_translate_amqp2mqtt_on_retention,
|
||||
should_translate_amqp2mqtt_on_retention_search
|
||||
].
|
||||
|
||||
suite() ->
|
||||
|
@ -51,38 +49,31 @@ end_per_suite(Config) ->
|
|||
|
||||
init_per_group(Group, Config0) ->
|
||||
Config = rabbit_ct_helpers:set_config(
|
||||
Config0,
|
||||
[
|
||||
{rmq_nodename_suffix, Group},
|
||||
{rmq_extra_tcp_ports, [
|
||||
tcp_port_mqtt_extra,
|
||||
tcp_port_mqtt_tls_extra
|
||||
]}
|
||||
]
|
||||
),
|
||||
Config0,
|
||||
[
|
||||
{rmq_nodename_suffix, Group},
|
||||
{rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
|
||||
tcp_port_mqtt_tls_extra]}
|
||||
]),
|
||||
Mod = list_to_atom("rabbit_mqtt_retained_msg_store_" ++ atom_to_list(Group)),
|
||||
Env = [
|
||||
{rabbitmq_mqtt, [{retained_message_store, Mod}]},
|
||||
{rabbit, [
|
||||
{default_user, "guest"},
|
||||
{default_pass, "guest"},
|
||||
{default_vhost, "/"},
|
||||
{default_permissions, [".*", ".*", ".*"]}
|
||||
]}
|
||||
],
|
||||
Env = [{rabbitmq_mqtt, [{retained_message_store, Mod}]},
|
||||
{rabbit, [
|
||||
{default_user, "guest"},
|
||||
{default_pass, "guest"},
|
||||
{default_vhost, "/"},
|
||||
{default_permissions, [".*", ".*", ".*"]}
|
||||
]}],
|
||||
rabbit_ct_helpers:run_setup_steps(
|
||||
Config,
|
||||
[fun(Conf) -> rabbit_ct_helpers:merge_app_env(Conf, Env) end] ++
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()
|
||||
).
|
||||
Config,
|
||||
[fun(Conf) -> rabbit_ct_helpers:merge_app_env(Conf, Env) end] ++
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()).
|
||||
|
||||
end_per_group(_, Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()
|
||||
).
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()).
|
||||
|
||||
init_per_testcase(Testcase, Config) ->
|
||||
rabbit_ct_helpers:testcase_started(Config, Testcase).
|
||||
|
@ -90,6 +81,7 @@ init_per_testcase(Testcase, Config) ->
|
|||
end_per_testcase(Testcase, Config) ->
|
||||
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
||||
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Testsuite cases
|
||||
%% -------------------------------------------------------------------
|
||||
|
@ -112,7 +104,7 @@ should_translate_amqp2mqtt_on_publish(Config) ->
|
|||
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
|
||||
%% there's an active consumer
|
||||
{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 = emqtt:disconnect(C).
|
||||
|
||||
|
@ -124,7 +116,7 @@ should_translate_amqp2mqtt_on_publish(Config) ->
|
|||
should_translate_amqp2mqtt_on_retention(Config) ->
|
||||
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
|
||||
%% 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 = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
|
||||
ok = emqtt:disconnect(C).
|
||||
|
@ -136,19 +128,19 @@ should_translate_amqp2mqtt_on_retention(Config) ->
|
|||
%% -------------------------------------------------------------------
|
||||
should_translate_amqp2mqtt_on_retention_search(Config) ->
|
||||
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 = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
|
||||
ok = emqtt:disconnect(C).
|
||||
|
||||
does_not_retain(Config) ->
|
||||
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),
|
||||
receive
|
||||
Unexpected ->
|
||||
ct:fail("Unexpected message: ~p", [Unexpected])
|
||||
after 1000 ->
|
||||
ok
|
||||
ok
|
||||
end,
|
||||
ok = emqtt:disconnect(C).
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,58 +4,46 @@
|
|||
-include_lib("rabbit_common/include/rabbit.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([
|
||||
all_connection_pids/1,
|
||||
publish_qos1_timeout/4,
|
||||
sync_publish_result/3,
|
||||
get_global_counters/2,
|
||||
get_global_counters/3,
|
||||
get_global_counters/4,
|
||||
expect_publishes/3,
|
||||
connect/2,
|
||||
connect/3,
|
||||
connect/4,
|
||||
get_events/1,
|
||||
assert_event_type/2,
|
||||
assert_event_prop/2,
|
||||
await_exit/1,
|
||||
await_exit/2
|
||||
]).
|
||||
-export([all_connection_pids/1,
|
||||
publish_qos1_timeout/4,
|
||||
sync_publish_result/3,
|
||||
get_global_counters/2,
|
||||
get_global_counters/3,
|
||||
get_global_counters/4,
|
||||
expect_publishes/3,
|
||||
connect/2,
|
||||
connect/3,
|
||||
connect/4,
|
||||
get_events/1,
|
||||
assert_event_type/2,
|
||||
assert_event_prop/2,
|
||||
await_exit/1,
|
||||
await_exit/2
|
||||
]).
|
||||
|
||||
all_connection_pids(Config) ->
|
||||
Nodes = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
|
||||
Result = erpc:multicall(Nodes, rabbit_mqtt, local_connection_pids, [], 5000),
|
||||
lists:foldl(
|
||||
fun
|
||||
({ok, Pids}, Acc) ->
|
||||
Pids ++ Acc;
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end,
|
||||
[],
|
||||
Result
|
||||
).
|
||||
lists:foldl(fun({ok, Pids}, Acc) ->
|
||||
Pids ++ Acc;
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, [], Result).
|
||||
|
||||
publish_qos1_timeout(Client, Topic, Payload, Timeout) ->
|
||||
Mref = erlang:monitor(process, Client),
|
||||
ok = emqtt:publish_async(
|
||||
Client,
|
||||
Topic,
|
||||
#{},
|
||||
Payload,
|
||||
[{qos, 1}],
|
||||
infinity,
|
||||
{fun ?MODULE:sync_publish_result/3, [self(), Mref]}
|
||||
),
|
||||
ok = emqtt:publish_async(Client, Topic, #{}, Payload, [{qos, 1}], infinity,
|
||||
{fun ?MODULE:sync_publish_result/3, [self(), Mref]}),
|
||||
receive
|
||||
{Mref, Reply} ->
|
||||
erlang:demonitor(Mref, [flush]),
|
||||
Reply;
|
||||
{'DOWN', Mref, process, Client, Reason} ->
|
||||
ct:fail("client is down: ~tp", [Reason])
|
||||
after Timeout ->
|
||||
erlang:demonitor(Mref, [flush]),
|
||||
puback_timeout
|
||||
after
|
||||
Timeout ->
|
||||
erlang:demonitor(Mref, [flush]),
|
||||
puback_timeout
|
||||
end.
|
||||
|
||||
sync_publish_result(Caller, Mref, Result) ->
|
||||
|
@ -63,27 +51,20 @@ sync_publish_result(Caller, Mref, Result) ->
|
|||
|
||||
expect_publishes(_, _, []) ->
|
||||
ok;
|
||||
expect_publishes(Client, Topic, [Payload | Rest]) when
|
||||
is_pid(Client)
|
||||
->
|
||||
expect_publishes(Client, Topic, [Payload|Rest])
|
||||
when is_pid(Client) ->
|
||||
receive
|
||||
{publish, #{
|
||||
client_pid := Client,
|
||||
topic := Topic,
|
||||
payload := Payload
|
||||
}} ->
|
||||
{publish, #{client_pid := Client,
|
||||
topic := Topic,
|
||||
payload := Payload}} ->
|
||||
expect_publishes(Client, Topic, Rest);
|
||||
{publish, #{
|
||||
client_pid := Client,
|
||||
topic := Topic,
|
||||
payload := Other
|
||||
}} ->
|
||||
ct:fail(
|
||||
"Received unexpected PUBLISH payload. Expected: ~p Got: ~p",
|
||||
[Payload, Other]
|
||||
)
|
||||
{publish, #{client_pid := Client,
|
||||
topic := Topic,
|
||||
payload := Other}} ->
|
||||
ct:fail("Received unexpected PUBLISH payload. Expected: ~p Got: ~p",
|
||||
[Payload, Other])
|
||||
after 3000 ->
|
||||
{publish_not_received, Payload}
|
||||
{publish_not_received, Payload}
|
||||
end.
|
||||
|
||||
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, ?MQTT_PROTO_V4, Node, QType);
|
||||
get_global_counters(Config, Proto, Node, QType) ->
|
||||
maps:get(
|
||||
[{protocol, Proto}] ++ QType,
|
||||
rabbit_ct_broker_helpers:rpc(Config, Node, rabbit_global_counters, overview, [])
|
||||
).
|
||||
maps:get([{protocol, Proto}] ++ QType,
|
||||
rabbit_ct_broker_helpers:rpc(Config, Node, rabbit_global_counters, overview, [])).
|
||||
|
||||
get_events(Node) ->
|
||||
%% events are sent and processed asynchronously
|
||||
timer:sleep(300),
|
||||
timer:sleep(300), %% events are sent and processed asynchronously
|
||||
Result = gen_event:call({rabbit_event, Node}, event_recorder, take_state),
|
||||
?assert(is_list(Result)),
|
||||
Result.
|
||||
|
@ -114,26 +92,24 @@ assert_event_type(ExpectedType, #event{type = ActualType}) ->
|
|||
|
||||
assert_event_prop(ExpectedProp = {Key, _Value}, #event{props = Props}) ->
|
||||
?assertEqual(ExpectedProp, lists:keyfind(Key, 1, Props));
|
||||
assert_event_prop(ExpectedProps, Event) when
|
||||
is_list(ExpectedProps)
|
||||
->
|
||||
lists:foreach(
|
||||
fun(P) ->
|
||||
assert_event_prop(P, Event)
|
||||
end,
|
||||
ExpectedProps
|
||||
).
|
||||
assert_event_prop(ExpectedProps, Event)
|
||||
when is_list(ExpectedProps) ->
|
||||
lists:foreach(fun(P) ->
|
||||
assert_event_prop(P, Event)
|
||||
end, ExpectedProps).
|
||||
|
||||
await_exit(Pid) ->
|
||||
receive
|
||||
{'EXIT', Pid, _} -> ok
|
||||
after 20_000 -> ct:fail({missing_exit, Pid})
|
||||
after
|
||||
20_000 -> ct:fail({missing_exit, Pid})
|
||||
end.
|
||||
|
||||
await_exit(Pid, Reason) ->
|
||||
receive
|
||||
{'EXIT', Pid, Reason} -> ok
|
||||
after 20_000 -> ct:fail({missing_exit, Pid})
|
||||
after
|
||||
20_000 -> ct:fail({missing_exit, Pid})
|
||||
end.
|
||||
|
||||
connect(ClientId, Config) ->
|
||||
|
@ -144,27 +120,21 @@ connect(ClientId, Config, AdditionalOpts) ->
|
|||
|
||||
connect(ClientId, Config, Node, AdditionalOpts) ->
|
||||
{Port, WsOpts, Connect} =
|
||||
case rabbit_ct_helpers:get_config(Config, websocket, false) of
|
||||
false ->
|
||||
{
|
||||
rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_mqtt),
|
||||
[],
|
||||
fun emqtt:connect/1
|
||||
};
|
||||
true ->
|
||||
{
|
||||
rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_web_mqtt),
|
||||
[{ws_path, "/ws"}],
|
||||
fun emqtt:ws_connect/1
|
||||
}
|
||||
end,
|
||||
Options =
|
||||
[
|
||||
{host, "localhost"},
|
||||
{port, Port},
|
||||
{proto_ver, v4},
|
||||
{clientid, rabbit_data_coercion:to_binary(ClientId)}
|
||||
] ++ WsOpts ++ AdditionalOpts,
|
||||
case rabbit_ct_helpers:get_config(Config, websocket, false) of
|
||||
false ->
|
||||
{rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_mqtt),
|
||||
[],
|
||||
fun emqtt:connect/1};
|
||||
true ->
|
||||
{rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_web_mqtt),
|
||||
[{ws_path, "/ws"}],
|
||||
fun emqtt:ws_connect/1}
|
||||
end,
|
||||
Options = [{host, "localhost"},
|
||||
{port, Port},
|
||||
{proto_ver, v4},
|
||||
{clientid, rabbit_data_coercion:to_binary(ClientId)}
|
||||
] ++ WsOpts ++ AdditionalOpts,
|
||||
{ok, C} = emqtt:start_link(Options),
|
||||
{ok, _Properties} = Connect(C),
|
||||
C.
|
||||
|
|
|
@ -12,18 +12,19 @@
|
|||
|
||||
all() ->
|
||||
[
|
||||
{group, tests}
|
||||
{group, tests}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{tests, [parallel], [
|
||||
coerce_exchange,
|
||||
coerce_vhost,
|
||||
coerce_default_user,
|
||||
coerce_default_pass,
|
||||
mqtt_amqp_topic_translation
|
||||
]}
|
||||
{tests, [parallel], [
|
||||
coerce_exchange,
|
||||
coerce_vhost,
|
||||
coerce_default_user,
|
||||
coerce_default_pass,
|
||||
mqtt_amqp_topic_translation
|
||||
]
|
||||
}
|
||||
].
|
||||
|
||||
suite() ->
|
||||
|
|
|
@ -49,7 +49,7 @@ init([]) -> {ok, {{one_for_one, 1, 5}, []}}.
|
|||
-spec list_connections() -> [pid()].
|
||||
list_connections() ->
|
||||
PlainPids = connection_pids_of_protocol(?TCP_PROTOCOL),
|
||||
TLSPids = connection_pids_of_protocol(?TLS_PROTOCOL),
|
||||
TLSPids = connection_pids_of_protocol(?TLS_PROTOCOL),
|
||||
PlainPids ++ TLSPids.
|
||||
|
||||
%%
|
||||
|
@ -58,8 +58,7 @@ list_connections() ->
|
|||
|
||||
connection_pids_of_protocol(Protocol) ->
|
||||
case rabbit_networking:ranch_ref_of_protocol(Protocol) of
|
||||
undefined ->
|
||||
[];
|
||||
undefined -> [];
|
||||
AcceptorRef ->
|
||||
lists:map(fun cowboy_ws_connection_pid/1, ranch:procs(AcceptorRef, connections))
|
||||
end.
|
||||
|
@ -71,38 +70,36 @@ cowboy_ws_connection_pid(RanchConnPid) ->
|
|||
Pid.
|
||||
|
||||
mqtt_init() ->
|
||||
CowboyOpts0 = maps:from_list(get_env(cowboy_opts, [])),
|
||||
CowboyWsOpts = maps:from_list(get_env(cowboy_ws_opts, [])),
|
||||
Routes = cowboy_router:compile([
|
||||
{'_', [
|
||||
{get_env(ws_path, "/ws"), rabbit_web_mqtt_handler, [{ws_opts, CowboyWsOpts}]}
|
||||
]}
|
||||
]),
|
||||
CowboyOpts = CowboyOpts0#{
|
||||
env => #{dispatch => Routes},
|
||||
proxy_header => get_env(proxy_protocol, false),
|
||||
stream_handlers => [rabbit_web_mqtt_stream_handler, cowboy_stream_h]
|
||||
},
|
||||
case get_env(tcp_config, []) of
|
||||
[] -> ok;
|
||||
TCPConf0 -> start_tcp_listener(TCPConf0, CowboyOpts)
|
||||
end,
|
||||
case get_env(ssl_config, []) of
|
||||
[] -> ok;
|
||||
TLSConf0 -> start_tls_listener(TLSConf0, CowboyOpts)
|
||||
end,
|
||||
ok.
|
||||
CowboyOpts0 = maps:from_list(get_env(cowboy_opts, [])),
|
||||
CowboyWsOpts = maps:from_list(get_env(cowboy_ws_opts, [])),
|
||||
Routes = cowboy_router:compile([{'_', [
|
||||
{get_env(ws_path, "/ws"), rabbit_web_mqtt_handler, [{ws_opts, CowboyWsOpts}]}
|
||||
]}]),
|
||||
CowboyOpts = CowboyOpts0#{
|
||||
env => #{dispatch => Routes},
|
||||
proxy_header => get_env(proxy_protocol, false),
|
||||
stream_handlers => [rabbit_web_mqtt_stream_handler, cowboy_stream_h]
|
||||
},
|
||||
case get_env(tcp_config, []) of
|
||||
[] -> ok;
|
||||
TCPConf0 -> start_tcp_listener(TCPConf0, CowboyOpts)
|
||||
end,
|
||||
case get_env(ssl_config, []) of
|
||||
[] -> ok;
|
||||
TLSConf0 -> start_tls_listener(TLSConf0, CowboyOpts)
|
||||
end,
|
||||
ok.
|
||||
|
||||
start_tcp_listener(TCPConf0, CowboyOpts) ->
|
||||
{TCPConf, IpStr, Port} = get_tcp_conf(TCPConf0),
|
||||
RanchRef = rabbit_networking:ranch_ref(TCPConf),
|
||||
RanchTransportOpts =
|
||||
#{
|
||||
socket_opts => TCPConf,
|
||||
max_connections => get_max_connections(),
|
||||
num_acceptors => get_env(num_tcp_acceptors, 10),
|
||||
num_conns_sups => get_env(num_conns_sup, 1)
|
||||
},
|
||||
#{
|
||||
socket_opts => TCPConf,
|
||||
max_connections => get_max_connections(),
|
||||
num_acceptors => get_env(num_tcp_acceptors, 10),
|
||||
num_conns_sups => get_env(num_conns_sup, 1)
|
||||
},
|
||||
case cowboy:start_clear(RanchRef, RanchTransportOpts, CowboyOpts) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
|
@ -110,28 +107,25 @@ start_tcp_listener(TCPConf0, CowboyOpts) ->
|
|||
ok;
|
||||
{error, ErrTCP} ->
|
||||
rabbit_log:error(
|
||||
"Failed to start a WebSocket (HTTP) listener. Error: ~p, listener settings: ~p",
|
||||
[ErrTCP, TCPConf]
|
||||
),
|
||||
"Failed to start a WebSocket (HTTP) listener. Error: ~p, listener settings: ~p",
|
||||
[ErrTCP, TCPConf]),
|
||||
throw(ErrTCP)
|
||||
end,
|
||||
listener_started(?TCP_PROTOCOL, TCPConf),
|
||||
rabbit_log:info(
|
||||
"rabbit_web_mqtt: listening for HTTP connections on ~s:~w",
|
||||
[IpStr, Port]
|
||||
).
|
||||
rabbit_log:info("rabbit_web_mqtt: listening for HTTP connections on ~s:~w",
|
||||
[IpStr, Port]).
|
||||
|
||||
start_tls_listener(TLSConf0, CowboyOpts) ->
|
||||
_ = rabbit_networking:ensure_ssl(),
|
||||
{TLSConf, TLSIpStr, TLSPort} = get_tls_conf(TLSConf0),
|
||||
RanchRef = rabbit_networking:ranch_ref(TLSConf),
|
||||
RanchTransportOpts =
|
||||
#{
|
||||
socket_opts => TLSConf,
|
||||
max_connections => get_max_connections(),
|
||||
num_acceptors => get_env(num_ssl_acceptors, 10),
|
||||
num_conns_sups => get_env(num_conns_sup, 1)
|
||||
},
|
||||
#{
|
||||
socket_opts => TLSConf,
|
||||
max_connections => get_max_connections(),
|
||||
num_acceptors => get_env(num_ssl_acceptors, 10),
|
||||
num_conns_sups => get_env(num_conns_sup, 1)
|
||||
},
|
||||
case cowboy:start_tls(RanchRef, RanchTransportOpts, CowboyOpts) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
|
@ -139,45 +133,34 @@ start_tls_listener(TLSConf0, CowboyOpts) ->
|
|||
ok;
|
||||
{error, ErrTLS} ->
|
||||
rabbit_log:error(
|
||||
"Failed to start a TLS WebSocket (HTTPS) listener. Error: ~p, listener settings: ~p",
|
||||
[ErrTLS, TLSConf]
|
||||
),
|
||||
"Failed to start a TLS WebSocket (HTTPS) listener. Error: ~p, listener settings: ~p",
|
||||
[ErrTLS, TLSConf]),
|
||||
throw(ErrTLS)
|
||||
end,
|
||||
listener_started(?TLS_PROTOCOL, TLSConf),
|
||||
rabbit_log:info(
|
||||
"rabbit_web_mqtt: listening for HTTPS connections on ~s:~w",
|
||||
[TLSIpStr, TLSPort]
|
||||
).
|
||||
rabbit_log:info("rabbit_web_mqtt: listening for HTTPS connections on ~s:~w",
|
||||
[TLSIpStr, TLSPort]).
|
||||
|
||||
listener_started(Protocol, Listener) ->
|
||||
Port = rabbit_misc:pget(port, Listener),
|
||||
[
|
||||
rabbit_networking:tcp_listener_started(
|
||||
Protocol,
|
||||
Listener,
|
||||
IPAddress,
|
||||
Port
|
||||
)
|
||||
|| {IPAddress, _Port, _Family} <-
|
||||
rabbit_networking:tcp_listener_addresses(Port)
|
||||
],
|
||||
[rabbit_networking:tcp_listener_started(Protocol, Listener,
|
||||
IPAddress, Port)
|
||||
|| {IPAddress, _Port, _Family}
|
||||
<- rabbit_networking:tcp_listener_addresses(Port)],
|
||||
ok.
|
||||
|
||||
get_tcp_conf(TCPConf0) ->
|
||||
TCPConf1 =
|
||||
case proplists:get_value(port, TCPConf0) of
|
||||
undefined -> [{port, 15675} | TCPConf0];
|
||||
_ -> TCPConf0
|
||||
end,
|
||||
TCPConf1 = case proplists:get_value(port, TCPConf0) of
|
||||
undefined -> [{port, 15675}|TCPConf0];
|
||||
_ -> TCPConf0
|
||||
end,
|
||||
get_ip_port(TCPConf1).
|
||||
|
||||
get_tls_conf(TLSConf0) ->
|
||||
TLSConf1 =
|
||||
case proplists:get_value(port, TLSConf0) of
|
||||
undefined -> [{port, 15675} | proplists:delete(port, TLSConf0)];
|
||||
_ -> TLSConf0
|
||||
end,
|
||||
TLSConf1 = case proplists:get_value(port, TLSConf0) of
|
||||
undefined -> [{port, 15675}|proplists:delete(port, TLSConf0)];
|
||||
_ -> TLSConf0
|
||||
end,
|
||||
get_ip_port(TLSConf1).
|
||||
|
||||
get_ip_port(Conf0) ->
|
||||
|
@ -194,7 +177,7 @@ normalize_ip(Ip) ->
|
|||
Ip.
|
||||
|
||||
get_max_connections() ->
|
||||
get_env(max_connections, infinity).
|
||||
get_env(max_connections, infinity).
|
||||
|
||||
get_env(Key, Default) ->
|
||||
rabbit_misc:get_env(rabbitmq_web_mqtt, Key, Default).
|
||||
|
|
|
@ -24,25 +24,23 @@
|
|||
-export([conserve_resources/3]).
|
||||
|
||||
%% cowboy_sub_protocol
|
||||
-export([
|
||||
upgrade/4,
|
||||
upgrade/5,
|
||||
takeover/7
|
||||
]).
|
||||
-export([upgrade/4,
|
||||
upgrade/5,
|
||||
takeover/7]).
|
||||
|
||||
-type option(T) :: undefined | T.
|
||||
|
||||
-record(state, {
|
||||
socket :: {rabbit_proxy_socket, any(), any()} | rabbit_net:socket(),
|
||||
parse_state = rabbit_mqtt_packet:initial_state() :: rabbit_mqtt_packet:state(),
|
||||
proc_state :: undefined | rabbit_mqtt_processor:state(),
|
||||
connection_state = running :: running | blocked,
|
||||
conserve = false :: boolean(),
|
||||
stats_timer :: option(rabbit_event:state()),
|
||||
keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
|
||||
conn_name :: option(binary()),
|
||||
received_connect_packet = false :: boolean()
|
||||
}).
|
||||
socket :: {rabbit_proxy_socket, any(), any()} | rabbit_net:socket(),
|
||||
parse_state = rabbit_mqtt_packet:initial_state() :: rabbit_mqtt_packet:state(),
|
||||
proc_state :: undefined | rabbit_mqtt_processor:state(),
|
||||
connection_state = running :: running | blocked,
|
||||
conserve = false :: boolean(),
|
||||
stats_timer :: option(rabbit_event:state()),
|
||||
keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
|
||||
conn_name :: option(binary()),
|
||||
received_connect_packet = false :: boolean()
|
||||
}).
|
||||
|
||||
-type state() :: #state{}.
|
||||
|
||||
|
@ -60,22 +58,14 @@ upgrade(Req, Env, Handler, HandlerState, Opts) ->
|
|||
cowboy_websocket:upgrade(Req, Env, Handler, HandlerState, Opts).
|
||||
|
||||
takeover(Parent, Ref, Socket, Transport, Opts, Buffer, {Handler, {HandlerState, PeerAddr}}) ->
|
||||
Sock =
|
||||
case HandlerState#state.socket of
|
||||
undefined ->
|
||||
Socket;
|
||||
ProxyInfo ->
|
||||
{rabbit_proxy_socket, Socket, ProxyInfo}
|
||||
end,
|
||||
cowboy_websocket:takeover(
|
||||
Parent,
|
||||
Ref,
|
||||
Socket,
|
||||
Transport,
|
||||
Opts,
|
||||
Buffer,
|
||||
{Handler, {HandlerState#state{socket = Sock}, PeerAddr}}
|
||||
).
|
||||
Sock = case HandlerState#state.socket of
|
||||
undefined ->
|
||||
Socket;
|
||||
ProxyInfo ->
|
||||
{rabbit_proxy_socket, Socket, ProxyInfo}
|
||||
end,
|
||||
cowboy_websocket:takeover(Parent, Ref, Socket, Transport, Opts, Buffer,
|
||||
{Handler, {HandlerState#state{socket = Sock}, PeerAddr}}).
|
||||
|
||||
%% cowboy_websocket
|
||||
init(Req, Opts) ->
|
||||
|
@ -85,20 +75,22 @@ init(Req, Opts) ->
|
|||
Protocol ->
|
||||
{PeerAddr, _PeerPort} = maps:get(peer, Req),
|
||||
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
|
||||
false ->
|
||||
no_supported_sub_protocol(Protocol, Req);
|
||||
true ->
|
||||
{?MODULE,
|
||||
cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, <<"mqtt">>, Req),
|
||||
{#state{socket = maps:get(proxy_header, Req, undefined)}, PeerAddr}, WsOpts}
|
||||
cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, <<"mqtt">>, Req),
|
||||
{#state{socket = maps:get(proxy_header, Req, undefined)},
|
||||
PeerAddr},
|
||||
WsOpts}
|
||||
end
|
||||
end.
|
||||
|
||||
-spec websocket_init({state(), PeerAddr :: binary()}) ->
|
||||
{cowboy_websocket:commands(), state()}
|
||||
| {cowboy_websocket:commands(), state(), hibernate}.
|
||||
{cowboy_websocket:commands(), state()} |
|
||||
{cowboy_websocket:commands(), state(), hibernate}.
|
||||
websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
|
||||
logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [web_mqtt]}),
|
||||
ok = file_handle_cache:obtain(),
|
||||
|
@ -108,15 +100,12 @@ websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
|
|||
?LOG_INFO("Accepting Web MQTT connection ~s", [ConnName]),
|
||||
_ = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}),
|
||||
PState = rabbit_mqtt_processor:initial_state(
|
||||
rabbit_net:unwrap_socket(Sock),
|
||||
ConnName,
|
||||
fun send_reply/2,
|
||||
PeerAddr
|
||||
),
|
||||
State1 = State0#state{
|
||||
conn_name = ConnName,
|
||||
proc_state = PState
|
||||
},
|
||||
rabbit_net:unwrap_socket(Sock),
|
||||
ConnName,
|
||||
fun send_reply/2,
|
||||
PeerAddr),
|
||||
State1 = State0#state{conn_name = ConnName,
|
||||
proc_state = PState},
|
||||
State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
|
||||
process_flag(trap_exit, true),
|
||||
{[], State, hibernate};
|
||||
|
@ -124,28 +113,24 @@ websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
|
|||
{[{shutdown_reason, Reason}], State0}
|
||||
end.
|
||||
|
||||
-spec conserve_resources(
|
||||
pid(),
|
||||
rabbit_alarm:resource_alarm_source(),
|
||||
rabbit_alarm:resource_alert()
|
||||
) -> ok.
|
||||
-spec conserve_resources(pid(),
|
||||
rabbit_alarm:resource_alarm_source(),
|
||||
rabbit_alarm:resource_alert()) -> ok.
|
||||
conserve_resources(Pid, _, {_, Conserve, _}) ->
|
||||
Pid ! {conserve_resources, Conserve},
|
||||
ok.
|
||||
|
||||
-spec websocket_handle(ping | pong | {text | binary | ping | pong, binary()}, State) ->
|
||||
{cowboy_websocket:commands(), State}
|
||||
| {cowboy_websocket:commands(), State, hibernate}.
|
||||
{cowboy_websocket:commands(), State} |
|
||||
{cowboy_websocket:commands(), State, hibernate}.
|
||||
websocket_handle({binary, Data}, State) ->
|
||||
handle_data(Data, State);
|
||||
%% Silently ignore ping and pong frames as Cowboy will automatically reply to ping frames.
|
||||
websocket_handle({Ping, _}, State) when
|
||||
Ping =:= ping orelse Ping =:= pong
|
||||
->
|
||||
websocket_handle({Ping, _}, State)
|
||||
when Ping =:= ping orelse Ping =:= pong ->
|
||||
{[], State, hibernate};
|
||||
websocket_handle(Ping, State) when
|
||||
Ping =:= ping orelse Ping =:= pong
|
||||
->
|
||||
websocket_handle(Ping, State)
|
||||
when Ping =:= ping orelse Ping =:= pong ->
|
||||
{[], State, hibernate};
|
||||
%% Log and close connection when receiving any other unexpected frames.
|
||||
websocket_handle(Frame, State) ->
|
||||
|
@ -153,8 +138,8 @@ websocket_handle(Frame, State) ->
|
|||
stop(State, ?CLOSE_UNACCEPTABLE_DATA_TYPE, <<"unexpected WebSocket frame">>).
|
||||
|
||||
-spec websocket_info(any(), State) ->
|
||||
{cowboy_websocket:commands(), State}
|
||||
| {cowboy_websocket:commands(), State, hibernate}.
|
||||
{cowboy_websocket:commands(), State} |
|
||||
{cowboy_websocket:commands(), State, hibernate}.
|
||||
websocket_info({conserve_resources, Conserve}, State) ->
|
||||
handle_credits(State#state{conserve = Conserve});
|
||||
websocket_info({bump_credit, Msg}, State) ->
|
||||
|
@ -164,66 +149,39 @@ websocket_info({reply, Data}, State) ->
|
|||
{[{binary, Data}], State, hibernate};
|
||||
websocket_info({'EXIT', _, _}, State) ->
|
||||
stop(State);
|
||||
websocket_info(
|
||||
{'$gen_cast', QueueEvent = {queue_event, _, _}},
|
||||
State = #state{proc_state = PState0}
|
||||
) ->
|
||||
websocket_info({'$gen_cast', QueueEvent = {queue_event, _, _}},
|
||||
State = #state{proc_state = PState0}) ->
|
||||
case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
|
||||
{ok, PState} ->
|
||||
handle_credits(State#state{proc_state = PState});
|
||||
{error, Reason, PState} ->
|
||||
?LOG_ERROR(
|
||||
"Web MQTT connection ~p failed to handle queue event: ~p",
|
||||
[State#state.conn_name, Reason]
|
||||
),
|
||||
?LOG_ERROR("Web MQTT connection ~p failed to handle queue event: ~p",
|
||||
[State#state.conn_name, Reason]),
|
||||
stop(State#state{proc_state = PState})
|
||||
end;
|
||||
websocket_info(
|
||||
{'$gen_cast', duplicate_id},
|
||||
State = #state{
|
||||
proc_state = ProcState,
|
||||
conn_name = ConnName
|
||||
}
|
||||
) ->
|
||||
?LOG_WARNING(
|
||||
"Web MQTT disconnecting a client with duplicate ID '~s' (~p)",
|
||||
[rabbit_mqtt_processor:info(client_id, ProcState), ConnName]
|
||||
),
|
||||
websocket_info({'$gen_cast', duplicate_id}, State = #state{ proc_state = ProcState,
|
||||
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);
|
||||
websocket_info(
|
||||
{'$gen_cast', {close_connection, Reason}},
|
||||
State = #state{
|
||||
proc_state = ProcState,
|
||||
conn_name = ConnName
|
||||
}
|
||||
) ->
|
||||
?LOG_WARNING(
|
||||
"Web MQTT disconnecting client with ID '~s' (~p), reason: ~s",
|
||||
[rabbit_mqtt_processor:info(client_id, ProcState), ConnName, Reason]
|
||||
),
|
||||
websocket_info({'$gen_cast', {close_connection, Reason}}, State = #state{ proc_state = ProcState,
|
||||
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);
|
||||
websocket_info({'$gen_cast', {force_event_refresh, Ref}}, State0) ->
|
||||
Infos = infos(?CREATION_EVENT_KEYS, State0),
|
||||
rabbit_event:notify(connection_created, Infos, Ref),
|
||||
State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
|
||||
{[], State, hibernate};
|
||||
websocket_info(
|
||||
{'$gen_cast', refresh_config},
|
||||
State0 = #state{
|
||||
proc_state = PState0,
|
||||
conn_name = ConnName
|
||||
}
|
||||
) ->
|
||||
websocket_info({'$gen_cast', refresh_config},
|
||||
State0 = #state{proc_state = PState0,
|
||||
conn_name = ConnName}) ->
|
||||
PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
|
||||
State = State0#state{proc_state = PState},
|
||||
{[], State, hibernate};
|
||||
websocket_info(
|
||||
{keepalive, Req},
|
||||
State = #state{
|
||||
keepalive = KState0,
|
||||
conn_name = ConnName
|
||||
}
|
||||
) ->
|
||||
websocket_info({keepalive, Req}, State = #state{keepalive = KState0,
|
||||
conn_name = ConnName}) ->
|
||||
case rabbit_mqtt_keepalive:handle(Req, KState0) of
|
||||
{ok, KState} ->
|
||||
{[], State#state{keepalive = KState}, hibernate};
|
||||
|
@ -231,24 +189,18 @@ websocket_info(
|
|||
?LOG_ERROR("keepalive timeout in Web MQTT connection ~p", [ConnName]),
|
||||
stop(State, ?CLOSE_NORMAL, <<"MQTT keepalive timeout">>);
|
||||
{error, Reason} ->
|
||||
?LOG_ERROR(
|
||||
"keepalive error in Web MQTT connection ~p: ~p",
|
||||
[ConnName, Reason]
|
||||
),
|
||||
?LOG_ERROR("keepalive error in Web MQTT connection ~p: ~p",
|
||||
[ConnName, Reason]),
|
||||
stop(State)
|
||||
end;
|
||||
websocket_info(emit_stats, State) ->
|
||||
{[], emit_stats(State), hibernate};
|
||||
websocket_info(
|
||||
{ra_event, _From, Evt},
|
||||
#state{proc_state = PState0} = State
|
||||
) ->
|
||||
websocket_info({ra_event, _From, Evt},
|
||||
#state{proc_state = PState0} = State) ->
|
||||
PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
|
||||
{[], State#state{proc_state = PState}, hibernate};
|
||||
websocket_info(
|
||||
{{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
|
||||
State = #state{proc_state = PState0}
|
||||
) ->
|
||||
websocket_info({{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
|
||||
State = #state{proc_state = PState0}) ->
|
||||
case rabbit_mqtt_processor:handle_down(Evt, PState0) of
|
||||
{ok, PState} ->
|
||||
handle_credits(State#state{proc_state = PState});
|
||||
|
@ -275,16 +227,10 @@ terminate(_Reason, _Req, #state{proc_state = undefined}) ->
|
|||
ok;
|
||||
terminate(Reason, Request, #state{} = State) ->
|
||||
terminate(Reason, Request, {true, State});
|
||||
terminate(
|
||||
_Reason,
|
||||
_Request,
|
||||
{SendWill,
|
||||
#state{
|
||||
conn_name = ConnName,
|
||||
proc_state = PState,
|
||||
keepalive = KState
|
||||
} = State}
|
||||
) ->
|
||||
terminate(_Reason, _Request,
|
||||
{SendWill, #state{conn_name = ConnName,
|
||||
proc_state = PState,
|
||||
keepalive = KState} = State}) ->
|
||||
?LOG_INFO("Web MQTT closing connection ~ts", [ConnName]),
|
||||
maybe_emit_stats(State),
|
||||
_ = rabbit_mqtt_keepalive:cancel_timer(KState),
|
||||
|
@ -306,49 +252,29 @@ handle_data(Data, State0 = #state{}) ->
|
|||
Other
|
||||
end.
|
||||
|
||||
handle_data1(
|
||||
<<>>,
|
||||
State0 = #state{
|
||||
received_connect_packet = false,
|
||||
proc_state = PState,
|
||||
conn_name = ConnName
|
||||
}
|
||||
) ->
|
||||
?LOG_INFO(
|
||||
"Accepted web MQTT connection ~p (~s, client ID: ~s)",
|
||||
[self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]
|
||||
),
|
||||
handle_data1(<<>>, State0 = #state{received_connect_packet = false,
|
||||
proc_state = 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},
|
||||
{ok, ensure_stats_timer(control_throttle(State)), hibernate};
|
||||
handle_data1(<<>>, State) ->
|
||||
{ok, ensure_stats_timer(control_throttle(State)), hibernate};
|
||||
handle_data1(
|
||||
Data,
|
||||
State = #state{
|
||||
parse_state = ParseState,
|
||||
proc_state = ProcState,
|
||||
conn_name = ConnName
|
||||
}
|
||||
) ->
|
||||
handle_data1(Data, State = #state{ parse_state = ParseState,
|
||||
proc_state = ProcState,
|
||||
conn_name = ConnName }) ->
|
||||
case parse(Data, ParseState) of
|
||||
{more, ParseState1} ->
|
||||
{ok,
|
||||
ensure_stats_timer(
|
||||
control_throttle(
|
||||
State#state{parse_state = ParseState1}
|
||||
)
|
||||
),
|
||||
hibernate};
|
||||
{ok, ensure_stats_timer(control_throttle(
|
||||
State #state{ parse_state = ParseState1 })), hibernate};
|
||||
{ok, Packet, Rest} ->
|
||||
case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
|
||||
{ok, ProcState1} ->
|
||||
handle_data1(
|
||||
Rest,
|
||||
State#state{
|
||||
parse_state = rabbit_mqtt_packet:initial_state(),
|
||||
proc_state = ProcState1
|
||||
}
|
||||
);
|
||||
Rest,
|
||||
State#state{parse_state = rabbit_mqtt_packet:initial_state(),
|
||||
proc_state = ProcState1});
|
||||
{error, Reason, _} ->
|
||||
stop_mqtt_protocol_error(State, Reason, ConnName);
|
||||
{stop, disconnect, ProcState1} ->
|
||||
|
@ -363,11 +289,9 @@ parse(Data, ParseState) ->
|
|||
rabbit_mqtt_packet:parse(Data, ParseState)
|
||||
catch
|
||||
_:Reason:Stacktrace ->
|
||||
?LOG_ERROR(
|
||||
"Web MQTT cannot parse a packet, reason: ~tp, stacktrace: ~tp, "
|
||||
"payload (first 100 bytes): ~tp",
|
||||
[Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Data, 100)]
|
||||
),
|
||||
?LOG_ERROR("Web MQTT cannot parse a packet, reason: ~tp, stacktrace: ~tp, "
|
||||
"payload (first 100 bytes): ~tp",
|
||||
[Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Data, 100)]),
|
||||
{error, cannot_parse}
|
||||
end.
|
||||
|
||||
|
@ -384,34 +308,26 @@ stop(State, CloseCode, Error0) ->
|
|||
|
||||
handle_credits(State0) ->
|
||||
State = #state{connection_state = CS} = control_throttle(State0),
|
||||
Active =
|
||||
case CS of
|
||||
running -> true;
|
||||
blocked -> false
|
||||
end,
|
||||
Active = case CS of
|
||||
running -> true;
|
||||
blocked -> false
|
||||
end,
|
||||
{[{active, Active}], State, hibernate}.
|
||||
|
||||
control_throttle(
|
||||
State = #state{
|
||||
connection_state = ConnState,
|
||||
conserve = Conserve,
|
||||
received_connect_packet = Connected,
|
||||
proc_state = PState,
|
||||
keepalive = KState
|
||||
}
|
||||
) ->
|
||||
control_throttle(State = #state{connection_state = ConnState,
|
||||
conserve = Conserve,
|
||||
received_connect_packet = Connected,
|
||||
proc_state = PState,
|
||||
keepalive = KState
|
||||
}) ->
|
||||
Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
|
||||
case {ConnState, Throttle} of
|
||||
{running, true} ->
|
||||
State#state{
|
||||
connection_state = blocked,
|
||||
keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)
|
||||
};
|
||||
{blocked, false} ->
|
||||
State#state{
|
||||
connection_state = running,
|
||||
keepalive = rabbit_mqtt_keepalive:start_timer(KState)
|
||||
};
|
||||
State#state{connection_state = blocked,
|
||||
keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)};
|
||||
{blocked,false} ->
|
||||
State#state{connection_state = running,
|
||||
keepalive = rabbit_mqtt_keepalive:start_timer(KState)};
|
||||
{_, _} ->
|
||||
State
|
||||
end.
|
||||
|
@ -425,23 +341,18 @@ ensure_stats_timer(State) ->
|
|||
maybe_emit_stats(#state{stats_timer = undefined}) ->
|
||||
ok;
|
||||
maybe_emit_stats(State) ->
|
||||
rabbit_event:if_enabled(
|
||||
State,
|
||||
#state.stats_timer,
|
||||
fun() -> emit_stats(State) end
|
||||
).
|
||||
rabbit_event:if_enabled(State, #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
|
||||
%% established, as this causes orphan entries on the stats database
|
||||
rabbit_event:reset_stats_timer(State, #state.stats_timer);
|
||||
emit_stats(State) ->
|
||||
[
|
||||
{_, Pid},
|
||||
{_, RecvOct},
|
||||
{_, SendOct},
|
||||
{_, Reductions}
|
||||
] = infos(?SIMPLE_METRICS, State),
|
||||
[{_, Pid},
|
||||
{_, RecvOct},
|
||||
{_, SendOct},
|
||||
{_, Reductions}] = infos(?SIMPLE_METRICS, State),
|
||||
Infos = infos(?OTHER_METRICS, State),
|
||||
rabbit_core_metrics:connection_stats(Pid, Infos),
|
||||
rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
|
||||
|
@ -453,13 +364,12 @@ infos(Items, State) ->
|
|||
|
||||
i(pid, _) ->
|
||||
self();
|
||||
i(SockStat, #state{socket = Sock}) when
|
||||
SockStat =:= recv_oct;
|
||||
SockStat =:= recv_cnt;
|
||||
SockStat =:= send_oct;
|
||||
SockStat =:= send_cnt;
|
||||
SockStat =:= send_pend
|
||||
->
|
||||
i(SockStat, #state{socket = Sock})
|
||||
when SockStat =:= recv_oct;
|
||||
SockStat =:= recv_cnt;
|
||||
SockStat =:= send_oct;
|
||||
SockStat =:= send_cnt;
|
||||
SockStat =:= send_pend ->
|
||||
case rabbit_net:getstat(Sock, [SockStat]) of
|
||||
{ok, [{_, N}]} when is_number(N) ->
|
||||
N;
|
||||
|
@ -473,23 +383,22 @@ i(garbage_collection, _) ->
|
|||
rabbit_misc:get_gc_info(self());
|
||||
i(protocol, #state{proc_state = PState}) ->
|
||||
{?PROTO_FAMILY, rabbit_mqtt_processor:proto_version_tuple(PState)};
|
||||
i(SSL, #state{socket = Sock}) when
|
||||
SSL =:= ssl;
|
||||
SSL =:= ssl_protocol;
|
||||
SSL =:= ssl_key_exchange;
|
||||
SSL =:= ssl_cipher;
|
||||
SSL =:= ssl_hash
|
||||
->
|
||||
rabbit_ssl:info(SSL, {rabbit_net:unwrap_socket(Sock), rabbit_net:maybe_get_proxy_socket(Sock)});
|
||||
i(SSL, #state{socket = Sock})
|
||||
when SSL =:= ssl;
|
||||
SSL =:= ssl_protocol;
|
||||
SSL =:= ssl_key_exchange;
|
||||
SSL =:= ssl_cipher;
|
||||
SSL =:= ssl_hash ->
|
||||
rabbit_ssl:info(SSL, {rabbit_net:unwrap_socket(Sock),
|
||||
rabbit_net:maybe_get_proxy_socket(Sock)});
|
||||
i(name, S) ->
|
||||
i(conn_name, S);
|
||||
i(conn_name, #state{conn_name = Val}) ->
|
||||
Val;
|
||||
i(Cert, #state{socket = Sock}) when
|
||||
Cert =:= peer_cert_issuer;
|
||||
Cert =:= peer_cert_subject;
|
||||
Cert =:= peer_cert_validity
|
||||
->
|
||||
i(Cert, #state{socket = Sock})
|
||||
when Cert =:= peer_cert_issuer;
|
||||
Cert =:= peer_cert_subject;
|
||||
Cert =:= peer_cert_validity ->
|
||||
rabbit_ssl:cert_info(Cert, rabbit_net:unwrap_socket(Sock));
|
||||
i(state, S) ->
|
||||
i(connection_state, S);
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
-export([terminate/3]).
|
||||
-export([early_error/5]).
|
||||
|
||||
|
||||
-record(state, {next}).
|
||||
|
||||
init(StreamID, Req, Opts) ->
|
||||
|
|
|
@ -23,6 +23,7 @@ init_per_suite(Config) ->
|
|||
Config1 = rabbit_ct_helpers:run_setup_steps(Config),
|
||||
rabbit_ct_config_schema:init_schemas(rabbitmq_web_mqtt, Config1).
|
||||
|
||||
|
||||
end_per_suite(Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(Config).
|
||||
|
||||
|
@ -30,19 +31,15 @@ init_per_testcase(Testcase, Config) ->
|
|||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||
{rmq_nodename_suffix, Testcase}
|
||||
]),
|
||||
rabbit_ct_helpers:run_steps(
|
||||
Config1,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()
|
||||
).
|
||||
]),
|
||||
rabbit_ct_helpers:run_steps(Config1,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()).
|
||||
|
||||
end_per_testcase(Testcase, Config) ->
|
||||
Config1 = rabbit_ct_helpers:run_steps(
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()
|
||||
),
|
||||
Config1 = rabbit_ct_helpers:run_steps(Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()),
|
||||
rabbit_ct_helpers:testcase_finished(Config1, Testcase).
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
|
@ -50,13 +47,9 @@ end_per_testcase(Testcase, Config) ->
|
|||
%% -------------------------------------------------------------------
|
||||
|
||||
run_snippets(Config) ->
|
||||
ok = rabbit_ct_broker_helpers:rpc(
|
||||
Config,
|
||||
0,
|
||||
?MODULE,
|
||||
run_snippets1,
|
||||
[Config]
|
||||
).
|
||||
ok = rabbit_ct_broker_helpers:rpc(Config, 0,
|
||||
?MODULE, run_snippets1, [Config]).
|
||||
|
||||
run_snippets1(Config) ->
|
||||
rabbit_ct_config_schema:run_snippets(Config).
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
-module(proxy_protocol_SUITE).
|
||||
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
@ -14,24 +15,20 @@
|
|||
|
||||
suite() ->
|
||||
[
|
||||
%% If a test hangs, no need to wait for 30 minutes.
|
||||
{timetrap, {minutes, 2}}
|
||||
%% If a test hangs, no need to wait for 30 minutes.
|
||||
{timetrap, {minutes, 2}}
|
||||
].
|
||||
|
||||
all() ->
|
||||
[
|
||||
{group, http_tests},
|
||||
{group, https_tests}
|
||||
].
|
||||
[{group, http_tests},
|
||||
{group, https_tests}].
|
||||
|
||||
groups() ->
|
||||
Tests = [
|
||||
proxy_protocol
|
||||
],
|
||||
[
|
||||
{https_tests, [], Tests},
|
||||
{http_tests, [], Tests}
|
||||
].
|
||||
[{https_tests, [], Tests},
|
||||
{http_tests, [], Tests}].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
rabbit_ct_helpers:log_environment(),
|
||||
|
@ -41,29 +38,22 @@ end_per_suite(Config) ->
|
|||
Config.
|
||||
|
||||
init_per_group(Group, Config) ->
|
||||
Protocol =
|
||||
case Group of
|
||||
http_tests -> "ws";
|
||||
https_tests -> "wss"
|
||||
end,
|
||||
Config1 = rabbit_ct_helpers:set_config(
|
||||
Config,
|
||||
[
|
||||
{rmq_nodename_suffix, ?MODULE},
|
||||
{protocol, Protocol},
|
||||
{rabbitmq_ct_tls_verify, verify_none},
|
||||
{rabbitmq_ct_tls_fail_if_no_peer_cert, false}
|
||||
]
|
||||
),
|
||||
Protocol = case Group of
|
||||
http_tests -> "ws";
|
||||
https_tests -> "wss"
|
||||
end,
|
||||
Config1 = rabbit_ct_helpers:set_config(Config,
|
||||
[{rmq_nodename_suffix, ?MODULE},
|
||||
{protocol, Protocol},
|
||||
{rabbitmq_ct_tls_verify, verify_none},
|
||||
{rabbitmq_ct_tls_fail_if_no_peer_cert, false}]),
|
||||
|
||||
rabbit_ct_helpers:run_setup_steps(
|
||||
Config1,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
[
|
||||
fun configure_proxy_protocol/1,
|
||||
fun configure_ssl/1
|
||||
]
|
||||
).
|
||||
rabbit_ct_broker_helpers:setup_steps() ++ [
|
||||
fun configure_proxy_protocol/1,
|
||||
fun configure_ssl/1
|
||||
]).
|
||||
|
||||
configure_proxy_protocol(Config) ->
|
||||
rabbit_ws_test_util:update_app_env(Config, proxy_protocol, true),
|
||||
|
@ -74,16 +64,12 @@ configure_ssl(Config) ->
|
|||
RabbitAppConfig = proplists:get_value(rabbit, ErlangConfig, []),
|
||||
RabbitSslConfig = proplists:get_value(ssl_options, RabbitAppConfig, []),
|
||||
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, [
|
||||
{port, Port} | lists:keydelete(port, 1, RabbitSslConfig)
|
||||
]),
|
||||
rabbit_ws_test_util:update_app_env(Config, ssl_config, [{port, Port} | lists:keydelete(port, 1, RabbitSslConfig)]),
|
||||
Config.
|
||||
|
||||
end_per_group(_Group, Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(
|
||||
Config,
|
||||
rabbit_ct_broker_helpers:teardown_steps()
|
||||
).
|
||||
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||
rabbit_ct_broker_helpers:teardown_steps()).
|
||||
|
||||
init_per_testcase(Testcase, Config) ->
|
||||
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),
|
||||
|
||||
Protocol = ?config(protocol, Config),
|
||||
WS = rfc6455_client:new(
|
||||
Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws",
|
||||
self(),
|
||||
undefined,
|
||||
["mqtt"],
|
||||
"PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"
|
||||
),
|
||||
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self(),
|
||||
undefined, ["mqtt"], "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
|
||||
{ok, _} = rfc6455_client:open(WS),
|
||||
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
|
||||
{binary, _P} = rfc6455_client:recv(WS),
|
||||
ConnectionName = rabbit_ct_broker_helpers:rpc(
|
||||
Config,
|
||||
0,
|
||||
?MODULE,
|
||||
connection_name,
|
||||
[]
|
||||
),
|
||||
ConnectionName = rabbit_ct_broker_helpers:rpc(Config, 0,
|
||||
?MODULE, connection_name, []),
|
||||
match = re:run(ConnectionName, <<"^192.168.1.1:80 -> 192.168.1.2:81$">>, [{capture, none}]),
|
||||
{close, _} = rfc6455_client:close(WS),
|
||||
ok.
|
||||
|
|
|
@ -18,13 +18,13 @@ all() ->
|
|||
|
||||
groups() ->
|
||||
[
|
||||
{tests, [], [
|
||||
no_websocket_subprotocol,
|
||||
unsupported_websocket_subprotocol,
|
||||
unacceptable_data_type,
|
||||
handle_invalid_packets,
|
||||
duplicate_connect
|
||||
]}
|
||||
{tests, [],
|
||||
[no_websocket_subprotocol
|
||||
,unsupported_websocket_subprotocol
|
||||
,unacceptable_data_type
|
||||
,handle_invalid_packets
|
||||
,duplicate_connect
|
||||
]}
|
||||
].
|
||||
|
||||
suite() ->
|
||||
|
@ -35,19 +35,15 @@ init_per_suite(Config) ->
|
|||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||
{rmq_nodename_suffix, ?MODULE},
|
||||
{protocol, "ws"}
|
||||
]),
|
||||
rabbit_ct_helpers:run_setup_steps(
|
||||
Config1,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()
|
||||
).
|
||||
]),
|
||||
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||
rabbit_ct_broker_helpers:setup_steps() ++
|
||||
rabbit_ct_client_helpers:setup_steps()).
|
||||
|
||||
end_per_suite(Config) ->
|
||||
rabbit_ct_helpers:run_teardown_steps(
|
||||
Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()
|
||||
).
|
||||
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||
rabbit_ct_client_helpers:teardown_steps() ++
|
||||
rabbit_ct_broker_helpers:teardown_steps()).
|
||||
|
||||
init_per_group(_, Config) ->
|
||||
Config.
|
||||
|
@ -76,9 +72,7 @@ websocket_subprotocol(Config, SubProtocol) ->
|
|||
PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config),
|
||||
WS = rfc6455_client:new("ws://localhost:" ++ PortStr ++ "/ws", self(), undefined, SubProtocol),
|
||||
{_, [{http_response, Res}]} = rfc6455_client:open(WS),
|
||||
{'HTTP/1.1', 400, <<"Bad Request">>, _} = cow_http:parse_status_line(
|
||||
rabbit_data_coercion:to_binary(Res)
|
||||
),
|
||||
{'HTTP/1.1', 400, <<"Bad Request">>, _} = cow_http:parse_status_line(rabbit_data_coercion:to_binary(Res)),
|
||||
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
|
||||
{close, _} = rfc6455_client:recv(WS, timer:seconds(1)).
|
||||
|
||||
|
@ -116,8 +110,7 @@ duplicate_connect(Config) ->
|
|||
process_flag(trap_exit, true),
|
||||
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
|
||||
eventually(?_assertEqual(0, num_mqtt_connections(Config, 0))),
|
||||
receive
|
||||
{'EXIT', WS, _} -> ok
|
||||
receive {'EXIT', WS, _} -> ok
|
||||
after 500 -> ct:fail("expected web socket to exit")
|
||||
end.
|
||||
|
||||
|
|
|
@ -14,10 +14,3 @@
|
|||
inline_items => {when_under, 4}
|
||||
}}
|
||||
]}.
|
||||
|
||||
{project_plugins, [erlfmt]}.
|
||||
{erlfmt, [
|
||||
write,
|
||||
{print_width, 100},
|
||||
{files, "deps/{rabbitmq_mqtt,rabbitmq_web_mqtt}/{test,src}/*.erl"}
|
||||
]}.
|
||||
|
|
Loading…
Reference in New Issue