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').
|
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
|
||||||
|
|
||||||
-export([
|
-export([scopes/0,
|
||||||
scopes/0,
|
switches/0,
|
||||||
switches/0,
|
aliases/0,
|
||||||
aliases/0,
|
usage/0,
|
||||||
usage/0,
|
usage_doc_guides/0,
|
||||||
usage_doc_guides/0,
|
banner/2,
|
||||||
banner/2,
|
validate/2,
|
||||||
validate/2,
|
merge_defaults/2,
|
||||||
merge_defaults/2,
|
run/2,
|
||||||
run/2,
|
output/2,
|
||||||
output/2,
|
description/0,
|
||||||
description/0,
|
help_section/0]).
|
||||||
help_section/0
|
|
||||||
]).
|
|
||||||
|
|
||||||
scopes() -> [ctl].
|
scopes() -> [ctl].
|
||||||
switches() -> [].
|
switches() -> [].
|
||||||
|
@ -50,29 +48,20 @@ usage() ->
|
||||||
usage_doc_guides() ->
|
usage_doc_guides() ->
|
||||||
[?MQTT_GUIDE_URL].
|
[?MQTT_GUIDE_URL].
|
||||||
|
|
||||||
run([Node], #{
|
run([Node], #{node := NodeName,
|
||||||
node := NodeName,
|
timeout := Timeout}) ->
|
||||||
timeout := Timeout
|
|
||||||
}) ->
|
|
||||||
case rabbit_misc:rpc_call(NodeName, rabbit_mqtt_collector, leave, [Node], Timeout) of
|
case rabbit_misc:rpc_call(NodeName, rabbit_mqtt_collector, leave, [Node], Timeout) of
|
||||||
{badrpc, _} = Error ->
|
{badrpc, _} = Error ->
|
||||||
Error;
|
Error;
|
||||||
nodedown ->
|
nodedown ->
|
||||||
{ok,
|
{ok, list_to_binary(io_lib:format("Node ~ts is down but has been successfully removed"
|
||||||
list_to_binary(
|
" from the cluster", [Node]))};
|
||||||
io_lib:format(
|
|
||||||
"Node ~ts is down but has been successfully removed"
|
|
||||||
" from the cluster",
|
|
||||||
[Node]
|
|
||||||
)
|
|
||||||
)};
|
|
||||||
Result ->
|
Result ->
|
||||||
%% 'ok' or 'timeout'
|
%% 'ok' or 'timeout'
|
||||||
Result
|
Result
|
||||||
end.
|
end.
|
||||||
|
|
||||||
banner([Node], _) ->
|
banner([Node], _) -> list_to_binary(io_lib:format("Removing node ~ts from the list of MQTT nodes...", [Node])).
|
||||||
list_to_binary(io_lib:format("Removing node ~ts from the list of MQTT nodes...", [Node])).
|
|
||||||
|
|
||||||
output(Result, _Opts) ->
|
output(Result, _Opts) ->
|
||||||
'Elixir.RabbitMQ.CLI.DefaultOutput':output(Result).
|
'Elixir.RabbitMQ.CLI.DefaultOutput':output(Result).
|
||||||
|
|
|
@ -10,22 +10,20 @@
|
||||||
|
|
||||||
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
|
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
|
||||||
|
|
||||||
-export([
|
-export([formatter/0,
|
||||||
formatter/0,
|
scopes/0,
|
||||||
scopes/0,
|
switches/0,
|
||||||
switches/0,
|
aliases/0,
|
||||||
aliases/0,
|
usage/0,
|
||||||
usage/0,
|
usage_additional/0,
|
||||||
usage_additional/0,
|
usage_doc_guides/0,
|
||||||
usage_doc_guides/0,
|
banner/2,
|
||||||
banner/2,
|
validate/2,
|
||||||
validate/2,
|
merge_defaults/2,
|
||||||
merge_defaults/2,
|
run/2,
|
||||||
run/2,
|
output/2,
|
||||||
output/2,
|
description/0,
|
||||||
description/0,
|
help_section/0]).
|
||||||
help_section/0
|
|
||||||
]).
|
|
||||||
|
|
||||||
formatter() -> 'Elixir.RabbitMQ.CLI.Formatters.Table'.
|
formatter() -> 'Elixir.RabbitMQ.CLI.Formatters.Table'.
|
||||||
scopes() -> [ctl, diagnostics].
|
scopes() -> [ctl, diagnostics].
|
||||||
|
@ -39,14 +37,10 @@ help_section() ->
|
||||||
|
|
||||||
validate(Args, _) ->
|
validate(Args, _) ->
|
||||||
InfoItems = lists:map(fun atom_to_list/1, ?INFO_ITEMS),
|
InfoItems = lists:map(fun atom_to_list/1, ?INFO_ITEMS),
|
||||||
case
|
case 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':validate_info_keys(Args,
|
||||||
'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':validate_info_keys(
|
InfoItems) of
|
||||||
Args,
|
|
||||||
InfoItems
|
|
||||||
)
|
|
||||||
of
|
|
||||||
{ok, _} -> ok;
|
{ok, _} -> ok;
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
merge_defaults([], Opts) ->
|
merge_defaults([], Opts) ->
|
||||||
|
@ -61,22 +55,19 @@ usage_additional() ->
|
||||||
Prefix = <<" must be one of ">>,
|
Prefix = <<" must be one of ">>,
|
||||||
InfoItems = 'Elixir.Enum':join(lists:usort(?INFO_ITEMS), <<", ">>),
|
InfoItems = 'Elixir.Enum':join(lists:usort(?INFO_ITEMS), <<", ">>),
|
||||||
[
|
[
|
||||||
{<<"<column>">>, <<Prefix/binary, InfoItems/binary>>}
|
{<<"<column>">>, <<Prefix/binary, InfoItems/binary>>}
|
||||||
].
|
].
|
||||||
|
|
||||||
usage_doc_guides() ->
|
usage_doc_guides() ->
|
||||||
[?MQTT_GUIDE_URL].
|
[?MQTT_GUIDE_URL].
|
||||||
|
|
||||||
run(Args, #{
|
run(Args, #{node := NodeName,
|
||||||
node := NodeName,
|
timeout := Timeout,
|
||||||
timeout := Timeout,
|
verbose := Verbose}) ->
|
||||||
verbose := Verbose
|
InfoKeys = case Verbose of
|
||||||
}) ->
|
true -> ?INFO_ITEMS;
|
||||||
InfoKeys =
|
false -> 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':prepare_info_keys(Args)
|
||||||
case Verbose of
|
end,
|
||||||
true -> ?INFO_ITEMS;
|
|
||||||
false -> 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':prepare_info_keys(Args)
|
|
||||||
end,
|
|
||||||
|
|
||||||
Nodes = 'Elixir.RabbitMQ.CLI.Core.Helpers':nodes_in_cluster(NodeName),
|
Nodes = 'Elixir.RabbitMQ.CLI.Core.Helpers':nodes_in_cluster(NodeName),
|
||||||
|
|
||||||
|
@ -87,8 +78,7 @@ run(Args, #{
|
||||||
[Nodes, InfoKeys],
|
[Nodes, InfoKeys],
|
||||||
Timeout,
|
Timeout,
|
||||||
InfoKeys,
|
InfoKeys,
|
||||||
length(Nodes)
|
length(Nodes)).
|
||||||
).
|
|
||||||
|
|
||||||
banner(_, _) -> <<"Listing MQTT connections ...">>.
|
banner(_, _) -> <<"Listing MQTT connections ...">>.
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,13 @@
|
||||||
|
|
||||||
-include("mqtt_machine.hrl").
|
-include("mqtt_machine.hrl").
|
||||||
|
|
||||||
-export([
|
-export([version/0,
|
||||||
version/0,
|
which_module/1,
|
||||||
which_module/1,
|
init/1,
|
||||||
init/1,
|
apply/3,
|
||||||
apply/3,
|
state_enter/2,
|
||||||
state_enter/2,
|
notify_connection/2,
|
||||||
notify_connection/2,
|
overview/1]).
|
||||||
overview/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-type state() :: #machine_state{}.
|
-type state() :: #machine_state{}.
|
||||||
|
|
||||||
|
@ -26,10 +24,9 @@
|
||||||
-type reply() :: {ok, term()} | {error, term()}.
|
-type reply() :: {ok, term()} | {error, term()}.
|
||||||
-type client_id() :: term().
|
-type client_id() :: term().
|
||||||
|
|
||||||
-type command() ::
|
-type command() :: {register, client_id(), pid()} |
|
||||||
{register, client_id(), pid()}
|
{unregister, client_id(), pid()} |
|
||||||
| {unregister, client_id(), pid()}
|
list.
|
||||||
| list.
|
|
||||||
version() -> 1.
|
version() -> 1.
|
||||||
|
|
||||||
which_module(1) -> ?MODULE;
|
which_module(1) -> ?MODULE;
|
||||||
|
@ -41,130 +38,93 @@ init(_Conf) ->
|
||||||
|
|
||||||
-spec apply(map(), command(), state()) ->
|
-spec apply(map(), command(), state()) ->
|
||||||
{state(), reply(), ra_machine:effects()}.
|
{state(), reply(), ra_machine:effects()}.
|
||||||
apply(
|
apply(_Meta, {register, ClientId, Pid},
|
||||||
_Meta,
|
#machine_state{client_ids = Ids,
|
||||||
{register, ClientId, Pid},
|
pids = Pids0} = State0) ->
|
||||||
#machine_state{
|
|
||||||
client_ids = Ids,
|
|
||||||
pids = Pids0
|
|
||||||
} = State0
|
|
||||||
) ->
|
|
||||||
{Effects, Ids1, Pids} =
|
{Effects, Ids1, Pids} =
|
||||||
case maps:find(ClientId, Ids) of
|
case maps:find(ClientId, Ids) of
|
||||||
{ok, OldPid} when Pid =/= OldPid ->
|
{ok, OldPid} when Pid =/= OldPid ->
|
||||||
Effects0 = [
|
Effects0 = [{demonitor, process, OldPid},
|
||||||
{demonitor, process, OldPid},
|
{monitor, process, Pid},
|
||||||
{monitor, process, Pid},
|
{mod_call, ?MODULE, notify_connection,
|
||||||
{mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]}
|
[OldPid, duplicate_id]}],
|
||||||
],
|
Pids2 = case maps:take(OldPid, Pids0) of
|
||||||
Pids2 =
|
error ->
|
||||||
case maps:take(OldPid, Pids0) of
|
Pids0;
|
||||||
error ->
|
{[ClientId], Pids1} ->
|
||||||
Pids0;
|
Pids1;
|
||||||
{[ClientId], Pids1} ->
|
{ClientIds, Pids1} ->
|
||||||
Pids1;
|
Pids1#{ClientId => lists:delete(ClientId, ClientIds)}
|
||||||
{ClientIds, Pids1} ->
|
end,
|
||||||
Pids1#{ClientId => lists:delete(ClientId, ClientIds)}
|
Pids3 = maps:update_with(Pid, fun(CIds) -> [ClientId | CIds] end,
|
||||||
end,
|
[ClientId], Pids2),
|
||||||
Pids3 = maps:update_with(
|
|
||||||
Pid,
|
|
||||||
fun(CIds) -> [ClientId | CIds] end,
|
|
||||||
[ClientId],
|
|
||||||
Pids2
|
|
||||||
),
|
|
||||||
{Effects0, maps:remove(ClientId, Ids), Pids3};
|
{Effects0, maps:remove(ClientId, Ids), Pids3};
|
||||||
{ok, Pid} ->
|
|
||||||
|
{ok, Pid} ->
|
||||||
{[], Ids, Pids0};
|
{[], Ids, Pids0};
|
||||||
error ->
|
error ->
|
||||||
Pids1 = maps:update_with(
|
Pids1 = maps:update_with(Pid, fun(CIds) -> [ClientId | CIds] end,
|
||||||
Pid,
|
[ClientId], Pids0),
|
||||||
fun(CIds) -> [ClientId | CIds] end,
|
|
||||||
[ClientId],
|
|
||||||
Pids0
|
|
||||||
),
|
|
||||||
Effects0 = [{monitor, process, Pid}],
|
Effects0 = [{monitor, process, Pid}],
|
||||||
{Effects0, Ids, Pids1}
|
{Effects0, Ids, Pids1}
|
||||||
end,
|
end,
|
||||||
State = State0#machine_state{
|
State = State0#machine_state{client_ids = maps:put(ClientId, Pid, Ids1),
|
||||||
client_ids = maps:put(ClientId, Pid, Ids1),
|
pids = Pids},
|
||||||
pids = Pids
|
|
||||||
},
|
|
||||||
{State, ok, Effects};
|
{State, ok, Effects};
|
||||||
apply(
|
|
||||||
Meta,
|
|
||||||
{unregister, ClientId, Pid},
|
|
||||||
#machine_state{
|
|
||||||
client_ids = Ids,
|
|
||||||
pids = Pids0
|
|
||||||
} = State0
|
|
||||||
) ->
|
|
||||||
State =
|
|
||||||
case maps:find(ClientId, Ids) of
|
|
||||||
{ok, Pid} ->
|
|
||||||
Pids =
|
|
||||||
case maps:get(Pid, Pids0, undefined) of
|
|
||||||
undefined ->
|
|
||||||
Pids0;
|
|
||||||
[ClientId] ->
|
|
||||||
maps:remove(Pid, Pids0);
|
|
||||||
Cids ->
|
|
||||||
Pids0#{Pid => lists:delete(ClientId, Cids)}
|
|
||||||
end,
|
|
||||||
|
|
||||||
State0#machine_state{
|
apply(Meta, {unregister, ClientId, Pid}, #machine_state{client_ids = Ids,
|
||||||
client_ids = maps:remove(ClientId, Ids),
|
pids = Pids0} = State0) ->
|
||||||
pids = Pids
|
State = case maps:find(ClientId, Ids) of
|
||||||
};
|
{ok, Pid} ->
|
||||||
%% don't delete client id that might belong to a newer connection
|
Pids = case maps:get(Pid, Pids0, undefined) of
|
||||||
%% that kicked the one with Pid out
|
undefined ->
|
||||||
{ok, _AnotherPid} ->
|
Pids0;
|
||||||
State0;
|
[ClientId] ->
|
||||||
error ->
|
maps:remove(Pid, Pids0);
|
||||||
State0
|
Cids ->
|
||||||
end,
|
Pids0#{Pid => lists:delete(ClientId, Cids)}
|
||||||
|
end,
|
||||||
|
|
||||||
|
State0#machine_state{client_ids = maps:remove(ClientId, Ids),
|
||||||
|
pids = Pids};
|
||||||
|
%% don't delete client id that might belong to a newer connection
|
||||||
|
%% that kicked the one with Pid out
|
||||||
|
{ok, _AnotherPid} ->
|
||||||
|
State0;
|
||||||
|
error ->
|
||||||
|
State0
|
||||||
|
end,
|
||||||
Effects0 = [{demonitor, process, Pid}],
|
Effects0 = [{demonitor, process, Pid}],
|
||||||
%% snapshot only when the map has changed
|
%% snapshot only when the map has changed
|
||||||
Effects =
|
Effects = case State of
|
||||||
case State of
|
State0 -> Effects0;
|
||||||
State0 -> Effects0;
|
_ -> Effects0 ++ snapshot_effects(Meta, State)
|
||||||
_ -> Effects0 ++ snapshot_effects(Meta, State)
|
end,
|
||||||
end,
|
|
||||||
{State, ok, Effects};
|
{State, ok, Effects};
|
||||||
|
|
||||||
apply(_Meta, {down, DownPid, noconnection}, State) ->
|
apply(_Meta, {down, DownPid, noconnection}, State) ->
|
||||||
%% Monitor the node the pid is on (see {nodeup, Node} below)
|
%% Monitor the node the pid is on (see {nodeup, Node} below)
|
||||||
%% so that we can detect when the node is re-connected and discover the
|
%% so that we can detect when the node is re-connected and discover the
|
||||||
%% actual fate of the connection processes on it
|
%% actual fate of the connection processes on it
|
||||||
Effect = {monitor, node, node(DownPid)},
|
Effect = {monitor, node, node(DownPid)},
|
||||||
{State, ok, Effect};
|
{State, ok, Effect};
|
||||||
apply(
|
|
||||||
Meta,
|
apply(Meta, {down, DownPid, _}, #machine_state{client_ids = Ids,
|
||||||
{down, DownPid, _},
|
pids = Pids0} = State0) ->
|
||||||
#machine_state{
|
|
||||||
client_ids = Ids,
|
|
||||||
pids = Pids0
|
|
||||||
} = State0
|
|
||||||
) ->
|
|
||||||
case maps:get(DownPid, Pids0, undefined) of
|
case maps:get(DownPid, Pids0, undefined) of
|
||||||
undefined ->
|
undefined ->
|
||||||
{State0, ok, []};
|
{State0, ok, []};
|
||||||
ClientIds ->
|
ClientIds ->
|
||||||
Ids1 = maps:without(ClientIds, Ids),
|
Ids1 = maps:without(ClientIds, Ids),
|
||||||
State = State0#machine_state{
|
State = State0#machine_state{client_ids = Ids1,
|
||||||
client_ids = Ids1,
|
pids = maps:remove(DownPid, Pids0)},
|
||||||
pids = maps:remove(DownPid, Pids0)
|
Effects = lists:map(fun(Id) ->
|
||||||
},
|
[{mod_call, rabbit_log, debug,
|
||||||
Effects = lists:map(
|
["MQTT connection with client id '~ts' failed", [Id]]}]
|
||||||
fun(Id) ->
|
end, ClientIds),
|
||||||
[
|
|
||||||
{mod_call, rabbit_log, debug, [
|
|
||||||
"MQTT connection with client id '~ts' failed", [Id]
|
|
||||||
]}
|
|
||||||
]
|
|
||||||
end,
|
|
||||||
ClientIds
|
|
||||||
),
|
|
||||||
{State, ok, Effects ++ snapshot_effects(Meta, State)}
|
{State, ok, Effects ++ snapshot_effects(Meta, State)}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
apply(_Meta, {nodeup, Node}, State) ->
|
apply(_Meta, {nodeup, Node}, State) ->
|
||||||
%% Work out if any pids that were disconnected are still
|
%% Work out if any pids that were disconnected are still
|
||||||
%% alive.
|
%% alive.
|
||||||
|
@ -173,69 +133,41 @@ apply(_Meta, {nodeup, Node}, State) ->
|
||||||
{State, ok, Effects};
|
{State, ok, Effects};
|
||||||
apply(_Meta, {nodedown, _Node}, State) ->
|
apply(_Meta, {nodedown, _Node}, State) ->
|
||||||
{State, ok};
|
{State, ok};
|
||||||
apply(
|
|
||||||
Meta,
|
|
||||||
{leave, Node},
|
|
||||||
#machine_state{
|
|
||||||
client_ids = Ids,
|
|
||||||
pids = Pids0
|
|
||||||
} = State0
|
|
||||||
) ->
|
|
||||||
{Keep, Remove} = maps:fold(
|
|
||||||
fun(ClientId, Pid, {In, Out}) ->
|
|
||||||
case node(Pid) =/= Node of
|
|
||||||
true ->
|
|
||||||
{In#{ClientId => Pid}, Out};
|
|
||||||
false ->
|
|
||||||
{In, Out#{ClientId => Pid}}
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
{#{}, #{}},
|
|
||||||
Ids
|
|
||||||
),
|
|
||||||
Effects = maps:fold(
|
|
||||||
fun(ClientId, _Pid, Acc) ->
|
|
||||||
Pid = maps:get(ClientId, Ids),
|
|
||||||
[
|
|
||||||
{demonitor, process, Pid},
|
|
||||||
{mod_call, ?MODULE, notify_connection, [Pid, decommission_node]},
|
|
||||||
{mod_call, rabbit_log, debug, [
|
|
||||||
"MQTT will remove client ID '~ts' from known "
|
|
||||||
"as its node has been decommissioned",
|
|
||||||
[ClientId]
|
|
||||||
]}
|
|
||||||
] ++ Acc
|
|
||||||
end,
|
|
||||||
[],
|
|
||||||
Remove
|
|
||||||
),
|
|
||||||
|
|
||||||
State = State0#machine_state{
|
apply(Meta, {leave, Node}, #machine_state{client_ids = Ids,
|
||||||
client_ids = Keep,
|
pids = Pids0} = State0) ->
|
||||||
pids = maps:without(maps:keys(Remove), Pids0)
|
{Keep, Remove} = maps:fold(
|
||||||
},
|
fun (ClientId, Pid, {In, Out}) ->
|
||||||
|
case node(Pid) =/= Node of
|
||||||
|
true ->
|
||||||
|
{In#{ClientId => Pid}, Out};
|
||||||
|
false ->
|
||||||
|
{In, Out#{ClientId => Pid}}
|
||||||
|
end
|
||||||
|
end, {#{}, #{}}, Ids),
|
||||||
|
Effects = maps:fold(fun (ClientId, _Pid, Acc) ->
|
||||||
|
Pid = maps:get(ClientId, Ids),
|
||||||
|
[
|
||||||
|
{demonitor, process, Pid},
|
||||||
|
{mod_call, ?MODULE, notify_connection, [Pid, decommission_node]},
|
||||||
|
{mod_call, rabbit_log, debug,
|
||||||
|
["MQTT will remove client ID '~ts' from known "
|
||||||
|
"as its node has been decommissioned", [ClientId]]}
|
||||||
|
] ++ Acc
|
||||||
|
end, [], Remove),
|
||||||
|
|
||||||
|
State = State0#machine_state{client_ids = Keep,
|
||||||
|
pids = maps:without(maps:keys(Remove), Pids0)},
|
||||||
{State, ok, Effects ++ snapshot_effects(Meta, State)};
|
{State, ok, Effects ++ snapshot_effects(Meta, State)};
|
||||||
apply(_Meta, {machine_version, 0, 1}, {machine_state, Ids}) ->
|
apply(_Meta, {machine_version, 0, 1}, {machine_state, Ids}) ->
|
||||||
Pids = maps:fold(
|
Pids = maps:fold(
|
||||||
fun(Id, Pid, Acc) ->
|
fun(Id, Pid, Acc) ->
|
||||||
maps:update_with(
|
maps:update_with(Pid,
|
||||||
Pid,
|
fun(CIds) -> [Id | CIds] end,
|
||||||
fun(CIds) -> [Id | CIds] end,
|
[Id], Acc)
|
||||||
[Id],
|
end, #{}, Ids),
|
||||||
Acc
|
{#machine_state{client_ids = Ids,
|
||||||
)
|
pids = Pids}, ok, []};
|
||||||
end,
|
|
||||||
#{},
|
|
||||||
Ids
|
|
||||||
),
|
|
||||||
{
|
|
||||||
#machine_state{
|
|
||||||
client_ids = Ids,
|
|
||||||
pids = Pids
|
|
||||||
},
|
|
||||||
ok,
|
|
||||||
[]
|
|
||||||
};
|
|
||||||
apply(_Meta, Unknown, State) ->
|
apply(_Meta, Unknown, State) ->
|
||||||
logger:error("MQTT Raft state machine v1 received unknown command ~tp", [Unknown]),
|
logger:error("MQTT Raft state machine v1 received unknown command ~tp", [Unknown]),
|
||||||
{State, {error, {unknown_command, Unknown}}, []}.
|
{State, {error, {unknown_command, Unknown}}, []}.
|
||||||
|
@ -250,21 +182,17 @@ state_enter(_, _) ->
|
||||||
[].
|
[].
|
||||||
|
|
||||||
-spec overview(state()) -> map().
|
-spec overview(state()) -> map().
|
||||||
overview(#machine_state{
|
overview(#machine_state{client_ids = ClientIds,
|
||||||
client_ids = ClientIds,
|
pids = Pids}) ->
|
||||||
pids = Pids
|
#{num_client_ids => maps:size(ClientIds),
|
||||||
}) ->
|
num_pids => maps:size(Pids)}.
|
||||||
#{
|
|
||||||
num_client_ids => maps:size(ClientIds),
|
|
||||||
num_pids => maps:size(Pids)
|
|
||||||
}.
|
|
||||||
|
|
||||||
%% ==========================
|
%% ==========================
|
||||||
|
|
||||||
%% Avoids blocking the Raft leader.
|
%% Avoids blocking the Raft leader.
|
||||||
-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
|
-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
|
||||||
notify_connection(Pid, Reason) ->
|
notify_connection(Pid, Reason) ->
|
||||||
spawn(fun() -> gen_server2:cast(Pid, Reason) end).
|
spawn(fun() -> gen_server2:cast(Pid, Reason) end).
|
||||||
|
|
||||||
-spec snapshot_effects(map(), state()) -> ra_machine:effects().
|
-spec snapshot_effects(map(), state()) -> ra_machine:effects().
|
||||||
snapshot_effects(#{index := RaftIdx}, State) ->
|
snapshot_effects(#{index := RaftIdx}, State) ->
|
||||||
|
|
|
@ -9,12 +9,10 @@
|
||||||
|
|
||||||
-include("mqtt_machine_v0.hrl").
|
-include("mqtt_machine_v0.hrl").
|
||||||
|
|
||||||
-export([
|
-export([init/1,
|
||||||
init/1,
|
apply/3,
|
||||||
apply/3,
|
state_enter/2,
|
||||||
state_enter/2,
|
notify_connection/2]).
|
||||||
notify_connection/2
|
|
||||||
]).
|
|
||||||
|
|
||||||
-type state() :: #machine_state{}.
|
-type state() :: #machine_state{}.
|
||||||
|
|
||||||
|
@ -23,10 +21,9 @@
|
||||||
-type reply() :: {ok, term()} | {error, term()}.
|
-type reply() :: {ok, term()} | {error, term()}.
|
||||||
-type client_id() :: term().
|
-type client_id() :: term().
|
||||||
|
|
||||||
-type command() ::
|
-type command() :: {register, client_id(), pid()} |
|
||||||
{register, client_id(), pid()}
|
{unregister, client_id(), pid()} |
|
||||||
| {unregister, client_id(), pid()}
|
list.
|
||||||
| list.
|
|
||||||
|
|
||||||
-spec init(config()) -> state().
|
-spec init(config()) -> state().
|
||||||
init(_Conf) ->
|
init(_Conf) ->
|
||||||
|
@ -38,60 +35,53 @@ apply(_Meta, {register, ClientId, Pid}, #machine_state{client_ids = Ids} = State
|
||||||
{Effects, Ids1} =
|
{Effects, Ids1} =
|
||||||
case maps:find(ClientId, Ids) of
|
case maps:find(ClientId, Ids) of
|
||||||
{ok, OldPid} when Pid =/= OldPid ->
|
{ok, OldPid} when Pid =/= OldPid ->
|
||||||
Effects0 = [
|
Effects0 = [{demonitor, process, OldPid},
|
||||||
{demonitor, process, OldPid},
|
{monitor, process, Pid},
|
||||||
{monitor, process, Pid},
|
{mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]}],
|
||||||
{mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]}
|
|
||||||
],
|
|
||||||
{Effects0, maps:remove(ClientId, Ids)};
|
{Effects0, maps:remove(ClientId, Ids)};
|
||||||
_ ->
|
_ ->
|
||||||
Effects0 = [{monitor, process, Pid}],
|
Effects0 = [{monitor, process, Pid}],
|
||||||
{Effects0, Ids}
|
{Effects0, Ids}
|
||||||
end,
|
end,
|
||||||
State = State0#machine_state{client_ids = maps:put(ClientId, Pid, Ids1)},
|
State = State0#machine_state{client_ids = maps:put(ClientId, Pid, Ids1)},
|
||||||
{State, ok, Effects};
|
{State, ok, Effects};
|
||||||
|
|
||||||
apply(Meta, {unregister, ClientId, Pid}, #machine_state{client_ids = Ids} = State0) ->
|
apply(Meta, {unregister, ClientId, Pid}, #machine_state{client_ids = Ids} = State0) ->
|
||||||
State =
|
State = case maps:find(ClientId, Ids) of
|
||||||
case maps:find(ClientId, Ids) of
|
{ok, Pid} -> State0#machine_state{client_ids = maps:remove(ClientId, Ids)};
|
||||||
{ok, Pid} -> State0#machine_state{client_ids = maps:remove(ClientId, Ids)};
|
%% don't delete client id that might belong to a newer connection
|
||||||
%% don't delete client id that might belong to a newer connection
|
%% that kicked the one with Pid out
|
||||||
%% that kicked the one with Pid out
|
{ok, _AnotherPid} -> State0;
|
||||||
{ok, _AnotherPid} -> State0;
|
error -> State0
|
||||||
error -> State0
|
end,
|
||||||
end,
|
|
||||||
Effects0 = [{demonitor, process, Pid}],
|
Effects0 = [{demonitor, process, Pid}],
|
||||||
%% snapshot only when the map has changed
|
%% snapshot only when the map has changed
|
||||||
Effects =
|
Effects = case State of
|
||||||
case State of
|
State0 -> Effects0;
|
||||||
State0 -> Effects0;
|
_ -> Effects0 ++ snapshot_effects(Meta, State)
|
||||||
_ -> Effects0 ++ snapshot_effects(Meta, State)
|
end,
|
||||||
end,
|
|
||||||
{State, ok, Effects};
|
{State, ok, Effects};
|
||||||
|
|
||||||
apply(_Meta, {down, DownPid, noconnection}, State) ->
|
apply(_Meta, {down, DownPid, noconnection}, State) ->
|
||||||
%% Monitor the node the pid is on (see {nodeup, Node} below)
|
%% Monitor the node the pid is on (see {nodeup, Node} below)
|
||||||
%% so that we can detect when the node is re-connected and discover the
|
%% so that we can detect when the node is re-connected and discover the
|
||||||
%% actual fate of the connection processes on it
|
%% actual fate of the connection processes on it
|
||||||
Effect = {monitor, node, node(DownPid)},
|
Effect = {monitor, node, node(DownPid)},
|
||||||
{State, ok, Effect};
|
{State, ok, Effect};
|
||||||
|
|
||||||
apply(Meta, {down, DownPid, _}, #machine_state{client_ids = Ids} = State0) ->
|
apply(Meta, {down, DownPid, _}, #machine_state{client_ids = Ids} = State0) ->
|
||||||
Ids1 = maps:filter(
|
Ids1 = maps:filter(fun (_ClientId, Pid) when Pid =:= DownPid ->
|
||||||
fun
|
false;
|
||||||
(_ClientId, Pid) when Pid =:= DownPid ->
|
(_, _) ->
|
||||||
false;
|
true
|
||||||
(_, _) ->
|
end, Ids),
|
||||||
true
|
|
||||||
end,
|
|
||||||
Ids
|
|
||||||
),
|
|
||||||
State = State0#machine_state{client_ids = Ids1},
|
State = State0#machine_state{client_ids = Ids1},
|
||||||
Delta = maps:keys(Ids) -- maps:keys(Ids1),
|
Delta = maps:keys(Ids) -- maps:keys(Ids1),
|
||||||
Effects = lists:map(
|
Effects = lists:map(fun(Id) ->
|
||||||
fun(Id) ->
|
[{mod_call, rabbit_log, debug,
|
||||||
[{mod_call, rabbit_log, debug, ["MQTT connection with client id '~ts' failed", [Id]]}]
|
["MQTT connection with client id '~ts' failed", [Id]]}] end, Delta),
|
||||||
end,
|
|
||||||
Delta
|
|
||||||
),
|
|
||||||
{State, ok, Effects ++ snapshot_effects(Meta, State)};
|
{State, ok, Effects ++ snapshot_effects(Meta, State)};
|
||||||
|
|
||||||
apply(_Meta, {nodeup, Node}, State) ->
|
apply(_Meta, {nodeup, Node}, State) ->
|
||||||
%% Work out if any pids that were disconnected are still
|
%% Work out if any pids that were disconnected are still
|
||||||
%% alive.
|
%% alive.
|
||||||
|
@ -100,29 +90,25 @@ apply(_Meta, {nodeup, Node}, State) ->
|
||||||
{State, ok, Effects};
|
{State, ok, Effects};
|
||||||
apply(_Meta, {nodedown, _Node}, State) ->
|
apply(_Meta, {nodedown, _Node}, State) ->
|
||||||
{State, ok};
|
{State, ok};
|
||||||
|
|
||||||
apply(Meta, {leave, Node}, #machine_state{client_ids = Ids} = State0) ->
|
apply(Meta, {leave, Node}, #machine_state{client_ids = Ids} = State0) ->
|
||||||
Ids1 = maps:filter(fun(_ClientId, Pid) -> node(Pid) =/= Node end, Ids),
|
Ids1 = maps:filter(fun (_ClientId, Pid) -> node(Pid) =/= Node end, Ids),
|
||||||
Delta = maps:keys(Ids) -- maps:keys(Ids1),
|
Delta = maps:keys(Ids) -- maps:keys(Ids1),
|
||||||
|
|
||||||
Effects = lists:foldl(
|
Effects = lists:foldl(fun (ClientId, Acc) ->
|
||||||
fun(ClientId, Acc) ->
|
Pid = maps:get(ClientId, Ids),
|
||||||
Pid = maps:get(ClientId, Ids),
|
[
|
||||||
[
|
{demonitor, process, Pid},
|
||||||
{demonitor, process, Pid},
|
{mod_call, ?MODULE, notify_connection, [Pid, decommission_node]},
|
||||||
{mod_call, ?MODULE, notify_connection, [Pid, decommission_node]},
|
{mod_call, rabbit_log, debug,
|
||||||
{mod_call, rabbit_log, debug, [
|
["MQTT will remove client ID '~ts' from known "
|
||||||
"MQTT will remove client ID '~ts' from known "
|
"as its node has been decommissioned", [ClientId]]}
|
||||||
"as its node has been decommissioned",
|
] ++ Acc
|
||||||
[ClientId]
|
end, [], Delta),
|
||||||
]}
|
|
||||||
] ++ Acc
|
|
||||||
end,
|
|
||||||
[],
|
|
||||||
Delta
|
|
||||||
),
|
|
||||||
|
|
||||||
State = State0#machine_state{client_ids = Ids1},
|
State = State0#machine_state{client_ids = Ids1},
|
||||||
{State, ok, Effects ++ snapshot_effects(Meta, State)};
|
{State, ok, Effects ++ snapshot_effects(Meta, State)};
|
||||||
|
|
||||||
apply(_Meta, Unknown, State) ->
|
apply(_Meta, Unknown, State) ->
|
||||||
logger:error("MQTT Raft state machine received an unknown command ~tp", [Unknown]),
|
logger:error("MQTT Raft state machine received an unknown command ~tp", [Unknown]),
|
||||||
{State, {error, {unknown_command, Unknown}}, []}.
|
{State, {error, {unknown_command, Unknown}}, []}.
|
||||||
|
@ -141,7 +127,7 @@ state_enter(_, _) ->
|
||||||
%% Avoids blocking the Raft leader.
|
%% Avoids blocking the Raft leader.
|
||||||
-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
|
-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
|
||||||
notify_connection(Pid, Reason) ->
|
notify_connection(Pid, Reason) ->
|
||||||
spawn(fun() -> gen_server2:cast(Pid, Reason) end).
|
spawn(fun() -> gen_server2:cast(Pid, Reason) end).
|
||||||
|
|
||||||
-spec snapshot_effects(map(), state()) -> ra_machine:effects().
|
-spec snapshot_effects(map(), state()) -> ra_machine:effects().
|
||||||
snapshot_effects(#{index := RaftIdx}, State) ->
|
snapshot_effects(#{index := RaftIdx}, State) ->
|
||||||
|
|
|
@ -6,15 +6,8 @@
|
||||||
%%
|
%%
|
||||||
-module(mqtt_node).
|
-module(mqtt_node).
|
||||||
|
|
||||||
-export([
|
-export([start/0, node_id/0, server_id/0, all_node_ids/0, leave/1, trigger_election/0,
|
||||||
start/0,
|
delete/1]).
|
||||||
node_id/0,
|
|
||||||
server_id/0,
|
|
||||||
all_node_ids/0,
|
|
||||||
leave/1,
|
|
||||||
trigger_election/0,
|
|
||||||
delete/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-define(ID_NAME, mqtt_node).
|
-define(ID_NAME, mqtt_node).
|
||||||
-define(START_TIMEOUT, 100_000).
|
-define(START_TIMEOUT, 100_000).
|
||||||
|
@ -32,11 +25,8 @@ server_id(Node) ->
|
||||||
{?ID_NAME, Node}.
|
{?ID_NAME, Node}.
|
||||||
|
|
||||||
all_node_ids() ->
|
all_node_ids() ->
|
||||||
[
|
[server_id(N) || N <- rabbit_nodes:all(),
|
||||||
server_id(N)
|
can_participate_in_clientid_tracking(N)].
|
||||||
|| N <- rabbit_nodes:all(),
|
|
||||||
can_participate_in_clientid_tracking(N)
|
|
||||||
].
|
|
||||||
|
|
||||||
start() ->
|
start() ->
|
||||||
%% 3s to 6s randomized
|
%% 3s to 6s randomized
|
||||||
|
@ -50,41 +40,34 @@ start(Delay, AttemptsLeft) ->
|
||||||
NodeId = server_id(),
|
NodeId = server_id(),
|
||||||
Nodes = compatible_peer_servers(),
|
Nodes = compatible_peer_servers(),
|
||||||
case ra_directory:uid_of(?RA_SYSTEM, ?ID_NAME) of
|
case ra_directory:uid_of(?RA_SYSTEM, ?ID_NAME) of
|
||||||
undefined ->
|
undefined ->
|
||||||
case Nodes of
|
case Nodes of
|
||||||
[] ->
|
[] ->
|
||||||
%% Since cluster members are not known ahead of time and initial boot can be happening in parallel,
|
%% Since cluster members are not known ahead of time and initial boot can be happening in parallel,
|
||||||
%% we wait and check a few times (up to a few seconds) to see if we can discover any peers to
|
%% we wait and check a few times (up to a few seconds) to see if we can discover any peers to
|
||||||
%% join before forming a cluster. This reduces the probability of N independent clusters being
|
%% join before forming a cluster. This reduces the probability of N independent clusters being
|
||||||
%% formed in the common scenario of N nodes booting in parallel e.g. because they were started
|
%% formed in the common scenario of N nodes booting in parallel e.g. because they were started
|
||||||
%% at the same time by a deployment tool.
|
%% at the same time by a deployment tool.
|
||||||
%%
|
%%
|
||||||
%% This scenario does not guarantee single cluster formation but without knowing the list of members
|
%% This scenario does not guarantee single cluster formation but without knowing the list of members
|
||||||
%% ahead of time, this is a best effort workaround. Multi-node consensus is apparently hard
|
%% ahead of time, this is a best effort workaround. Multi-node consensus is apparently hard
|
||||||
%% to achieve without having consensus around expected cluster members.
|
%% to achieve without having consensus around expected cluster members.
|
||||||
rabbit_log:info(
|
rabbit_log:info("MQTT: will wait for ~tp more ms for cluster members to join before triggering a Raft leader election", [Delay]),
|
||||||
"MQTT: will wait for ~tp more ms for cluster members to join before triggering a Raft leader election",
|
timer:sleep(Delay),
|
||||||
[Delay]
|
start(Delay, AttemptsLeft - 1);
|
||||||
),
|
Peers ->
|
||||||
timer:sleep(Delay),
|
%% Trigger an election.
|
||||||
start(Delay, AttemptsLeft - 1);
|
%% This is required when we start a node for the first time.
|
||||||
Peers ->
|
%% Using default timeout because it supposed to reply fast.
|
||||||
%% Trigger an election.
|
rabbit_log:info("MQTT: discovered ~tp cluster peers that support client ID tracking", [length(Peers)]),
|
||||||
%% This is required when we start a node for the first time.
|
ok = start_server(),
|
||||||
%% Using default timeout because it supposed to reply fast.
|
_ = join_peers(NodeId, Peers),
|
||||||
rabbit_log:info(
|
ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
|
||||||
"MQTT: discovered ~tp cluster peers that support client ID tracking", [
|
end;
|
||||||
length(Peers)
|
_ ->
|
||||||
]
|
_ = join_peers(NodeId, Nodes),
|
||||||
),
|
ok = ra:restart_server(?RA_SYSTEM, NodeId),
|
||||||
ok = start_server(),
|
ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
|
||||||
_ = join_peers(NodeId, Peers),
|
|
||||||
ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
_ = join_peers(NodeId, Nodes),
|
|
||||||
ok = ra:restart_server(?RA_SYSTEM, NodeId),
|
|
||||||
ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
compatible_peer_servers() ->
|
compatible_peer_servers() ->
|
||||||
|
@ -95,15 +78,14 @@ start_server() ->
|
||||||
Nodes = compatible_peer_servers(),
|
Nodes = compatible_peer_servers(),
|
||||||
UId = ra:new_uid(ra_lib:to_binary(?ID_NAME)),
|
UId = ra:new_uid(ra_lib:to_binary(?ID_NAME)),
|
||||||
Timeout = application:get_env(kernel, net_ticktime, 60) + 5,
|
Timeout = application:get_env(kernel, net_ticktime, 60) + 5,
|
||||||
Conf = #{
|
Conf = #{cluster_name => ?ID_NAME,
|
||||||
cluster_name => ?ID_NAME,
|
id => NodeId,
|
||||||
id => NodeId,
|
uid => UId,
|
||||||
uid => UId,
|
friendly_name => atom_to_list(?ID_NAME),
|
||||||
friendly_name => atom_to_list(?ID_NAME),
|
initial_members => Nodes,
|
||||||
initial_members => Nodes,
|
log_init_args => #{uid => UId},
|
||||||
log_init_args => #{uid => UId},
|
tick_timeout => Timeout,
|
||||||
tick_timeout => Timeout,
|
machine => {module, mqtt_machine, #{}}
|
||||||
machine => {module, mqtt_machine, #{}}
|
|
||||||
},
|
},
|
||||||
ra:start_server(?RA_SYSTEM, Conf).
|
ra:start_server(?RA_SYSTEM, Conf).
|
||||||
|
|
||||||
|
@ -121,13 +103,11 @@ join_peers(NodeId, Nodes, RetriesLeft) ->
|
||||||
case ra:members(Nodes, ?START_TIMEOUT) of
|
case ra:members(Nodes, ?START_TIMEOUT) of
|
||||||
{ok, Members, _} ->
|
{ok, Members, _} ->
|
||||||
case lists:member(NodeId, Members) of
|
case lists:member(NodeId, Members) of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> ra:add_member(Members, NodeId)
|
false -> ra:add_member(Members, NodeId)
|
||||||
end;
|
end;
|
||||||
{timeout, _} ->
|
{timeout, _} ->
|
||||||
rabbit_log:debug("MQTT: timed out contacting cluster peers, %s retries left", [
|
rabbit_log:debug("MQTT: timed out contacting cluster peers, %s retries left", [RetriesLeft]),
|
||||||
RetriesLeft
|
|
||||||
]),
|
|
||||||
timer:sleep(?RETRY_INTERVAL),
|
timer:sleep(?RETRY_INTERVAL),
|
||||||
join_peers(NodeId, Nodes, RetriesLeft - 1);
|
join_peers(NodeId, Nodes, RetriesLeft - 1);
|
||||||
Err ->
|
Err ->
|
||||||
|
@ -148,12 +128,12 @@ leave(Node) ->
|
||||||
can_participate_in_clientid_tracking(Node) ->
|
can_participate_in_clientid_tracking(Node) ->
|
||||||
case rpc:call(Node, mqtt_machine, module_info, []) of
|
case rpc:call(Node, mqtt_machine, module_info, []) of
|
||||||
{badrpc, _} -> false;
|
{badrpc, _} -> false;
|
||||||
_ -> true
|
_ -> true
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec delete(Args) -> Ret when
|
-spec delete(Args) -> Ret when
|
||||||
Args :: rabbit_feature_flags:enable_callback_args(),
|
Args :: rabbit_feature_flags:enable_callback_args(),
|
||||||
Ret :: rabbit_feature_flags:enable_callback_ret().
|
Ret :: rabbit_feature_flags:enable_callback_ret().
|
||||||
delete(_) ->
|
delete(_) ->
|
||||||
RaNodes = all_node_ids(),
|
RaNodes = all_node_ids(),
|
||||||
Nodes = lists:map(fun({_, N}) -> N end, RaNodes),
|
Nodes = lists:map(fun({_, N}) -> N end, RaNodes),
|
||||||
|
@ -171,13 +151,12 @@ delete(_) ->
|
||||||
{ok, _Leader} ->
|
{ok, _Leader} ->
|
||||||
rabbit_log:info("Successfully deleted Ra cluster ~s", [?ID_NAME]),
|
rabbit_log:info("Successfully deleted Ra cluster ~s", [?ID_NAME]),
|
||||||
ok;
|
ok;
|
||||||
{error, _} = Err ->
|
{error, _} = Err ->
|
||||||
rabbit_log:info("Failed to delete Ra cluster ~s: ~p", [?ID_NAME, Err]),
|
rabbit_log:info("Failed to delete Ra cluster ~s: ~p", [?ID_NAME, Err]),
|
||||||
Err
|
Err
|
||||||
catch
|
catch exit:{{shutdown, delete}, _Stacktrace} ->
|
||||||
exit:{{shutdown, delete}, _Stacktrace} ->
|
rabbit_log:info("Ra cluster ~s already being deleted", [?ID_NAME]),
|
||||||
rabbit_log:info("Ra cluster ~s already being deleted", [?ID_NAME]),
|
ok
|
||||||
ok
|
|
||||||
end
|
end
|
||||||
after
|
after
|
||||||
true = global:del_lock(LockId, Nodes),
|
true = global:del_lock(LockId, Nodes),
|
||||||
|
|
|
@ -13,13 +13,11 @@
|
||||||
-include_lib("stdlib/include/assert.hrl").
|
-include_lib("stdlib/include/assert.hrl").
|
||||||
|
|
||||||
-export([start/2, stop/1]).
|
-export([start/2, stop/1]).
|
||||||
-export([
|
-export([emit_connection_info_all/4,
|
||||||
emit_connection_info_all/4,
|
emit_connection_info_local/3,
|
||||||
emit_connection_info_local/3,
|
close_local_client_connections/1,
|
||||||
close_local_client_connections/1,
|
%% Exported for tests, but could also be used for debugging.
|
||||||
%% Exported for tests, but could also be used for debugging.
|
local_connection_pids/0]).
|
||||||
local_connection_pids/0
|
|
||||||
]).
|
|
||||||
|
|
||||||
start(normal, []) ->
|
start(normal, []) ->
|
||||||
init_global_counters(),
|
init_global_counters(),
|
||||||
|
@ -33,11 +31,10 @@ start(normal, []) ->
|
||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
Result = rabbit_mqtt_sup:start_link({Listeners, SslListeners}, []),
|
Result = rabbit_mqtt_sup:start_link({Listeners, SslListeners}, []),
|
||||||
EMPid =
|
EMPid = case rabbit_event:start_link() of
|
||||||
case rabbit_event:start_link() of
|
{ok, Pid} -> Pid;
|
||||||
{ok, Pid} -> Pid;
|
{error, {already_started, Pid}} -> Pid
|
||||||
{error, {already_started, Pid}} -> Pid
|
end,
|
||||||
end,
|
|
||||||
gen_event:add_handler(EMPid, rabbit_mqtt_internal_event_handler, []),
|
gen_event:add_handler(EMPid, rabbit_mqtt_internal_event_handler, []),
|
||||||
Result.
|
Result.
|
||||||
|
|
||||||
|
@ -55,15 +52,9 @@ emit_connection_info_all(Nodes, Items, Ref, AggregatorPid) ->
|
||||||
%% remaining nodes, we send back 'finished' so that the CLI does not time out.
|
%% remaining nodes, we send back 'finished' so that the CLI does not time out.
|
||||||
[AggregatorPid ! {Ref, finished} || _ <- lists:seq(1, length(Nodes) - 1)];
|
[AggregatorPid ! {Ref, finished} || _ <- lists:seq(1, length(Nodes) - 1)];
|
||||||
false ->
|
false ->
|
||||||
Pids = [
|
Pids = [spawn_link(Node, ?MODULE, emit_connection_info_local,
|
||||||
spawn_link(
|
[Items, Ref, AggregatorPid])
|
||||||
Node,
|
|| Node <- Nodes],
|
||||||
?MODULE,
|
|
||||||
emit_connection_info_local,
|
|
||||||
[Items, Ref, AggregatorPid]
|
|
||||||
)
|
|
||||||
|| Node <- Nodes
|
|
||||||
],
|
|
||||||
rabbit_control_misc:await_emitters_termination(Pids)
|
rabbit_control_misc:await_emitters_termination(Pids)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -74,23 +65,17 @@ emit_connection_info_local(Items, Ref, AggregatorPid) ->
|
||||||
|
|
||||||
emit_connection_info(Items, Ref, AggregatorPid, Pids) ->
|
emit_connection_info(Items, Ref, AggregatorPid, Pids) ->
|
||||||
rabbit_control_misc:emitting_map_with_exit_handler(
|
rabbit_control_misc:emitting_map_with_exit_handler(
|
||||||
AggregatorPid,
|
AggregatorPid, Ref,
|
||||||
Ref,
|
fun(Pid) ->
|
||||||
fun(Pid) ->
|
rabbit_mqtt_reader:info(Pid, Items)
|
||||||
rabbit_mqtt_reader:info(Pid, Items)
|
end, Pids).
|
||||||
end,
|
|
||||||
Pids
|
|
||||||
).
|
|
||||||
|
|
||||||
-spec close_local_client_connections(string() | binary()) -> {'ok', non_neg_integer()}.
|
-spec close_local_client_connections(string() | binary()) -> {'ok', non_neg_integer()}.
|
||||||
close_local_client_connections(Reason) ->
|
close_local_client_connections(Reason) ->
|
||||||
Pids = local_connection_pids(),
|
Pids = local_connection_pids(),
|
||||||
lists:foreach(
|
lists:foreach(fun(Pid) ->
|
||||||
fun(Pid) ->
|
rabbit_mqtt_reader:close_connection(Pid, Reason)
|
||||||
rabbit_mqtt_reader:close_connection(Pid, Reason)
|
end, Pids),
|
||||||
end,
|
|
||||||
Pids
|
|
||||||
),
|
|
||||||
{ok, length(Pids)}.
|
{ok, length(Pids)}.
|
||||||
|
|
||||||
-spec local_connection_pids() -> [pid()].
|
-spec local_connection_pids() -> [pid()].
|
||||||
|
@ -101,12 +86,9 @@ local_connection_pids() ->
|
||||||
lists:filter(fun(Pid) -> node(Pid) =:= node() end, AllPids);
|
lists:filter(fun(Pid) -> node(Pid) =:= node() end, AllPids);
|
||||||
false ->
|
false ->
|
||||||
PgScope = persistent_term:get(?PG_SCOPE),
|
PgScope = persistent_term:get(?PG_SCOPE),
|
||||||
lists:flatmap(
|
lists:flatmap(fun(Group) ->
|
||||||
fun(Group) ->
|
pg:get_local_members(PgScope, Group)
|
||||||
pg:get_local_members(PgScope, Group)
|
end, pg:which_groups(PgScope))
|
||||||
end,
|
|
||||||
pg:which_groups(PgScope)
|
|
||||||
)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
init_global_counters() ->
|
init_global_counters() ->
|
||||||
|
|
|
@ -9,13 +9,8 @@
|
||||||
|
|
||||||
-include("mqtt_machine.hrl").
|
-include("mqtt_machine.hrl").
|
||||||
|
|
||||||
-export([
|
-export([register/2, register/3, unregister/2,
|
||||||
register/2, register/3,
|
list/0, list_pids/0, leave/1]).
|
||||||
unregister/2,
|
|
||||||
list/0,
|
|
||||||
list_pids/0,
|
|
||||||
leave/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
%%----------------------------------------------------------------------------
|
%%----------------------------------------------------------------------------
|
||||||
-spec register(term(), pid()) -> {ok, reference()} | {error, term()}.
|
-spec register(term(), pid()) -> {ok, reference()} | {error, term()}.
|
||||||
|
@ -26,7 +21,7 @@ register(ClientId, Pid) ->
|
||||||
case ra:members(NodeId) of
|
case ra:members(NodeId) of
|
||||||
{ok, _, Leader} ->
|
{ok, _, Leader} ->
|
||||||
register(Leader, ClientId, Pid);
|
register(Leader, ClientId, Pid);
|
||||||
_ = Error ->
|
_ = Error ->
|
||||||
Error
|
Error
|
||||||
end;
|
end;
|
||||||
Leader ->
|
Leader ->
|
||||||
|
@ -65,31 +60,25 @@ list(QF) ->
|
||||||
undefined ->
|
undefined ->
|
||||||
NodeIds = mqtt_node:all_node_ids(),
|
NodeIds = mqtt_node:all_node_ids(),
|
||||||
case ra:leader_query(NodeIds, QF) of
|
case ra:leader_query(NodeIds, QF) of
|
||||||
{ok, {_, Result}, _} ->
|
{ok, {_, Result}, _} -> Result;
|
||||||
Result;
|
{timeout, _} ->
|
||||||
{timeout, _} ->
|
rabbit_log:debug("~ts:list/1 leader query timed out",
|
||||||
rabbit_log:debug(
|
[?MODULE]),
|
||||||
"~ts:list/1 leader query timed out",
|
|
||||||
[?MODULE]
|
|
||||||
),
|
|
||||||
[]
|
[]
|
||||||
end;
|
end;
|
||||||
Leader ->
|
Leader ->
|
||||||
case ra:leader_query(Leader, QF) of
|
case ra:leader_query(Leader, QF) of
|
||||||
{ok, {_, Result}, _} ->
|
{ok, {_, Result}, _} -> Result;
|
||||||
Result;
|
|
||||||
{error, _} ->
|
{error, _} ->
|
||||||
[];
|
[];
|
||||||
{timeout, _} ->
|
{timeout, _} ->
|
||||||
rabbit_log:debug(
|
rabbit_log:debug("~ts:list/1 leader query timed out",
|
||||||
"~ts:list/1 leader query timed out",
|
[?MODULE]),
|
||||||
[?MODULE]
|
|
||||||
),
|
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec leave(binary()) -> ok | timeout | nodedown.
|
-spec leave(binary()) -> ok | timeout | nodedown.
|
||||||
leave(NodeBin) ->
|
leave(NodeBin) ->
|
||||||
Node = binary_to_atom(NodeBin, utf8),
|
Node = binary_to_atom(NodeBin, utf8),
|
||||||
ServerId = mqtt_node:server_id(),
|
ServerId = mqtt_node:server_id(),
|
||||||
|
|
|
@ -10,15 +10,13 @@
|
||||||
-include("rabbit_mqtt_packet.hrl").
|
-include("rabbit_mqtt_packet.hrl").
|
||||||
-compile({no_auto_import, [size/1]}).
|
-compile({no_auto_import, [size/1]}).
|
||||||
|
|
||||||
-export([
|
-export([init/0,
|
||||||
init/0,
|
insert/3,
|
||||||
insert/3,
|
confirm/3,
|
||||||
confirm/3,
|
reject/2,
|
||||||
reject/2,
|
remove_queue/2,
|
||||||
remove_queue/2,
|
size/1,
|
||||||
size/1,
|
contains/2]).
|
||||||
contains/2
|
|
||||||
]).
|
|
||||||
|
|
||||||
%% As done in OTP's sets module:
|
%% As done in OTP's sets module:
|
||||||
%% Empty list is cheaper to serialize than atom.
|
%% Empty list is cheaper to serialize than atom.
|
||||||
|
@ -41,32 +39,26 @@ contains(PktId, State) ->
|
||||||
maps:is_key(PktId, State).
|
maps:is_key(PktId, State).
|
||||||
|
|
||||||
-spec insert(packet_id(), [queue_name()], state()) -> state().
|
-spec insert(packet_id(), [queue_name()], state()) -> state().
|
||||||
insert(PktId, QNames, State) when
|
insert(PktId, QNames, State)
|
||||||
is_integer(PktId) andalso
|
when is_integer(PktId) andalso
|
||||||
PktId > 0 andalso
|
PktId > 0 andalso
|
||||||
not is_map_key(PktId, State)
|
not is_map_key(PktId, State) ->
|
||||||
->
|
|
||||||
QMap = maps:from_keys(QNames, ?VALUE),
|
QMap = maps:from_keys(QNames, ?VALUE),
|
||||||
maps:put(PktId, QMap, State).
|
maps:put(PktId, QMap, State).
|
||||||
|
|
||||||
-spec confirm([packet_id()], queue_name(), state()) ->
|
-spec confirm([packet_id()], queue_name(), state()) ->
|
||||||
{[packet_id()], state()}.
|
{[packet_id()], state()}.
|
||||||
confirm(PktIds, QName, State0) ->
|
confirm(PktIds, QName, State0) ->
|
||||||
{L0, State} = lists:foldl(
|
{L0, State} = lists:foldl(fun(PktId, Acc) ->
|
||||||
fun(PktId, Acc) ->
|
confirm_one(PktId, QName, Acc)
|
||||||
confirm_one(PktId, QName, Acc)
|
end, {[], State0}, PktIds),
|
||||||
end,
|
|
||||||
{[], State0},
|
|
||||||
PktIds
|
|
||||||
),
|
|
||||||
L = lists:reverse(L0),
|
L = lists:reverse(L0),
|
||||||
{L, State}.
|
{L, State}.
|
||||||
|
|
||||||
-spec reject(packet_id(), state()) ->
|
-spec reject(packet_id(), state()) ->
|
||||||
{ok, state()} | {error, not_found}.
|
{ok, state()} | {error, not_found}.
|
||||||
reject(PktId, State0) when
|
reject(PktId, State0)
|
||||||
is_integer(PktId)
|
when is_integer(PktId) ->
|
||||||
->
|
|
||||||
case maps:take(PktId, State0) of
|
case maps:take(PktId, State0) of
|
||||||
{_, State} ->
|
{_, State} ->
|
||||||
{ok, State};
|
{ok, State};
|
||||||
|
@ -79,31 +71,24 @@ reject(PktId, State0) when
|
||||||
{[packet_id()], state()}.
|
{[packet_id()], state()}.
|
||||||
remove_queue(QName, State) ->
|
remove_queue(QName, State) ->
|
||||||
PktIds = maps:fold(
|
PktIds = maps:fold(
|
||||||
fun
|
fun(PktId, QMap, PktIds)
|
||||||
(PktId, QMap, PktIds) when
|
when is_map_key(QName, QMap) ->
|
||||||
is_map_key(QName, QMap)
|
[PktId | PktIds];
|
||||||
->
|
(_, _, PktIds) ->
|
||||||
[PktId | PktIds];
|
PktIds
|
||||||
(_, _, PktIds) ->
|
end, [], State),
|
||||||
PktIds
|
|
||||||
end,
|
|
||||||
[],
|
|
||||||
State
|
|
||||||
),
|
|
||||||
confirm(lists:sort(PktIds), QName, State).
|
confirm(lists:sort(PktIds), QName, State).
|
||||||
|
|
||||||
%% INTERNAL
|
%% INTERNAL
|
||||||
|
|
||||||
confirm_one(PktId, QName, {PktIds, State0}) when
|
confirm_one(PktId, QName, {PktIds, State0})
|
||||||
is_integer(PktId)
|
when is_integer(PktId) ->
|
||||||
->
|
|
||||||
case maps:take(PktId, State0) of
|
case maps:take(PktId, State0) of
|
||||||
{QMap0, State1} when
|
{QMap0, State1}
|
||||||
is_map_key(QName, QMap0) andalso
|
when is_map_key(QName, QMap0)
|
||||||
map_size(QMap0) =:= 1
|
andalso map_size(QMap0) =:= 1 ->
|
||||||
->
|
|
||||||
%% last queue confirm
|
%% last queue confirm
|
||||||
{[PktId | PktIds], State1};
|
{[PktId| PktIds], State1};
|
||||||
{QMap0, State1} ->
|
{QMap0, State1} ->
|
||||||
QMap = maps:remove(QName, QMap0),
|
QMap = maps:remove(QName, QMap0),
|
||||||
State = maps:put(PktId, QMap, State1),
|
State = maps:put(PktId, QMap, State1),
|
||||||
|
|
|
@ -12,19 +12,17 @@
|
||||||
-export([track_client_id_in_ra/0]).
|
-export([track_client_id_in_ra/0]).
|
||||||
|
|
||||||
-rabbit_feature_flag(
|
-rabbit_feature_flag(
|
||||||
{?QUEUE_TYPE_QOS_0, #{
|
{?QUEUE_TYPE_QOS_0,
|
||||||
desc => "Support pseudo queue type for MQTT QoS 0 subscribers omitting a queue process",
|
#{desc => "Support pseudo queue type for MQTT QoS 0 subscribers omitting a queue process",
|
||||||
stability => stable
|
stability => stable
|
||||||
}}
|
}}).
|
||||||
).
|
|
||||||
|
|
||||||
-rabbit_feature_flag(
|
-rabbit_feature_flag(
|
||||||
{delete_ra_cluster_mqtt_node, #{
|
{delete_ra_cluster_mqtt_node,
|
||||||
desc => "Delete Ra cluster 'mqtt_node' since MQTT client IDs are tracked locally",
|
#{desc => "Delete Ra cluster 'mqtt_node' since MQTT client IDs are tracked locally",
|
||||||
stability => stable,
|
stability => stable,
|
||||||
callbacks => #{enable => {mqtt_node, delete}}
|
callbacks => #{enable => {mqtt_node, delete}}
|
||||||
}}
|
}}).
|
||||||
).
|
|
||||||
|
|
||||||
-spec track_client_id_in_ra() -> boolean().
|
-spec track_client_id_in_ra() -> boolean().
|
||||||
track_client_id_in_ra() ->
|
track_client_id_in_ra() ->
|
||||||
|
|
|
@ -28,9 +28,7 @@ handle_event({event, vhost_deleted, Info, _, _}, ?STATE) ->
|
||||||
{ok, ?STATE};
|
{ok, ?STATE};
|
||||||
handle_event({event, maintenance_connections_closed, _Info, _, _}, ?STATE) ->
|
handle_event({event, maintenance_connections_closed, _Info, _, _}, ?STATE) ->
|
||||||
%% we should close our connections
|
%% we should close our connections
|
||||||
{ok, NConnections} = rabbit_mqtt:close_local_client_connections(
|
{ok, NConnections} = rabbit_mqtt:close_local_client_connections("node is being put into maintenance mode"),
|
||||||
"node is being put into maintenance mode"
|
|
||||||
),
|
|
||||||
rabbit_log:warning("Closed ~b local MQTT client connections", [NConnections]),
|
rabbit_log:warning("Closed ~b local MQTT client connections", [NConnections]),
|
||||||
{ok, ?STATE};
|
{ok, ?STATE};
|
||||||
handle_event(_Event, ?STATE) ->
|
handle_event(_Event, ?STATE) ->
|
||||||
|
|
|
@ -1,26 +1,23 @@
|
||||||
-module(rabbit_mqtt_keepalive).
|
-module(rabbit_mqtt_keepalive).
|
||||||
|
|
||||||
-export([
|
-export([init/0,
|
||||||
init/0,
|
start/2,
|
||||||
start/2,
|
handle/2,
|
||||||
handle/2,
|
start_timer/1,
|
||||||
start_timer/1,
|
cancel_timer/1,
|
||||||
cancel_timer/1,
|
interval_secs/1]).
|
||||||
interval_secs/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-export_type([state/0]).
|
-export_type([state/0]).
|
||||||
|
|
||||||
-record(state, {
|
-record(state, {
|
||||||
%% Keep Alive value as sent in the CONNECT packet.
|
%% Keep Alive value as sent in the CONNECT packet.
|
||||||
interval_secs :: pos_integer(),
|
interval_secs :: pos_integer(),
|
||||||
timer :: reference(),
|
timer :: reference(),
|
||||||
socket :: inet:socket(),
|
socket :: inet:socket(),
|
||||||
recv_oct :: non_neg_integer(),
|
recv_oct :: non_neg_integer(),
|
||||||
received :: boolean()
|
received :: boolean()}).
|
||||||
}).
|
|
||||||
|
|
||||||
-opaque state() :: disabled | #state{}.
|
-opaque(state() :: disabled | #state{}).
|
||||||
|
|
||||||
-spec init() -> state().
|
-spec init() -> state().
|
||||||
init() ->
|
init() ->
|
||||||
|
@ -29,9 +26,8 @@ init() ->
|
||||||
-spec start(IntervalSeconds :: non_neg_integer(), inet:socket()) -> ok.
|
-spec start(IntervalSeconds :: non_neg_integer(), inet:socket()) -> ok.
|
||||||
start(0, _Sock) ->
|
start(0, _Sock) ->
|
||||||
ok;
|
ok;
|
||||||
start(Seconds, Sock) when
|
start(Seconds, Sock)
|
||||||
is_integer(Seconds) andalso Seconds > 0
|
when is_integer(Seconds) andalso Seconds > 0 ->
|
||||||
->
|
|
||||||
self() ! {keepalive, {init, Seconds, Sock}},
|
self() ! {keepalive, {init, Seconds, Sock}},
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
@ -40,28 +36,20 @@ start(Seconds, Sock) when
|
||||||
handle({init, IntervalSecs, Sock}, _State) ->
|
handle({init, IntervalSecs, Sock}, _State) ->
|
||||||
case rabbit_net:getstat(Sock, [recv_oct]) of
|
case rabbit_net:getstat(Sock, [recv_oct]) of
|
||||||
{ok, [{recv_oct, RecvOct}]} ->
|
{ok, [{recv_oct, RecvOct}]} ->
|
||||||
{ok, #state{
|
{ok, #state{interval_secs = IntervalSecs,
|
||||||
interval_secs = IntervalSecs,
|
timer = start_timer0(IntervalSecs),
|
||||||
timer = start_timer0(IntervalSecs),
|
socket = Sock,
|
||||||
socket = Sock,
|
recv_oct = RecvOct,
|
||||||
recv_oct = RecvOct,
|
received = true}};
|
||||||
received = true
|
|
||||||
}};
|
|
||||||
{error, _} = Err ->
|
{error, _} = Err ->
|
||||||
Err
|
Err
|
||||||
end;
|
end;
|
||||||
handle(
|
handle(check, State = #state{socket = Sock,
|
||||||
check,
|
recv_oct = SameRecvOct,
|
||||||
State = #state{
|
received = ReceivedPreviously}) ->
|
||||||
socket = Sock,
|
|
||||||
recv_oct = SameRecvOct,
|
|
||||||
received = ReceivedPreviously
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
case rabbit_net:getstat(Sock, [recv_oct]) of
|
case rabbit_net:getstat(Sock, [recv_oct]) of
|
||||||
{ok, [{recv_oct, SameRecvOct}]} when
|
{ok, [{recv_oct, SameRecvOct}]}
|
||||||
ReceivedPreviously
|
when ReceivedPreviously ->
|
||||||
->
|
|
||||||
%% Did not receive from socket for the 1st time.
|
%% Did not receive from socket for the 1st time.
|
||||||
{ok, start_timer(State#state{received = false})};
|
{ok, start_timer(State#state{received = false})};
|
||||||
{ok, [{recv_oct, SameRecvOct}]} ->
|
{ok, [{recv_oct, SameRecvOct}]} ->
|
||||||
|
@ -69,11 +57,8 @@ handle(
|
||||||
{error, timeout};
|
{error, timeout};
|
||||||
{ok, [{recv_oct, NewRecvOct}]} ->
|
{ok, [{recv_oct, NewRecvOct}]} ->
|
||||||
%% Received from socket.
|
%% Received from socket.
|
||||||
{ok,
|
{ok, start_timer(State#state{recv_oct = NewRecvOct,
|
||||||
start_timer(State#state{
|
received = true})};
|
||||||
recv_oct = NewRecvOct,
|
|
||||||
received = true
|
|
||||||
})};
|
|
||||||
{error, _} = Err ->
|
{error, _} = Err ->
|
||||||
Err
|
Err
|
||||||
end.
|
end.
|
||||||
|
@ -89,13 +74,10 @@ start_timer0(KeepAliveSeconds) ->
|
||||||
erlang:send_after(timer_ms(KeepAliveSeconds), self(), {keepalive, check}).
|
erlang:send_after(timer_ms(KeepAliveSeconds), self(), {keepalive, check}).
|
||||||
|
|
||||||
-spec cancel_timer(state()) -> state().
|
-spec cancel_timer(state()) -> state().
|
||||||
cancel_timer(#state{timer = Ref} = State) when
|
cancel_timer(#state{timer = Ref} = State)
|
||||||
is_reference(Ref)
|
when is_reference(Ref) ->
|
||||||
->
|
ok = erlang:cancel_timer(Ref, [{async, true},
|
||||||
ok = erlang:cancel_timer(Ref, [
|
{info, false}]),
|
||||||
{async, true},
|
|
||||||
{info, false}
|
|
||||||
]),
|
|
||||||
State;
|
State;
|
||||||
cancel_timer(disabled) ->
|
cancel_timer(disabled) ->
|
||||||
disabled.
|
disabled.
|
||||||
|
|
|
@ -24,148 +24,119 @@
|
||||||
initial_state() -> none.
|
initial_state() -> none.
|
||||||
|
|
||||||
-spec parse(binary(), state()) ->
|
-spec parse(binary(), state()) ->
|
||||||
{more, state()}
|
{more, state()} |
|
||||||
| {ok, mqtt_packet(), binary()}
|
{ok, mqtt_packet(), binary()} |
|
||||||
| {error, any()}.
|
{error, any()}.
|
||||||
parse(<<>>, none) ->
|
parse(<<>>, none) ->
|
||||||
{more, fun(Bin) -> parse(Bin, none) end};
|
{more, fun(Bin) -> parse(Bin, none) end};
|
||||||
parse(<<MessageType:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, none) ->
|
parse(<<MessageType:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, none) ->
|
||||||
parse_remaining_len(Rest, #mqtt_packet_fixed{
|
parse_remaining_len(Rest, #mqtt_packet_fixed{ type = MessageType,
|
||||||
type = MessageType,
|
dup = bool(Dup),
|
||||||
dup = bool(Dup),
|
qos = QoS,
|
||||||
qos = QoS,
|
retain = bool(Retain) });
|
||||||
retain = bool(Retain)
|
parse(Bin, Cont) -> Cont(Bin).
|
||||||
});
|
|
||||||
parse(Bin, Cont) ->
|
|
||||||
Cont(Bin).
|
|
||||||
|
|
||||||
parse_remaining_len(<<>>, Fixed) ->
|
parse_remaining_len(<<>>, Fixed) ->
|
||||||
{more, fun(Bin) -> parse_remaining_len(Bin, Fixed) end};
|
{more, fun(Bin) -> parse_remaining_len(Bin, Fixed) end};
|
||||||
parse_remaining_len(Rest, Fixed) ->
|
parse_remaining_len(Rest, Fixed) ->
|
||||||
parse_remaining_len(Rest, Fixed, 1, 0).
|
parse_remaining_len(Rest, Fixed, 1, 0).
|
||||||
|
|
||||||
parse_remaining_len(_Bin, _Fixed, _Multiplier, Length) when
|
parse_remaining_len(_Bin, _Fixed, _Multiplier, Length)
|
||||||
Length > ?MAX_LEN
|
when Length > ?MAX_LEN ->
|
||||||
->
|
|
||||||
{error, invalid_mqtt_packet_len};
|
{error, invalid_mqtt_packet_len};
|
||||||
parse_remaining_len(<<>>, Fixed, Multiplier, Length) ->
|
parse_remaining_len(<<>>, Fixed, Multiplier, Length) ->
|
||||||
{more, fun(Bin) -> parse_remaining_len(Bin, Fixed, Multiplier, Length) end};
|
{more, fun(Bin) -> parse_remaining_len(Bin, Fixed, Multiplier, Length) end};
|
||||||
parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
|
parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
|
||||||
parse_remaining_len(Rest, Fixed, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
|
parse_remaining_len(Rest, Fixed, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
|
||||||
parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
|
parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
|
||||||
parse_packet(Rest, Fixed, Value + Len * Multiplier).
|
parse_packet(Rest, Fixed, Value + Len * Multiplier).
|
||||||
|
|
||||||
parse_packet(
|
parse_packet(Bin, #mqtt_packet_fixed{ type = Type,
|
||||||
Bin,
|
qos = Qos } = Fixed, Length)
|
||||||
#mqtt_packet_fixed{
|
when Length =< ?MAX_LEN ->
|
||||||
type = Type,
|
|
||||||
qos = Qos
|
|
||||||
} = Fixed,
|
|
||||||
Length
|
|
||||||
) when
|
|
||||||
Length =< ?MAX_LEN
|
|
||||||
->
|
|
||||||
case {Type, Bin} of
|
case {Type, Bin} of
|
||||||
{?CONNECT, <<PacketBin:Length/binary, Rest/binary>>} ->
|
{?CONNECT, <<PacketBin:Length/binary, Rest/binary>>} ->
|
||||||
{ProtoName, Rest1} = parse_utf(PacketBin),
|
{ProtoName, Rest1} = parse_utf(PacketBin),
|
||||||
<<ProtoVersion:8, Rest2/binary>> = Rest1,
|
<<ProtoVersion : 8, Rest2/binary>> = Rest1,
|
||||||
<<UsernameFlag:1, PasswordFlag:1, WillRetain:1, WillQos:2, WillFlag:1, CleanSession:1,
|
<<UsernameFlag : 1,
|
||||||
_Reserved:1, KeepAlive:16/big, Rest3/binary>> = Rest2,
|
PasswordFlag : 1,
|
||||||
{ClientId, Rest4} = parse_utf(Rest3),
|
WillRetain : 1,
|
||||||
|
WillQos : 2,
|
||||||
|
WillFlag : 1,
|
||||||
|
CleanSession : 1,
|
||||||
|
_Reserved : 1,
|
||||||
|
KeepAlive : 16/big,
|
||||||
|
Rest3/binary>> = Rest2,
|
||||||
|
{ClientId, Rest4} = parse_utf(Rest3),
|
||||||
{WillTopic, Rest5} = parse_utf(Rest4, WillFlag),
|
{WillTopic, Rest5} = parse_utf(Rest4, WillFlag),
|
||||||
{WillMsg, Rest6} = parse_msg(Rest5, WillFlag),
|
{WillMsg, Rest6} = parse_msg(Rest5, WillFlag),
|
||||||
{UserName, Rest7} = parse_utf(Rest6, UsernameFlag),
|
{UserName, Rest7} = parse_utf(Rest6, UsernameFlag),
|
||||||
{PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag),
|
{PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag),
|
||||||
case protocol_name_approved(ProtoVersion, ProtoName) of
|
case protocol_name_approved(ProtoVersion, ProtoName) of
|
||||||
true ->
|
true ->
|
||||||
wrap(
|
wrap(Fixed,
|
||||||
Fixed,
|
#mqtt_packet_connect{
|
||||||
#mqtt_packet_connect{
|
proto_ver = ProtoVersion,
|
||||||
proto_ver = ProtoVersion,
|
will_retain = bool(WillRetain),
|
||||||
will_retain = bool(WillRetain),
|
will_qos = WillQos,
|
||||||
will_qos = WillQos,
|
will_flag = bool(WillFlag),
|
||||||
will_flag = bool(WillFlag),
|
clean_sess = bool(CleanSession),
|
||||||
clean_sess = bool(CleanSession),
|
keep_alive = KeepAlive,
|
||||||
keep_alive = KeepAlive,
|
client_id = ClientId,
|
||||||
client_id = ClientId,
|
will_topic = WillTopic,
|
||||||
will_topic = WillTopic,
|
will_msg = WillMsg,
|
||||||
will_msg = WillMsg,
|
username = UserName,
|
||||||
username = UserName,
|
password = PasssWord}, Rest);
|
||||||
password = PasssWord
|
false ->
|
||||||
},
|
|
||||||
Rest
|
|
||||||
);
|
|
||||||
false ->
|
|
||||||
{error, protocol_header_corrupt}
|
{error, protocol_header_corrupt}
|
||||||
end;
|
end;
|
||||||
{?PUBLISH, <<PacketBin:Length/binary, Rest/binary>>} ->
|
{?PUBLISH, <<PacketBin:Length/binary, Rest/binary>>} ->
|
||||||
{TopicName, Rest1} = parse_utf(PacketBin),
|
{TopicName, Rest1} = parse_utf(PacketBin),
|
||||||
{PacketId, Payload} =
|
{PacketId, Payload} = case Qos of
|
||||||
case Qos of
|
0 -> {undefined, Rest1};
|
||||||
0 ->
|
_ -> <<M:16/big, R/binary>> = Rest1,
|
||||||
{undefined, Rest1};
|
{M, R}
|
||||||
_ ->
|
end,
|
||||||
<<M:16/big, R/binary>> = Rest1,
|
wrap(Fixed, #mqtt_packet_publish { topic_name = TopicName,
|
||||||
{M, R}
|
packet_id = PacketId },
|
||||||
end,
|
Payload, Rest);
|
||||||
wrap(
|
|
||||||
Fixed,
|
|
||||||
#mqtt_packet_publish{
|
|
||||||
topic_name = TopicName,
|
|
||||||
packet_id = PacketId
|
|
||||||
},
|
|
||||||
Payload,
|
|
||||||
Rest
|
|
||||||
);
|
|
||||||
{?PUBACK, <<PacketBin:Length/binary, Rest/binary>>} ->
|
{?PUBACK, <<PacketBin:Length/binary, Rest/binary>>} ->
|
||||||
<<PacketId:16/big>> = PacketBin,
|
<<PacketId:16/big>> = PacketBin,
|
||||||
wrap(Fixed, #mqtt_packet_publish{packet_id = PacketId}, Rest);
|
wrap(Fixed, #mqtt_packet_publish { packet_id = PacketId }, Rest);
|
||||||
{Subs, <<PacketBin:Length/binary, Rest/binary>>} when
|
{Subs, <<PacketBin:Length/binary, Rest/binary>>}
|
||||||
Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE
|
when Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE ->
|
||||||
->
|
|
||||||
1 = Qos,
|
1 = Qos,
|
||||||
<<PacketId:16/big, Rest1/binary>> = PacketBin,
|
<<PacketId:16/big, Rest1/binary>> = PacketBin,
|
||||||
Topics = parse_topics(Subs, Rest1, []),
|
Topics = parse_topics(Subs, Rest1, []),
|
||||||
wrap(
|
wrap(Fixed, #mqtt_packet_subscribe { packet_id = PacketId,
|
||||||
Fixed,
|
topic_table = Topics }, Rest);
|
||||||
#mqtt_packet_subscribe{
|
{Minimal, Rest}
|
||||||
packet_id = PacketId,
|
when Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ ->
|
||||||
topic_table = Topics
|
|
||||||
},
|
|
||||||
Rest
|
|
||||||
);
|
|
||||||
{Minimal, Rest} when
|
|
||||||
Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ
|
|
||||||
->
|
|
||||||
Length = 0,
|
Length = 0,
|
||||||
wrap(Fixed, Rest);
|
wrap(Fixed, Rest);
|
||||||
{_, TooShortBin} when
|
{_, TooShortBin}
|
||||||
byte_size(TooShortBin) < Length
|
when byte_size(TooShortBin) < Length ->
|
||||||
->
|
|
||||||
{more, fun(BinMore) ->
|
{more, fun(BinMore) ->
|
||||||
parse_packet(
|
parse_packet(<<TooShortBin/binary, BinMore/binary>>,
|
||||||
<<TooShortBin/binary, BinMore/binary>>,
|
Fixed, Length)
|
||||||
Fixed,
|
end}
|
||||||
Length
|
|
||||||
)
|
|
||||||
end}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
parse_topics(_, <<>>, Topics) ->
|
parse_topics(_, <<>>, Topics) ->
|
||||||
Topics;
|
Topics;
|
||||||
parse_topics(?SUBSCRIBE = Sub, Bin, Topics) ->
|
parse_topics(?SUBSCRIBE = Sub, Bin, Topics) ->
|
||||||
{Name, <<_:6, QoS:2, Rest/binary>>} = parse_utf(Bin),
|
{Name, <<_:6, QoS:2, Rest/binary>>} = parse_utf(Bin),
|
||||||
parse_topics(Sub, Rest, [#mqtt_topic{name = Name, qos = QoS} | Topics]);
|
parse_topics(Sub, Rest, [#mqtt_topic { name = Name, qos = QoS } | Topics]);
|
||||||
parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) ->
|
parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) ->
|
||||||
{Name, <<Rest/binary>>} = parse_utf(Bin),
|
{Name, <<Rest/binary>>} = parse_utf(Bin),
|
||||||
parse_topics(Sub, Rest, [#mqtt_topic{name = Name} | Topics]).
|
parse_topics(Sub, Rest, [#mqtt_topic { name = Name } | Topics]).
|
||||||
|
|
||||||
wrap(Fixed, Variable, Payload, Rest) ->
|
wrap(Fixed, Variable, Payload, Rest) ->
|
||||||
{ok, #mqtt_packet{variable = Variable, fixed = Fixed, payload = Payload}, Rest}.
|
{ok, #mqtt_packet { variable = Variable, fixed = Fixed, payload = Payload }, Rest}.
|
||||||
wrap(Fixed, Variable, Rest) ->
|
wrap(Fixed, Variable, Rest) ->
|
||||||
{ok, #mqtt_packet{variable = Variable, fixed = Fixed}, Rest}.
|
{ok, #mqtt_packet { variable = Variable, fixed = Fixed }, Rest}.
|
||||||
wrap(Fixed, Rest) ->
|
wrap(Fixed, Rest) ->
|
||||||
{ok, #mqtt_packet{fixed = Fixed}, Rest}.
|
{ok, #mqtt_packet { fixed = Fixed }, Rest}.
|
||||||
|
|
||||||
parse_utf(Bin, 0) ->
|
parse_utf(Bin, 0) ->
|
||||||
{undefined, Bin};
|
{undefined, Bin};
|
||||||
|
@ -187,109 +158,72 @@ bool(1) -> true.
|
||||||
|
|
||||||
-spec serialise(#mqtt_packet{}, ?MQTT_PROTO_V3 | ?MQTT_PROTO_V4) ->
|
-spec serialise(#mqtt_packet{}, ?MQTT_PROTO_V3 | ?MQTT_PROTO_V4) ->
|
||||||
iodata().
|
iodata().
|
||||||
serialise(
|
serialise(#mqtt_packet{fixed = Fixed,
|
||||||
#mqtt_packet{
|
variable = Variable,
|
||||||
fixed = Fixed,
|
payload = Payload}, Vsn) ->
|
||||||
variable = Variable,
|
|
||||||
payload = Payload
|
|
||||||
},
|
|
||||||
Vsn
|
|
||||||
) ->
|
|
||||||
serialise_variable(Fixed, Variable, serialise_payload(Payload), Vsn).
|
serialise_variable(Fixed, Variable, serialise_payload(Payload), Vsn).
|
||||||
|
|
||||||
serialise_payload(undefined) ->
|
serialise_payload(undefined) ->
|
||||||
<<>>;
|
<<>>;
|
||||||
serialise_payload(P) when
|
serialise_payload(P)
|
||||||
is_binary(P) orelse is_list(P)
|
when is_binary(P) orelse is_list(P) ->
|
||||||
->
|
|
||||||
P.
|
P.
|
||||||
|
|
||||||
serialise_variable(
|
serialise_variable(#mqtt_packet_fixed { type = ?CONNACK } = Fixed,
|
||||||
#mqtt_packet_fixed{type = ?CONNACK} = Fixed,
|
#mqtt_packet_connack { session_present = SessionPresent,
|
||||||
#mqtt_packet_connack{
|
return_code = ReturnCode },
|
||||||
session_present = SessionPresent,
|
<<>> = PayloadBin, _Vsn) ->
|
||||||
return_code = ReturnCode
|
|
||||||
},
|
|
||||||
<<>> = PayloadBin,
|
|
||||||
_Vsn
|
|
||||||
) ->
|
|
||||||
VariableBin = <<?RESERVED:7, (opt(SessionPresent)):1, ReturnCode:8>>,
|
VariableBin = <<?RESERVED:7, (opt(SessionPresent)):1, ReturnCode:8>>,
|
||||||
serialise_fixed(Fixed, VariableBin, PayloadBin);
|
serialise_fixed(Fixed, VariableBin, PayloadBin);
|
||||||
serialise_variable(
|
|
||||||
#mqtt_packet_fixed{type = SubAck} = Fixed,
|
serialise_variable(#mqtt_packet_fixed { type = SubAck } = Fixed,
|
||||||
#mqtt_packet_suback{
|
#mqtt_packet_suback { packet_id = PacketId,
|
||||||
packet_id = PacketId,
|
qos_table = Qos },
|
||||||
qos_table = Qos
|
<<>> = _PayloadBin, Vsn)
|
||||||
},
|
when SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK ->
|
||||||
<<>> = _PayloadBin,
|
|
||||||
Vsn
|
|
||||||
) when
|
|
||||||
SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK
|
|
||||||
->
|
|
||||||
VariableBin = <<PacketId:16/big>>,
|
VariableBin = <<PacketId:16/big>>,
|
||||||
QosBin =
|
QosBin = case Vsn of
|
||||||
case Vsn of
|
?MQTT_PROTO_V3 ->
|
||||||
?MQTT_PROTO_V3 ->
|
<< <<?RESERVED:6, Q:2>> || Q <- Qos >>;
|
||||||
<<<<?RESERVED:6, Q:2>> || Q <- Qos>>;
|
?MQTT_PROTO_V4 ->
|
||||||
?MQTT_PROTO_V4 ->
|
%% Allow error code (0x80) in the MQTT SUBACK message.
|
||||||
%% Allow error code (0x80) in the MQTT SUBACK message.
|
<< <<Q:8>> || Q <- Qos >>
|
||||||
<<<<Q:8>> || Q <- Qos>>
|
end,
|
||||||
end,
|
|
||||||
serialise_fixed(Fixed, VariableBin, QosBin);
|
serialise_fixed(Fixed, VariableBin, QosBin);
|
||||||
serialise_variable(
|
|
||||||
#mqtt_packet_fixed{
|
serialise_variable(#mqtt_packet_fixed { type = ?PUBLISH,
|
||||||
type = ?PUBLISH,
|
qos = Qos } = Fixed,
|
||||||
qos = Qos
|
#mqtt_packet_publish { topic_name = TopicName,
|
||||||
} = Fixed,
|
packet_id = PacketId },
|
||||||
#mqtt_packet_publish{
|
Payload, _Vsn) ->
|
||||||
topic_name = TopicName,
|
|
||||||
packet_id = PacketId
|
|
||||||
},
|
|
||||||
Payload,
|
|
||||||
_Vsn
|
|
||||||
) ->
|
|
||||||
TopicBin = serialise_utf(TopicName),
|
TopicBin = serialise_utf(TopicName),
|
||||||
PacketIdBin =
|
PacketIdBin = case Qos of
|
||||||
case Qos of
|
0 -> <<>>;
|
||||||
0 -> <<>>;
|
1 -> <<PacketId:16/big>>
|
||||||
1 -> <<PacketId:16/big>>
|
end,
|
||||||
end,
|
|
||||||
serialise_fixed(Fixed, <<TopicBin/binary, PacketIdBin/binary>>, Payload);
|
serialise_fixed(Fixed, <<TopicBin/binary, PacketIdBin/binary>>, Payload);
|
||||||
serialise_variable(
|
|
||||||
#mqtt_packet_fixed{type = ?PUBACK} = Fixed,
|
serialise_variable(#mqtt_packet_fixed { type = ?PUBACK } = Fixed,
|
||||||
#mqtt_packet_publish{packet_id = PacketId},
|
#mqtt_packet_publish { packet_id = PacketId },
|
||||||
PayloadBin,
|
PayloadBin, _Vsn) ->
|
||||||
_Vsn
|
|
||||||
) ->
|
|
||||||
PacketIdBin = <<PacketId:16/big>>,
|
PacketIdBin = <<PacketId:16/big>>,
|
||||||
serialise_fixed(Fixed, PacketIdBin, PayloadBin);
|
serialise_fixed(Fixed, PacketIdBin, PayloadBin);
|
||||||
serialise_variable(
|
|
||||||
#mqtt_packet_fixed{} = Fixed,
|
serialise_variable(#mqtt_packet_fixed {} = Fixed,
|
||||||
undefined,
|
undefined,
|
||||||
<<>> = _PayloadBin,
|
<<>> = _PayloadBin, _Vsn) ->
|
||||||
_Vsn
|
|
||||||
) ->
|
|
||||||
serialise_fixed(Fixed, <<>>, <<>>).
|
serialise_fixed(Fixed, <<>>, <<>>).
|
||||||
|
|
||||||
serialise_fixed(
|
serialise_fixed(#mqtt_packet_fixed{ type = Type,
|
||||||
#mqtt_packet_fixed{
|
dup = Dup,
|
||||||
type = Type,
|
qos = Qos,
|
||||||
dup = Dup,
|
retain = Retain }, VariableBin, Payload)
|
||||||
qos = Qos,
|
when is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT ->
|
||||||
retain = Retain
|
|
||||||
},
|
|
||||||
VariableBin,
|
|
||||||
Payload
|
|
||||||
) when
|
|
||||||
is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT
|
|
||||||
->
|
|
||||||
Len = size(VariableBin) + iolist_size(Payload),
|
Len = size(VariableBin) + iolist_size(Payload),
|
||||||
true = (Len =< ?MAX_LEN),
|
true = (Len =< ?MAX_LEN),
|
||||||
LenBin = serialise_len(Len),
|
LenBin = serialise_len(Len),
|
||||||
[
|
[<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1,
|
||||||
<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1, LenBin/binary, VariableBin/binary>>,
|
LenBin/binary, VariableBin/binary>>, Payload].
|
||||||
Payload
|
|
||||||
].
|
|
||||||
|
|
||||||
serialise_utf(String) ->
|
serialise_utf(String) ->
|
||||||
StringBin = unicode:characters_to_binary(String),
|
StringBin = unicode:characters_to_binary(String),
|
||||||
|
@ -302,9 +236,9 @@ serialise_len(N) when N =< ?LOWBITS ->
|
||||||
serialise_len(N) ->
|
serialise_len(N) ->
|
||||||
<<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>.
|
<<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>.
|
||||||
|
|
||||||
opt(undefined) -> ?RESERVED;
|
opt(undefined) -> ?RESERVED;
|
||||||
opt(false) -> 0;
|
opt(false) -> 0;
|
||||||
opt(true) -> 1;
|
opt(true) -> 1;
|
||||||
opt(X) when is_integer(X) -> X.
|
opt(X) when is_integer(X) -> X.
|
||||||
|
|
||||||
protocol_name_approved(Ver, Name) ->
|
protocol_name_approved(Ver, Name) ->
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -24,50 +24,42 @@
|
||||||
|
|
||||||
%% Stateless rabbit_queue_type callbacks.
|
%% Stateless rabbit_queue_type callbacks.
|
||||||
-export([
|
-export([
|
||||||
is_stateful/0,
|
is_stateful/0,
|
||||||
declare/2,
|
declare/2,
|
||||||
delete/4,
|
delete/4,
|
||||||
deliver/2,
|
deliver/2,
|
||||||
is_enabled/0,
|
is_enabled/0,
|
||||||
is_compatible/3,
|
is_compatible/3,
|
||||||
is_recoverable/1,
|
is_recoverable/1,
|
||||||
recover/2,
|
recover/2,
|
||||||
purge/1,
|
purge/1,
|
||||||
policy_changed/1,
|
policy_changed/1,
|
||||||
info/2,
|
info/2,
|
||||||
stat/1,
|
stat/1,
|
||||||
capabilities/0,
|
capabilities/0,
|
||||||
notify_decorators/1
|
notify_decorators/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Stateful rabbit_queue_type callbacks are unsupported by this queue type.
|
%% Stateful rabbit_queue_type callbacks are unsupported by this queue type.
|
||||||
-define(STATEFUL_CALLBACKS, [
|
-define(STATEFUL_CALLBACKS,
|
||||||
init/1,
|
[
|
||||||
close/1,
|
init/1,
|
||||||
update/2,
|
close/1,
|
||||||
consume/3,
|
update/2,
|
||||||
cancel/5,
|
consume/3,
|
||||||
handle_event/3,
|
cancel/5,
|
||||||
settle/5,
|
handle_event/3,
|
||||||
credit/5,
|
settle/5,
|
||||||
dequeue/5,
|
credit/5,
|
||||||
state_info/1
|
dequeue/5,
|
||||||
]).
|
state_info/1
|
||||||
|
]).
|
||||||
-export(?STATEFUL_CALLBACKS).
|
-export(?STATEFUL_CALLBACKS).
|
||||||
-dialyzer({nowarn_function, ?STATEFUL_CALLBACKS}).
|
-dialyzer({nowarn_function, ?STATEFUL_CALLBACKS}).
|
||||||
-define(UNSUPPORTED(Args), erlang:error(unsupported, Args)).
|
-define(UNSUPPORTED(Args), erlang:error(unsupported, Args)).
|
||||||
|
|
||||||
-define(INFO_KEYS, [
|
-define(INFO_KEYS, [type, name, durable, auto_delete, arguments,
|
||||||
type,
|
pid, owner_pid, state, messages]).
|
||||||
name,
|
|
||||||
durable,
|
|
||||||
auto_delete,
|
|
||||||
arguments,
|
|
||||||
pid,
|
|
||||||
owner_pid,
|
|
||||||
state,
|
|
||||||
messages
|
|
||||||
]).
|
|
||||||
|
|
||||||
-spec is_stateful() ->
|
-spec is_stateful() ->
|
||||||
boolean().
|
boolean().
|
||||||
|
@ -75,8 +67,8 @@ is_stateful() ->
|
||||||
false.
|
false.
|
||||||
|
|
||||||
-spec declare(amqqueue:amqqueue(), node()) ->
|
-spec declare(amqqueue:amqqueue(), node()) ->
|
||||||
{'new' | 'existing' | 'owner_died', amqqueue:amqqueue()}
|
{'new' | 'existing' | 'owner_died', amqqueue:amqqueue()} |
|
||||||
| {'absent', amqqueue:amqqueue(), rabbit_amqqueue:absent_reason()}.
|
{'absent', amqqueue:amqqueue(), rabbit_amqqueue:absent_reason()}.
|
||||||
declare(Q0, _Node) ->
|
declare(Q0, _Node) ->
|
||||||
%% The queue gets persisted such that routing to this
|
%% The queue gets persisted such that routing to this
|
||||||
%% queue (via the topic exchange) works as usual.
|
%% queue (via the topic exchange) works as usual.
|
||||||
|
@ -84,29 +76,23 @@ declare(Q0, _Node) ->
|
||||||
{created, Q} ->
|
{created, Q} ->
|
||||||
Opts = amqqueue:get_options(Q),
|
Opts = amqqueue:get_options(Q),
|
||||||
ActingUser = maps:get(user, Opts, ?UNKNOWN_USER),
|
ActingUser = maps:get(user, Opts, ?UNKNOWN_USER),
|
||||||
rabbit_event:notify(
|
rabbit_event:notify(queue_created,
|
||||||
queue_created,
|
[{name, amqqueue:get_name(Q0)},
|
||||||
[
|
{durable, true},
|
||||||
{name, amqqueue:get_name(Q0)},
|
{auto_delete, false},
|
||||||
{durable, true},
|
{exclusive, true},
|
||||||
{auto_delete, false},
|
{type, amqqueue:get_type(Q0)},
|
||||||
{exclusive, true},
|
{arguments, amqqueue:get_arguments(Q0)},
|
||||||
{type, amqqueue:get_type(Q0)},
|
{user_who_performed_action, ActingUser}]),
|
||||||
{arguments, amqqueue:get_arguments(Q0)},
|
|
||||||
{user_who_performed_action, ActingUser}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
{new, Q};
|
{new, Q};
|
||||||
Other ->
|
Other ->
|
||||||
Other
|
Other
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec delete(
|
-spec delete(amqqueue:amqqueue(),
|
||||||
amqqueue:amqqueue(),
|
boolean(),
|
||||||
boolean(),
|
boolean(),
|
||||||
boolean(),
|
rabbit_types:username()) ->
|
||||||
rabbit_types:username()
|
|
||||||
) ->
|
|
||||||
rabbit_types:ok(non_neg_integer()).
|
rabbit_types:ok(non_neg_integer()).
|
||||||
delete(Q, _IfUnused, _IfEmpty, ActingUser) ->
|
delete(Q, _IfUnused, _IfEmpty, ActingUser) ->
|
||||||
QName = amqqueue:get_name(Q),
|
QName = amqqueue:get_name(Q),
|
||||||
|
@ -116,41 +102,35 @@ delete(Q, _IfUnused, _IfEmpty, ActingUser) ->
|
||||||
|
|
||||||
-spec deliver([{amqqueue:amqqueue(), stateless}], Delivery :: term()) ->
|
-spec deliver([{amqqueue:amqqueue(), stateless}], Delivery :: term()) ->
|
||||||
{[], rabbit_queue_type:actions()}.
|
{[], rabbit_queue_type:actions()}.
|
||||||
deliver(Qs, #delivery{
|
deliver(Qs, #delivery{message = BasicMessage,
|
||||||
message = BasicMessage,
|
confirm = Confirm,
|
||||||
confirm = Confirm,
|
msg_seq_no = SeqNo}) ->
|
||||||
msg_seq_no = SeqNo
|
Msg = {queue_event, ?MODULE,
|
||||||
}) ->
|
{?MODULE, _QPid = none, _QMsgId = none, _Redelivered = false, BasicMessage}},
|
||||||
Msg =
|
|
||||||
{queue_event, ?MODULE,
|
|
||||||
{?MODULE, _QPid = none, _QMsgId = none, _Redelivered = false, BasicMessage}},
|
|
||||||
{Pids, Actions} =
|
{Pids, Actions} =
|
||||||
case Confirm of
|
case Confirm of
|
||||||
false ->
|
false ->
|
||||||
Pids0 = lists:map(fun({Q, stateless}) -> amqqueue:get_pid(Q) end, Qs),
|
Pids0 = lists:map(fun({Q, stateless}) -> amqqueue:get_pid(Q) end, Qs),
|
||||||
{Pids0, []};
|
{Pids0, []};
|
||||||
true ->
|
true ->
|
||||||
%% We confirm the message directly here in the queue client.
|
%% We confirm the message directly here in the queue client.
|
||||||
%% Alternatively, we could have the target MQTT connection process confirm the message.
|
%% Alternatively, we could have the target MQTT connection process confirm the message.
|
||||||
%% However, given that this message might be lost anyway between target MQTT connection
|
%% However, given that this message might be lost anyway between target MQTT connection
|
||||||
%% process and MQTT subscriber, and we know that the MQTT subscriber wants to receive
|
%% process and MQTT subscriber, and we know that the MQTT subscriber wants to receive
|
||||||
%% this message at most once, we confirm here directly.
|
%% this message at most once, we confirm here directly.
|
||||||
%% Benefits:
|
%% Benefits:
|
||||||
%% 1. We do not block sending the confirmation back to the publishing client just because a single
|
%% 1. We do not block sending the confirmation back to the publishing client just because a single
|
||||||
%% (at-most-once) target queue out of potentially many (e.g. million) queues might be unavailable.
|
%% (at-most-once) target queue out of potentially many (e.g. million) queues might be unavailable.
|
||||||
%% 2. Memory usage in this (publishing) process is kept lower because the target queue name can be
|
%% 2. Memory usage in this (publishing) process is kept lower because the target queue name can be
|
||||||
%% directly removed from rabbit_mqtt_confirms and rabbit_confirms.
|
%% directly removed from rabbit_mqtt_confirms and rabbit_confirms.
|
||||||
%% 3. Reduced network traffic across RabbitMQ nodes.
|
%% 3. Reduced network traffic across RabbitMQ nodes.
|
||||||
%% 4. Lower latency of sending publisher confirmation back to the publishing client.
|
%% 4. Lower latency of sending publisher confirmation back to the publishing client.
|
||||||
SeqNos = [SeqNo],
|
SeqNos = [SeqNo],
|
||||||
lists:mapfoldl(
|
lists:mapfoldl(fun({Q, stateless}, Actions) ->
|
||||||
fun({Q, stateless}, Actions) ->
|
{amqqueue:get_pid(Q),
|
||||||
{amqqueue:get_pid(Q), [{settled, amqqueue:get_name(Q), SeqNos} | Actions]}
|
[{settled, amqqueue:get_name(Q), SeqNos} | Actions]}
|
||||||
end,
|
end, [], Qs)
|
||||||
[],
|
end,
|
||||||
Qs
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
delegate:invoke_no_result(Pids, {gen_server, cast, [Msg]}),
|
delegate:invoke_no_result(Pids, {gen_server, cast, [Msg]}),
|
||||||
{[], Actions}.
|
{[], Actions}.
|
||||||
|
|
||||||
|
@ -172,8 +152,8 @@ is_recoverable(Q) ->
|
||||||
Pid = amqqueue:get_pid(Q),
|
Pid = amqqueue:get_pid(Q),
|
||||||
OwnerPid = amqqueue:get_exclusive_owner(Q),
|
OwnerPid = amqqueue:get_exclusive_owner(Q),
|
||||||
node() =:= node(Pid) andalso
|
node() =:= node(Pid) andalso
|
||||||
Pid =:= OwnerPid andalso
|
Pid =:= OwnerPid andalso
|
||||||
not is_process_alive(Pid).
|
not is_process_alive(Pid).
|
||||||
|
|
||||||
%% We (mis)use the recover callback to clean up our exclusive queues
|
%% We (mis)use the recover callback to clean up our exclusive queues
|
||||||
%% which otherwise do not get cleaned up after a node crash.
|
%% which otherwise do not get cleaned up after a node crash.
|
||||||
|
@ -181,24 +161,20 @@ is_recoverable(Q) ->
|
||||||
{Recovered :: [amqqueue:amqqueue()], Failed :: [amqqueue:amqqueue()]}.
|
{Recovered :: [amqqueue:amqqueue()], Failed :: [amqqueue:amqqueue()]}.
|
||||||
recover(_VHost, Queues) ->
|
recover(_VHost, Queues) ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(Q) ->
|
fun(Q) ->
|
||||||
%% sanity check
|
%% sanity check
|
||||||
true = is_recoverable(Q),
|
true = is_recoverable(Q),
|
||||||
QName = amqqueue:get_name(Q),
|
QName = amqqueue:get_name(Q),
|
||||||
log_delete(QName, amqqueue:get_exclusive_owner(Q)),
|
log_delete(QName, amqqueue:get_exclusive_owner(Q)),
|
||||||
rabbit_amqqueue:internal_delete(QName, ?INTERNAL_USER)
|
rabbit_amqqueue:internal_delete(QName, ?INTERNAL_USER)
|
||||||
end,
|
end, Queues),
|
||||||
Queues
|
|
||||||
),
|
|
||||||
%% We mark the queue recovery as failed because these queues are not really
|
%% We mark the queue recovery as failed because these queues are not really
|
||||||
%% recovered, but deleted.
|
%% recovered, but deleted.
|
||||||
{[], Queues}.
|
{[], Queues}.
|
||||||
|
|
||||||
log_delete(QName, ConPid) ->
|
log_delete(QName, ConPid) ->
|
||||||
rabbit_log_queue:debug(
|
rabbit_log_queue:debug("Deleting ~s of type ~s because its declaring connection ~tp was closed",
|
||||||
"Deleting ~s of type ~s because its declaring connection ~tp was closed",
|
[rabbit_misc:rs(QName), ?MODULE, ConPid]).
|
||||||
[rabbit_misc:rs(QName), ?MODULE, ConPid]
|
|
||||||
).
|
|
||||||
|
|
||||||
-spec purge(amqqueue:amqqueue()) ->
|
-spec purge(amqqueue:amqqueue()) ->
|
||||||
{ok, non_neg_integer()}.
|
{ok, non_neg_integer()}.
|
||||||
|
@ -227,13 +203,11 @@ capabilities() ->
|
||||||
|
|
||||||
-spec info(amqqueue:amqqueue(), all_keys | rabbit_types:info_keys()) ->
|
-spec info(amqqueue:amqqueue(), all_keys | rabbit_types:info_keys()) ->
|
||||||
rabbit_types:infos().
|
rabbit_types:infos().
|
||||||
info(Q, all_keys) when
|
info(Q, all_keys)
|
||||||
?is_amqqueue(Q)
|
when ?is_amqqueue(Q) ->
|
||||||
->
|
|
||||||
info(Q, ?INFO_KEYS);
|
info(Q, ?INFO_KEYS);
|
||||||
info(Q, Items) when
|
info(Q, Items)
|
||||||
?is_amqqueue(Q)
|
when ?is_amqqueue(Q) ->
|
||||||
->
|
|
||||||
[{Item, i(Item, Q)} || Item <- Items].
|
[{Item, i(Item, Q)} || Item <- Items].
|
||||||
|
|
||||||
i(type, _) ->
|
i(type, _) ->
|
||||||
|
@ -275,26 +249,26 @@ init(A1) ->
|
||||||
close(A1) ->
|
close(A1) ->
|
||||||
?UNSUPPORTED([A1]).
|
?UNSUPPORTED([A1]).
|
||||||
|
|
||||||
update(A1, A2) ->
|
update(A1,A2) ->
|
||||||
?UNSUPPORTED([A1, A2]).
|
?UNSUPPORTED([A1,A2]).
|
||||||
|
|
||||||
consume(A1, A2, A3) ->
|
consume(A1,A2,A3) ->
|
||||||
?UNSUPPORTED([A1, A2, A3]).
|
?UNSUPPORTED([A1,A2,A3]).
|
||||||
|
|
||||||
cancel(A1, A2, A3, A4, A5) ->
|
cancel(A1,A2,A3,A4,A5) ->
|
||||||
?UNSUPPORTED([A1, A2, A3, A4, A5]).
|
?UNSUPPORTED([A1,A2,A3,A4,A5]).
|
||||||
|
|
||||||
handle_event(A1, A2, A3) ->
|
handle_event(A1,A2,A3) ->
|
||||||
?UNSUPPORTED([A1, A2, A3]).
|
?UNSUPPORTED([A1,A2,A3]).
|
||||||
|
|
||||||
settle(A1, A2, A3, A4, A5) ->
|
settle(A1,A2,A3,A4,A5) ->
|
||||||
?UNSUPPORTED([A1, A2, A3, A4, A5]).
|
?UNSUPPORTED([A1,A2,A3,A4,A5]).
|
||||||
|
|
||||||
credit(A1, A2, A3, A4, A5) ->
|
credit(A1,A2,A3,A4,A5) ->
|
||||||
?UNSUPPORTED([A1, A2, A3, A4, A5]).
|
?UNSUPPORTED([A1,A2,A3,A4,A5]).
|
||||||
|
|
||||||
dequeue(A1, A2, A3, A4, A5) ->
|
dequeue(A1,A2,A3,A4,A5) ->
|
||||||
?UNSUPPORTED([A1, A2, A3, A4, A5]).
|
?UNSUPPORTED([A1,A2,A3,A4,A5]).
|
||||||
|
|
||||||
state_info(A1) ->
|
state_info(A1) ->
|
||||||
?UNSUPPORTED([A1]).
|
?UNSUPPORTED([A1]).
|
||||||
|
|
|
@ -14,20 +14,11 @@
|
||||||
-include_lib("rabbit_common/include/logging.hrl").
|
-include_lib("rabbit_common/include/logging.hrl").
|
||||||
|
|
||||||
-export([start_link/3]).
|
-export([start_link/3]).
|
||||||
-export([
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
init/1,
|
code_change/3, terminate/2, format_status/1]).
|
||||||
handle_call/3,
|
|
||||||
handle_cast/2,
|
|
||||||
handle_info/2,
|
|
||||||
code_change/3,
|
|
||||||
terminate/2,
|
|
||||||
format_status/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-export([
|
-export([conserve_resources/3,
|
||||||
conserve_resources/3,
|
close_connection/2]).
|
||||||
close_connection/2
|
|
||||||
]).
|
|
||||||
|
|
||||||
-export([info/2]).
|
-export([info/2]).
|
||||||
|
|
||||||
|
@ -38,22 +29,22 @@
|
||||||
-define(HIBERNATE_AFTER, 1000).
|
-define(HIBERNATE_AFTER, 1000).
|
||||||
-define(PROTO_FAMILY, 'MQTT').
|
-define(PROTO_FAMILY, 'MQTT').
|
||||||
|
|
||||||
-record(state, {
|
-record(state,
|
||||||
socket :: rabbit_net:socket(),
|
{socket :: rabbit_net:socket(),
|
||||||
proxy_socket :: option({rabbit_proxy_socket, any(), any()}),
|
proxy_socket :: option({rabbit_proxy_socket, any(), any()}),
|
||||||
await_recv :: boolean(),
|
await_recv :: boolean(),
|
||||||
deferred_recv :: option(binary()),
|
deferred_recv :: option(binary()),
|
||||||
parse_state :: rabbit_mqtt_packet:state(),
|
parse_state :: rabbit_mqtt_packet:state(),
|
||||||
proc_state :: rabbit_mqtt_processor:state(),
|
proc_state :: rabbit_mqtt_processor:state(),
|
||||||
connection_state :: running | blocked,
|
connection_state :: running | blocked,
|
||||||
conserve :: boolean(),
|
conserve :: boolean(),
|
||||||
stats_timer :: option(rabbit_event:state()),
|
stats_timer :: option(rabbit_event:state()),
|
||||||
keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
|
keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
|
||||||
conn_name :: binary(),
|
conn_name :: binary(),
|
||||||
received_connect_packet :: boolean()
|
received_connect_packet :: boolean()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type state() :: #state{}.
|
-type(state() :: #state{}).
|
||||||
|
|
||||||
%%----------------------------------------------------------------------------
|
%%----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -61,11 +52,9 @@ start_link(Ref, _Transport, []) ->
|
||||||
Pid = proc_lib:spawn_link(?MODULE, init, [Ref]),
|
Pid = proc_lib:spawn_link(?MODULE, init, [Ref]),
|
||||||
{ok, Pid}.
|
{ok, Pid}.
|
||||||
|
|
||||||
-spec conserve_resources(
|
-spec conserve_resources(pid(),
|
||||||
pid(),
|
rabbit_alarm:resource_alarm_source(),
|
||||||
rabbit_alarm:resource_alarm_source(),
|
rabbit_alarm:resource_alert()) -> ok.
|
||||||
rabbit_alarm:resource_alert()
|
|
||||||
) -> ok.
|
|
||||||
conserve_resources(Pid, _, {_, Conserve, _}) ->
|
conserve_resources(Pid, _, {_, Conserve, _}) ->
|
||||||
Pid ! {conserve_resources, Conserve},
|
Pid ! {conserve_resources, Conserve},
|
||||||
ok.
|
ok.
|
||||||
|
@ -84,10 +73,8 @@ close_connection(Pid, Reason) ->
|
||||||
init(Ref) ->
|
init(Ref) ->
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [mqtt]}),
|
logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [mqtt]}),
|
||||||
{ok, Sock} = rabbit_networking:handshake(
|
{ok, Sock} = rabbit_networking:handshake(Ref,
|
||||||
Ref,
|
application:get_env(?APP_NAME, proxy_protocol, false)),
|
||||||
application:get_env(?APP_NAME, proxy_protocol, false)
|
|
||||||
),
|
|
||||||
RealSocket = rabbit_net:unwrap_socket(Sock),
|
RealSocket = rabbit_net:unwrap_socket(Sock),
|
||||||
case rabbit_net:connection_string(Sock, inbound) of
|
case rabbit_net:connection_string(Sock, inbound) of
|
||||||
{ok, ConnStr} ->
|
{ok, ConnStr} ->
|
||||||
|
@ -97,17 +84,15 @@ init(Ref) ->
|
||||||
LoginTimeout = application:get_env(?APP_NAME, login_timeout, 10_000),
|
LoginTimeout = application:get_env(?APP_NAME, login_timeout, 10_000),
|
||||||
erlang:send_after(LoginTimeout, self(), login_timeout),
|
erlang:send_after(LoginTimeout, self(), login_timeout),
|
||||||
ProcessorState = rabbit_mqtt_processor:initial_state(RealSocket, ConnName),
|
ProcessorState = rabbit_mqtt_processor:initial_state(RealSocket, ConnName),
|
||||||
State0 = #state{
|
State0 = #state{socket = RealSocket,
|
||||||
socket = RealSocket,
|
proxy_socket = rabbit_net:maybe_get_proxy_socket(Sock),
|
||||||
proxy_socket = rabbit_net:maybe_get_proxy_socket(Sock),
|
conn_name = ConnName,
|
||||||
conn_name = ConnName,
|
await_recv = false,
|
||||||
await_recv = false,
|
connection_state = running,
|
||||||
connection_state = running,
|
received_connect_packet = false,
|
||||||
received_connect_packet = false,
|
conserve = false,
|
||||||
conserve = false,
|
parse_state = rabbit_mqtt_packet:initial_state(),
|
||||||
parse_state = rabbit_mqtt_packet:initial_state(),
|
proc_state = ProcessorState},
|
||||||
proc_state = ProcessorState
|
|
||||||
},
|
|
||||||
State1 = control_throttle(State0),
|
State1 = control_throttle(State0),
|
||||||
State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
|
State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
|
||||||
gen_server:enter_loop(?MODULE, [], State);
|
gen_server:enter_loop(?MODULE, [], State);
|
||||||
|
@ -123,67 +108,51 @@ init(Ref) ->
|
||||||
|
|
||||||
handle_call({info, InfoItems}, _From, State) ->
|
handle_call({info, InfoItems}, _From, State) ->
|
||||||
{reply, infos(InfoItems, State), State, ?HIBERNATE_AFTER};
|
{reply, infos(InfoItems, State), State, ?HIBERNATE_AFTER};
|
||||||
|
|
||||||
handle_call(Msg, From, State) ->
|
handle_call(Msg, From, State) ->
|
||||||
{stop, {mqtt_unexpected_call, Msg, From}, State}.
|
{stop, {mqtt_unexpected_call, Msg, From}, State}.
|
||||||
|
|
||||||
handle_cast(
|
handle_cast(duplicate_id,
|
||||||
duplicate_id,
|
State = #state{ proc_state = PState,
|
||||||
State = #state{
|
conn_name = ConnName }) ->
|
||||||
proc_state = PState,
|
?LOG_WARNING("MQTT disconnecting client ~tp with duplicate id '~ts'",
|
||||||
conn_name = ConnName
|
[ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
|
||||||
}
|
|
||||||
) ->
|
|
||||||
?LOG_WARNING(
|
|
||||||
"MQTT disconnecting client ~tp with duplicate id '~ts'",
|
|
||||||
[ConnName, rabbit_mqtt_processor:info(client_id, PState)]
|
|
||||||
),
|
|
||||||
{stop, {shutdown, duplicate_id}, State};
|
{stop, {shutdown, duplicate_id}, State};
|
||||||
handle_cast(
|
|
||||||
decommission_node,
|
handle_cast(decommission_node,
|
||||||
State = #state{
|
State = #state{ proc_state = PState,
|
||||||
proc_state = PState,
|
conn_name = ConnName }) ->
|
||||||
conn_name = ConnName
|
?LOG_WARNING("MQTT disconnecting client ~tp with client ID '~ts' as its node is about"
|
||||||
}
|
" to be decommissioned",
|
||||||
) ->
|
[ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
|
||||||
?LOG_WARNING(
|
|
||||||
"MQTT disconnecting client ~tp with client ID '~ts' as its node is about"
|
|
||||||
" to be decommissioned",
|
|
||||||
[ConnName, rabbit_mqtt_processor:info(client_id, PState)]
|
|
||||||
),
|
|
||||||
{stop, {shutdown, decommission_node}, State};
|
{stop, {shutdown, decommission_node}, State};
|
||||||
handle_cast(
|
|
||||||
{close_connection, Reason},
|
handle_cast({close_connection, Reason},
|
||||||
State = #state{conn_name = ConnName, proc_state = PState}
|
State = #state{conn_name = ConnName, proc_state = PState}) ->
|
||||||
) ->
|
?LOG_WARNING("MQTT disconnecting client ~tp with client ID '~ts', reason: ~ts",
|
||||||
?LOG_WARNING(
|
[ConnName, rabbit_mqtt_processor:info(client_id, PState), Reason]),
|
||||||
"MQTT disconnecting client ~tp with client ID '~ts', reason: ~ts",
|
|
||||||
[ConnName, rabbit_mqtt_processor:info(client_id, PState), Reason]
|
|
||||||
),
|
|
||||||
{stop, {shutdown, server_initiated_close}, State};
|
{stop, {shutdown, server_initiated_close}, State};
|
||||||
handle_cast(
|
|
||||||
QueueEvent = {queue_event, _, _},
|
handle_cast(QueueEvent = {queue_event, _, _},
|
||||||
State = #state{proc_state = PState0}
|
State = #state{proc_state = PState0}) ->
|
||||||
) ->
|
|
||||||
case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
|
case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
|
||||||
{ok, PState} ->
|
{ok, PState} ->
|
||||||
maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
|
maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
|
||||||
{error, Reason, PState} ->
|
{error, Reason, PState} ->
|
||||||
{stop, Reason, pstate(State, PState)}
|
{stop, Reason, pstate(State, PState)}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_cast({force_event_refresh, Ref}, State0) ->
|
handle_cast({force_event_refresh, Ref}, State0) ->
|
||||||
Infos = infos(?CREATION_EVENT_KEYS, State0),
|
Infos = infos(?CREATION_EVENT_KEYS, State0),
|
||||||
rabbit_event:notify(connection_created, Infos, Ref),
|
rabbit_event:notify(connection_created, Infos, Ref),
|
||||||
State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
|
State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
|
||||||
{noreply, State, ?HIBERNATE_AFTER};
|
{noreply, State, ?HIBERNATE_AFTER};
|
||||||
handle_cast(
|
|
||||||
refresh_config,
|
handle_cast(refresh_config, State = #state{proc_state = PState0,
|
||||||
State = #state{
|
conn_name = ConnName}) ->
|
||||||
proc_state = PState0,
|
|
||||||
conn_name = ConnName
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
|
PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
|
||||||
{noreply, pstate(State, PState), ?HIBERNATE_AFTER};
|
{noreply, pstate(State, PState), ?HIBERNATE_AFTER};
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
handle_cast(Msg, State) ->
|
||||||
{stop, {mqtt_unexpected_cast, Msg}, State}.
|
{stop, {mqtt_unexpected_cast, Msg}, State}.
|
||||||
|
|
||||||
|
@ -192,53 +161,49 @@ handle_info(connection_created, State) ->
|
||||||
rabbit_core_metrics:connection_created(self(), Infos),
|
rabbit_core_metrics:connection_created(self(), Infos),
|
||||||
rabbit_event:notify(connection_created, Infos),
|
rabbit_event:notify(connection_created, Infos),
|
||||||
{noreply, State, ?HIBERNATE_AFTER};
|
{noreply, State, ?HIBERNATE_AFTER};
|
||||||
|
|
||||||
handle_info(timeout, State) ->
|
handle_info(timeout, State) ->
|
||||||
rabbit_mqtt_processor:handle_pre_hibernate(),
|
rabbit_mqtt_processor:handle_pre_hibernate(),
|
||||||
{noreply, State, hibernate};
|
{noreply, State, hibernate};
|
||||||
|
|
||||||
handle_info({'EXIT', _Conn, Reason}, State) ->
|
handle_info({'EXIT', _Conn, Reason}, State) ->
|
||||||
{stop, {connection_died, Reason}, State};
|
{stop, {connection_died, Reason}, State};
|
||||||
handle_info(
|
|
||||||
{Tag, Sock, Data},
|
handle_info({Tag, Sock, Data},
|
||||||
State = #state{socket = Sock, connection_state = blocked}
|
State = #state{ socket = Sock, connection_state = blocked })
|
||||||
) when
|
when Tag =:= tcp; Tag =:= ssl ->
|
||||||
Tag =:= tcp; Tag =:= ssl
|
{noreply, State#state{ deferred_recv = Data }, ?HIBERNATE_AFTER};
|
||||||
->
|
|
||||||
{noreply, State#state{deferred_recv = Data}, ?HIBERNATE_AFTER};
|
handle_info({Tag, Sock, Data},
|
||||||
handle_info(
|
State = #state{ socket = Sock, connection_state = running })
|
||||||
{Tag, Sock, Data},
|
when Tag =:= tcp; Tag =:= ssl ->
|
||||||
State = #state{socket = Sock, connection_state = running}
|
|
||||||
) when
|
|
||||||
Tag =:= tcp; Tag =:= ssl
|
|
||||||
->
|
|
||||||
process_received_bytes(
|
process_received_bytes(
|
||||||
Data, control_throttle(State#state{await_recv = false})
|
Data, control_throttle(State #state{ await_recv = false }));
|
||||||
);
|
|
||||||
handle_info({Tag, Sock}, State = #state{socket = Sock}) when
|
handle_info({Tag, Sock}, State = #state{socket = Sock})
|
||||||
Tag =:= tcp_closed; Tag =:= ssl_closed
|
when Tag =:= tcp_closed; Tag =:= ssl_closed ->
|
||||||
->
|
|
||||||
network_error(closed, State);
|
network_error(closed, State);
|
||||||
handle_info({Tag, Sock, Reason}, State = #state{socket = Sock}) when
|
|
||||||
Tag =:= tcp_error; Tag =:= ssl_error
|
handle_info({Tag, Sock, Reason}, State = #state{socket = Sock})
|
||||||
->
|
when Tag =:= tcp_error; Tag =:= ssl_error ->
|
||||||
network_error(Reason, State);
|
network_error(Reason, State);
|
||||||
|
|
||||||
handle_info({inet_reply, Sock, ok}, State = #state{socket = Sock}) ->
|
handle_info({inet_reply, Sock, ok}, State = #state{socket = Sock}) ->
|
||||||
{noreply, State, ?HIBERNATE_AFTER};
|
{noreply, State, ?HIBERNATE_AFTER};
|
||||||
|
|
||||||
handle_info({inet_reply, Sock, {error, Reason}}, State = #state{socket = Sock}) ->
|
handle_info({inet_reply, Sock, {error, Reason}}, State = #state{socket = Sock}) ->
|
||||||
network_error(Reason, State);
|
network_error(Reason, State);
|
||||||
|
|
||||||
handle_info({conserve_resources, Conserve}, State) ->
|
handle_info({conserve_resources, Conserve}, State) ->
|
||||||
maybe_process_deferred_recv(
|
maybe_process_deferred_recv(
|
||||||
control_throttle(State#state{conserve = Conserve})
|
control_throttle(State #state{ conserve = Conserve }));
|
||||||
);
|
|
||||||
handle_info({bump_credit, Msg}, State) ->
|
handle_info({bump_credit, Msg}, State) ->
|
||||||
credit_flow:handle_bump_msg(Msg),
|
credit_flow:handle_bump_msg(Msg),
|
||||||
maybe_process_deferred_recv(control_throttle(State));
|
maybe_process_deferred_recv(control_throttle(State));
|
||||||
handle_info(
|
|
||||||
{keepalive, Req},
|
handle_info({keepalive, Req}, State = #state{keepalive = KState0,
|
||||||
State = #state{
|
conn_name = ConnName}) ->
|
||||||
keepalive = KState0,
|
|
||||||
conn_name = ConnName
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
case rabbit_mqtt_keepalive:handle(Req, KState0) of
|
case rabbit_mqtt_keepalive:handle(Req, KState0) of
|
||||||
{ok, KState} ->
|
{ok, KState} ->
|
||||||
{noreply, State#state{keepalive = KState}, ?HIBERNATE_AFTER};
|
{noreply, State#state{keepalive = KState}, ?HIBERNATE_AFTER};
|
||||||
|
@ -248,6 +213,7 @@ handle_info(
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{stop, Reason, State}
|
{stop, Reason, State}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_info(login_timeout, State = #state{received_connect_packet = true}) ->
|
handle_info(login_timeout, State = #state{received_connect_packet = true}) ->
|
||||||
{noreply, State, ?HIBERNATE_AFTER};
|
{noreply, State, ?HIBERNATE_AFTER};
|
||||||
handle_info(login_timeout, State = #state{conn_name = ConnName}) ->
|
handle_info(login_timeout, State = #state{conn_name = ConnName}) ->
|
||||||
|
@ -258,47 +224,43 @@ handle_info(login_timeout, State = #state{conn_name = ConnName}) ->
|
||||||
%% and we don't want to skip closing the connection in that case.
|
%% and we don't want to skip closing the connection in that case.
|
||||||
?LOG_ERROR("closing MQTT connection ~tp (login timeout)", [ConnName]),
|
?LOG_ERROR("closing MQTT connection ~tp (login timeout)", [ConnName]),
|
||||||
{stop, {shutdown, login_timeout}, State};
|
{stop, {shutdown, login_timeout}, State};
|
||||||
|
|
||||||
handle_info(emit_stats, State) ->
|
handle_info(emit_stats, State) ->
|
||||||
{noreply, emit_stats(State), ?HIBERNATE_AFTER};
|
{noreply, emit_stats(State), ?HIBERNATE_AFTER};
|
||||||
handle_info(
|
|
||||||
{ra_event, _From, Evt},
|
handle_info({ra_event, _From, Evt},
|
||||||
#state{proc_state = PState0} = State
|
#state{proc_state = PState0} = State) ->
|
||||||
) ->
|
|
||||||
%% handle applied event to ensure registration command actually got applied
|
%% handle applied event to ensure registration command actually got applied
|
||||||
%% handle not_leader notification in case we send the command to a non-leader
|
%% handle not_leader notification in case we send the command to a non-leader
|
||||||
PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
|
PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
|
||||||
{noreply, pstate(State, PState), ?HIBERNATE_AFTER};
|
{noreply, pstate(State, PState), ?HIBERNATE_AFTER};
|
||||||
handle_info(
|
|
||||||
{{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
|
handle_info({{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
|
||||||
#state{proc_state = PState0} = State
|
#state{proc_state = PState0} = State) ->
|
||||||
) ->
|
|
||||||
case rabbit_mqtt_processor:handle_down(Evt, PState0) of
|
case rabbit_mqtt_processor:handle_down(Evt, PState0) of
|
||||||
{ok, PState} ->
|
{ok, PState} ->
|
||||||
maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
|
maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{stop, {shutdown, Reason, State}}
|
{stop, {shutdown, Reason, State}}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_info({'DOWN', _MRef, process, QPid, _Reason}, State) ->
|
handle_info({'DOWN', _MRef, process, QPid, _Reason}, State) ->
|
||||||
rabbit_amqqueue_common:notify_sent_queue_down(QPid),
|
rabbit_amqqueue_common:notify_sent_queue_down(QPid),
|
||||||
{noreply, State, ?HIBERNATE_AFTER};
|
{noreply, State, ?HIBERNATE_AFTER};
|
||||||
|
|
||||||
handle_info({shutdown, Explanation} = Reason, State = #state{conn_name = ConnName}) ->
|
handle_info({shutdown, Explanation} = Reason, State = #state{conn_name = ConnName}) ->
|
||||||
%% rabbitmq_management plugin requests to close connection.
|
%% rabbitmq_management plugin requests to close connection.
|
||||||
?LOG_INFO("MQTT closing connection ~tp: ~p", [ConnName, Explanation]),
|
?LOG_INFO("MQTT closing connection ~tp: ~p", [ConnName, Explanation]),
|
||||||
{stop, Reason, State};
|
{stop, Reason, State};
|
||||||
|
|
||||||
handle_info(Msg, State) ->
|
handle_info(Msg, State) ->
|
||||||
{stop, {mqtt_unexpected_msg, Msg}, State}.
|
{stop, {mqtt_unexpected_msg, Msg}, State}.
|
||||||
|
|
||||||
terminate(Reason, State = #state{}) ->
|
terminate(Reason, State = #state{}) ->
|
||||||
terminate(Reason, {true, State});
|
terminate(Reason, {true, State});
|
||||||
terminate(
|
terminate(Reason, {SendWill, State = #state{conn_name = ConnName,
|
||||||
Reason,
|
keepalive = KState0,
|
||||||
{SendWill,
|
proc_state = PState}}) ->
|
||||||
State = #state{
|
|
||||||
conn_name = ConnName,
|
|
||||||
keepalive = KState0,
|
|
||||||
proc_state = PState
|
|
||||||
}}
|
|
||||||
) ->
|
|
||||||
KState = rabbit_mqtt_keepalive:cancel_timer(KState0),
|
KState = rabbit_mqtt_keepalive:cancel_timer(KState0),
|
||||||
maybe_emit_stats(State#state{keepalive = KState}),
|
maybe_emit_stats(State#state{keepalive = KState}),
|
||||||
_ = rabbit_mqtt_processor:terminate(SendWill, ConnName, ?PROTO_FAMILY, PState),
|
_ = rabbit_mqtt_processor:terminate(SendWill, ConnName, ?PROTO_FAMILY, PState),
|
||||||
|
@ -306,25 +268,36 @@ terminate(
|
||||||
|
|
||||||
log_terminate({network_error, {ssl_upgrade_error, closed}, ConnName}, _State) ->
|
log_terminate({network_error, {ssl_upgrade_error, closed}, ConnName}, _State) ->
|
||||||
?LOG_ERROR("MQTT detected TLS upgrade error on ~s: connection closed", [ConnName]);
|
?LOG_ERROR("MQTT detected TLS upgrade error on ~s: connection closed", [ConnName]);
|
||||||
log_terminate(
|
|
||||||
{network_error, {ssl_upgrade_error, {tls_alert, "handshake failure"}}, ConnName}, _State
|
log_terminate({network_error,
|
||||||
) ->
|
{ssl_upgrade_error,
|
||||||
|
{tls_alert, "handshake failure"}}, ConnName}, _State) ->
|
||||||
log_tls_alert(handshake_failure, ConnName);
|
log_tls_alert(handshake_failure, ConnName);
|
||||||
log_terminate({network_error, {ssl_upgrade_error, {tls_alert, "unknown ca"}}, ConnName}, _State) ->
|
log_terminate({network_error,
|
||||||
|
{ssl_upgrade_error,
|
||||||
|
{tls_alert, "unknown ca"}}, ConnName}, _State) ->
|
||||||
log_tls_alert(unknown_ca, ConnName);
|
log_tls_alert(unknown_ca, ConnName);
|
||||||
log_terminate({network_error, {ssl_upgrade_error, {tls_alert, {Err, _}}}, ConnName}, _State) ->
|
log_terminate({network_error,
|
||||||
|
{ssl_upgrade_error,
|
||||||
|
{tls_alert, {Err, _}}}, ConnName}, _State) ->
|
||||||
log_tls_alert(Err, ConnName);
|
log_tls_alert(Err, ConnName);
|
||||||
log_terminate({network_error, {ssl_upgrade_error, {tls_alert, Alert}}, ConnName}, _State) ->
|
log_terminate({network_error,
|
||||||
|
{ssl_upgrade_error,
|
||||||
|
{tls_alert, Alert}}, ConnName}, _State) ->
|
||||||
log_tls_alert(Alert, ConnName);
|
log_tls_alert(Alert, ConnName);
|
||||||
log_terminate({network_error, {ssl_upgrade_error, Reason}, ConnName}, _State) ->
|
log_terminate({network_error, {ssl_upgrade_error, Reason}, ConnName}, _State) ->
|
||||||
?LOG_ERROR("MQTT detected TLS upgrade error on ~s: ~p", [ConnName, Reason]);
|
?LOG_ERROR("MQTT detected TLS upgrade error on ~s: ~p", [ConnName, Reason]);
|
||||||
|
|
||||||
log_terminate({network_error, Reason, ConnName}, _State) ->
|
log_terminate({network_error, Reason, ConnName}, _State) ->
|
||||||
?LOG_ERROR("MQTT detected network error on ~s: ~p", [ConnName, Reason]);
|
?LOG_ERROR("MQTT detected network error on ~s: ~p", [ConnName, Reason]);
|
||||||
|
|
||||||
log_terminate({network_error, Reason}, _State) ->
|
log_terminate({network_error, Reason}, _State) ->
|
||||||
?LOG_ERROR("MQTT detected network error: ~p", [Reason]);
|
?LOG_ERROR("MQTT detected network error: ~p", [Reason]);
|
||||||
log_terminate(normal, #state{conn_name = ConnName}) ->
|
|
||||||
|
log_terminate(normal, #state{conn_name = ConnName}) ->
|
||||||
?LOG_INFO("closing MQTT connection ~p (~s)", [self(), ConnName]),
|
?LOG_INFO("closing MQTT connection ~p (~s)", [self(), ConnName]),
|
||||||
ok;
|
ok;
|
||||||
|
|
||||||
log_terminate(_Reason, _State) ->
|
log_terminate(_Reason, _State) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
@ -336,80 +309,54 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
log_tls_alert(handshake_failure, ConnName) ->
|
log_tls_alert(handshake_failure, ConnName) ->
|
||||||
?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: handshake failure", [ConnName]);
|
?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: handshake failure", [ConnName]);
|
||||||
log_tls_alert(unknown_ca, ConnName) ->
|
log_tls_alert(unknown_ca, ConnName) ->
|
||||||
?LOG_ERROR(
|
?LOG_ERROR("MQTT detected TLS certificate verification error on ~ts: alert 'unknown CA'",
|
||||||
"MQTT detected TLS certificate verification error on ~ts: alert 'unknown CA'",
|
[ConnName]);
|
||||||
[ConnName]
|
|
||||||
);
|
|
||||||
log_tls_alert(Alert, ConnName) ->
|
log_tls_alert(Alert, ConnName) ->
|
||||||
?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: alert ~ts", [ConnName, Alert]).
|
?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: alert ~ts", [ConnName, Alert]).
|
||||||
|
|
||||||
process_received_bytes(
|
process_received_bytes(<<>>, State = #state{received_connect_packet = false,
|
||||||
<<>>,
|
proc_state = PState,
|
||||||
State = #state{
|
conn_name = ConnName}) ->
|
||||||
received_connect_packet = false,
|
?LOG_INFO("Accepted MQTT connection ~p (~s, client ID: ~s)",
|
||||||
proc_state = PState,
|
[self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
|
||||||
conn_name = ConnName
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
?LOG_INFO(
|
|
||||||
"Accepted MQTT connection ~p (~s, client ID: ~s)",
|
|
||||||
[self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]
|
|
||||||
),
|
|
||||||
{noreply, ensure_stats_timer(State#state{received_connect_packet = true}), ?HIBERNATE_AFTER};
|
{noreply, ensure_stats_timer(State#state{received_connect_packet = true}), ?HIBERNATE_AFTER};
|
||||||
process_received_bytes(<<>>, State) ->
|
process_received_bytes(<<>>, State) ->
|
||||||
{noreply, ensure_stats_timer(State), ?HIBERNATE_AFTER};
|
{noreply, ensure_stats_timer(State), ?HIBERNATE_AFTER};
|
||||||
process_received_bytes(
|
process_received_bytes(Bytes,
|
||||||
Bytes,
|
State = #state{ parse_state = ParseState,
|
||||||
State = #state{
|
proc_state = ProcState,
|
||||||
parse_state = ParseState,
|
conn_name = ConnName }) ->
|
||||||
proc_state = ProcState,
|
|
||||||
conn_name = ConnName
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
case parse(Bytes, ParseState) of
|
case parse(Bytes, ParseState) of
|
||||||
{more, ParseState1} ->
|
{more, ParseState1} ->
|
||||||
{noreply, ensure_stats_timer(State#state{parse_state = ParseState1}), ?HIBERNATE_AFTER};
|
{noreply,
|
||||||
|
ensure_stats_timer( State #state{ parse_state = ParseState1 }),
|
||||||
|
?HIBERNATE_AFTER};
|
||||||
{ok, Packet, Rest} ->
|
{ok, Packet, Rest} ->
|
||||||
case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
|
case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
|
||||||
{ok, ProcState1} ->
|
{ok, ProcState1} ->
|
||||||
process_received_bytes(
|
process_received_bytes(
|
||||||
Rest,
|
Rest,
|
||||||
State#state{
|
State #state{parse_state = rabbit_mqtt_packet:initial_state(),
|
||||||
parse_state = rabbit_mqtt_packet:initial_state(),
|
proc_state = ProcState1});
|
||||||
proc_state = ProcState1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
%% PUBLISH and more
|
%% PUBLISH and more
|
||||||
{error, unauthorized = Reason, ProcState1} ->
|
{error, unauthorized = Reason, ProcState1} ->
|
||||||
?LOG_ERROR("MQTT connection ~ts is closing due to an authorization failure", [
|
?LOG_ERROR("MQTT connection ~ts is closing due to an authorization failure", [ConnName]),
|
||||||
ConnName
|
|
||||||
]),
|
|
||||||
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
||||||
%% CONNECT packets only
|
%% CONNECT packets only
|
||||||
{error, unauthenticated = Reason, ProcState1} ->
|
{error, unauthenticated = Reason, ProcState1} ->
|
||||||
?LOG_ERROR("MQTT connection ~ts is closing due to an authentication failure", [
|
?LOG_ERROR("MQTT connection ~ts is closing due to an authentication failure", [ConnName]),
|
||||||
ConnName
|
|
||||||
]),
|
|
||||||
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
||||||
%% CONNECT packets only
|
%% CONNECT packets only
|
||||||
{error, invalid_client_id = Reason, ProcState1} ->
|
{error, invalid_client_id = Reason, ProcState1} ->
|
||||||
?LOG_ERROR("MQTT cannot accept connection ~ts: client uses an invalid ID", [
|
?LOG_ERROR("MQTT cannot accept connection ~ts: client uses an invalid ID", [ConnName]),
|
||||||
ConnName
|
|
||||||
]),
|
|
||||||
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
||||||
%% CONNECT packets only
|
%% CONNECT packets only
|
||||||
{error, unsupported_protocol_version = Reason, ProcState1} ->
|
{error, unsupported_protocol_version = Reason, ProcState1} ->
|
||||||
?LOG_ERROR(
|
?LOG_ERROR("MQTT cannot accept connection ~ts: incompatible protocol version", [ConnName]),
|
||||||
"MQTT cannot accept connection ~ts: incompatible protocol version", [
|
|
||||||
ConnName
|
|
||||||
]
|
|
||||||
),
|
|
||||||
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
||||||
{error, unavailable = Reason, ProcState1} ->
|
{error, unavailable = Reason, ProcState1} ->
|
||||||
?LOG_ERROR(
|
?LOG_ERROR("MQTT cannot accept connection ~ts due to an internal error or unavailable component",
|
||||||
"MQTT cannot accept connection ~ts due to an internal error or unavailable component",
|
[ConnName]),
|
||||||
[ConnName]
|
|
||||||
),
|
|
||||||
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
{stop, {shutdown, Reason}, pstate(State, ProcState1)};
|
||||||
{error, Reason, ProcState1} ->
|
{error, Reason, ProcState1} ->
|
||||||
?LOG_ERROR("MQTT protocol error on connection ~ts: ~tp", [ConnName, Reason]),
|
?LOG_ERROR("MQTT protocol error on connection ~ts: ~tp", [ConnName, Reason]),
|
||||||
|
@ -418,11 +365,9 @@ process_received_bytes(
|
||||||
{stop, normal, {_SendWill = false, pstate(State, ProcState1)}}
|
{stop, normal, {_SendWill = false, pstate(State, ProcState1)}}
|
||||||
end;
|
end;
|
||||||
{error, {cannot_parse, Reason, Stacktrace}} ->
|
{error, {cannot_parse, Reason, Stacktrace}} ->
|
||||||
?LOG_ERROR(
|
?LOG_ERROR("MQTT cannot parse a packet on connection '~ts', reason: ~tp, "
|
||||||
"MQTT cannot parse a packet on connection '~ts', reason: ~tp, "
|
"stacktrace: ~tp, payload (first 100 bytes): ~tp",
|
||||||
"stacktrace: ~tp, payload (first 100 bytes): ~tp",
|
[ConnName, Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Bytes, 100)]),
|
||||||
[ConnName, Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Bytes, 100)]
|
|
||||||
),
|
|
||||||
{stop, {shutdown, Reason}, State};
|
{stop, {shutdown, Reason}, State};
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
?LOG_ERROR("MQTT detected a framing error on connection ~ts: ~tp", [ConnName, Error]),
|
?LOG_ERROR("MQTT detected a framing error on connection ~ts: ~tp", [ConnName, Error]),
|
||||||
|
@ -430,8 +375,8 @@ process_received_bytes(
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec pstate(state(), rabbit_mqtt_processor:state()) -> state().
|
-spec pstate(state(), rabbit_mqtt_processor:state()) -> state().
|
||||||
pstate(State = #state{}, PState) ->
|
pstate(State = #state {}, PState) ->
|
||||||
State#state{proc_state = PState}.
|
State #state{ proc_state = PState }.
|
||||||
|
|
||||||
%%----------------------------------------------------------------------------
|
%%----------------------------------------------------------------------------
|
||||||
parse(Bytes, ParseState) ->
|
parse(Bytes, ParseState) ->
|
||||||
|
@ -442,13 +387,9 @@ parse(Bytes, ParseState) ->
|
||||||
{error, {cannot_parse, Reason, Stacktrace}}
|
{error, {cannot_parse, Reason, Stacktrace}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
network_error(
|
network_error(closed,
|
||||||
closed,
|
State = #state{conn_name = ConnName,
|
||||||
State = #state{
|
received_connect_packet = Connected}) ->
|
||||||
conn_name = ConnName,
|
|
||||||
received_connect_packet = Connected
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
Fmt = "MQTT connection ~p will terminate because peer closed TCP connection",
|
Fmt = "MQTT connection ~p will terminate because peer closed TCP connection",
|
||||||
Args = [ConnName],
|
Args = [ConnName],
|
||||||
case Connected of
|
case Connected of
|
||||||
|
@ -456,77 +397,62 @@ network_error(
|
||||||
false -> ?LOG_DEBUG(Fmt, Args)
|
false -> ?LOG_DEBUG(Fmt, Args)
|
||||||
end,
|
end,
|
||||||
{stop, {shutdown, conn_closed}, State};
|
{stop, {shutdown, conn_closed}, State};
|
||||||
network_error(
|
|
||||||
Reason,
|
network_error(Reason,
|
||||||
State = #state{conn_name = ConnName}
|
State = #state{conn_name = ConnName}) ->
|
||||||
) ->
|
|
||||||
?LOG_INFO("MQTT detected network error for ~p: ~p", [ConnName, Reason]),
|
?LOG_INFO("MQTT detected network error for ~p: ~p", [ConnName, Reason]),
|
||||||
{stop, {shutdown, conn_closed}, State}.
|
{stop, {shutdown, conn_closed}, State}.
|
||||||
|
|
||||||
run_socket(State = #state{connection_state = blocked}) ->
|
run_socket(State = #state{ connection_state = blocked }) ->
|
||||||
State;
|
State;
|
||||||
run_socket(State = #state{deferred_recv = Data}) when Data =/= undefined ->
|
run_socket(State = #state{ deferred_recv = Data }) when Data =/= undefined ->
|
||||||
State;
|
State;
|
||||||
run_socket(State = #state{await_recv = true}) ->
|
run_socket(State = #state{ await_recv = true }) ->
|
||||||
State;
|
State;
|
||||||
run_socket(State = #state{socket = Sock}) ->
|
run_socket(State = #state{ socket = Sock }) ->
|
||||||
ok = rabbit_net:setopts(Sock, [{active, once}]),
|
ok = rabbit_net:setopts(Sock, [{active, once}]),
|
||||||
State#state{await_recv = true}.
|
State#state{ await_recv = true }.
|
||||||
|
|
||||||
control_throttle(
|
control_throttle(State = #state{connection_state = ConnState,
|
||||||
State = #state{
|
conserve = Conserve,
|
||||||
connection_state = ConnState,
|
received_connect_packet = Connected,
|
||||||
conserve = Conserve,
|
proc_state = PState,
|
||||||
received_connect_packet = Connected,
|
keepalive = KState
|
||||||
proc_state = PState,
|
}) ->
|
||||||
keepalive = KState
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
|
Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
|
||||||
case {ConnState, Throttle} of
|
case {ConnState, Throttle} of
|
||||||
{running, true} ->
|
{running, true} ->
|
||||||
State#state{
|
State#state{connection_state = blocked,
|
||||||
connection_state = blocked,
|
keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)};
|
||||||
keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)
|
|
||||||
};
|
|
||||||
{blocked, false} ->
|
{blocked, false} ->
|
||||||
run_socket(State#state{
|
run_socket(State#state{connection_state = running,
|
||||||
connection_state = running,
|
keepalive = rabbit_mqtt_keepalive:start_timer(KState)});
|
||||||
keepalive = rabbit_mqtt_keepalive:start_timer(KState)
|
|
||||||
});
|
|
||||||
{_, _} ->
|
{_, _} ->
|
||||||
run_socket(State)
|
run_socket(State)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
maybe_process_deferred_recv(State = #state{deferred_recv = undefined}) ->
|
maybe_process_deferred_recv(State = #state{ deferred_recv = undefined }) ->
|
||||||
{noreply, State, ?HIBERNATE_AFTER};
|
{noreply, State, ?HIBERNATE_AFTER};
|
||||||
maybe_process_deferred_recv(State = #state{deferred_recv = Data, socket = Sock}) ->
|
maybe_process_deferred_recv(State = #state{ deferred_recv = Data, socket = Sock }) ->
|
||||||
handle_info(
|
handle_info({tcp, Sock, Data},
|
||||||
{tcp, Sock, Data},
|
State#state{ deferred_recv = undefined }).
|
||||||
State#state{deferred_recv = undefined}
|
|
||||||
).
|
|
||||||
|
|
||||||
maybe_emit_stats(#state{stats_timer = undefined}) ->
|
maybe_emit_stats(#state{stats_timer = undefined}) ->
|
||||||
ok;
|
ok;
|
||||||
maybe_emit_stats(State) ->
|
maybe_emit_stats(State) ->
|
||||||
rabbit_event:if_enabled(
|
rabbit_event:if_enabled(State, #state.stats_timer,
|
||||||
State,
|
fun() -> emit_stats(State) end).
|
||||||
#state.stats_timer,
|
|
||||||
fun() -> emit_stats(State) end
|
|
||||||
).
|
|
||||||
|
|
||||||
emit_stats(State = #state{received_connect_packet = false}) ->
|
emit_stats(State=#state{received_connect_packet = false}) ->
|
||||||
%% Avoid emitting stats on terminate when the connection has not yet been
|
%% Avoid emitting stats on terminate when the connection has not yet been
|
||||||
%% established, as this causes orphan entries on the stats database
|
%% established, as this causes orphan entries on the stats database
|
||||||
State1 = rabbit_event:reset_stats_timer(State, #state.stats_timer),
|
State1 = rabbit_event:reset_stats_timer(State, #state.stats_timer),
|
||||||
ensure_stats_timer(State1);
|
ensure_stats_timer(State1);
|
||||||
emit_stats(State) ->
|
emit_stats(State) ->
|
||||||
[
|
[{_, Pid},
|
||||||
{_, Pid},
|
{_, RecvOct},
|
||||||
{_, RecvOct},
|
{_, SendOct},
|
||||||
{_, SendOct},
|
{_, Reductions}] = infos(?SIMPLE_METRICS, State),
|
||||||
{_, Reductions}
|
|
||||||
] = infos(?SIMPLE_METRICS, State),
|
|
||||||
Infos = infos(?OTHER_METRICS, State),
|
Infos = infos(?OTHER_METRICS, State),
|
||||||
rabbit_core_metrics:connection_stats(Pid, Infos),
|
rabbit_core_metrics:connection_stats(Pid, Infos),
|
||||||
rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
|
rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
|
||||||
|
@ -539,13 +465,12 @@ ensure_stats_timer(State = #state{}) ->
|
||||||
infos(Items, State) ->
|
infos(Items, State) ->
|
||||||
[{Item, i(Item, State)} || Item <- Items].
|
[{Item, i(Item, State)} || Item <- Items].
|
||||||
|
|
||||||
i(SockStat, #state{socket = Sock}) when
|
i(SockStat, #state{socket = Sock})
|
||||||
SockStat =:= recv_oct;
|
when SockStat =:= recv_oct;
|
||||||
SockStat =:= recv_cnt;
|
SockStat =:= recv_cnt;
|
||||||
SockStat =:= send_oct;
|
SockStat =:= send_oct;
|
||||||
SockStat =:= send_cnt;
|
SockStat =:= send_cnt;
|
||||||
SockStat =:= send_pend
|
SockStat =:= send_pend ->
|
||||||
->
|
|
||||||
case rabbit_net:getstat(Sock, [SockStat]) of
|
case rabbit_net:getstat(Sock, [SockStat]) of
|
||||||
{ok, [{_, N}]} when is_number(N) ->
|
{ok, [{_, N}]} when is_number(N) ->
|
||||||
N;
|
N;
|
||||||
|
@ -569,19 +494,17 @@ i(connection_state, #state{connection_state = Val}) ->
|
||||||
Val;
|
Val;
|
||||||
i(pid, _) ->
|
i(pid, _) ->
|
||||||
self();
|
self();
|
||||||
i(SSL, #state{socket = Sock, proxy_socket = ProxySock}) when
|
i(SSL, #state{socket = Sock, proxy_socket = ProxySock})
|
||||||
SSL =:= ssl;
|
when SSL =:= ssl;
|
||||||
SSL =:= ssl_protocol;
|
SSL =:= ssl_protocol;
|
||||||
SSL =:= ssl_key_exchange;
|
SSL =:= ssl_key_exchange;
|
||||||
SSL =:= ssl_cipher;
|
SSL =:= ssl_cipher;
|
||||||
SSL =:= ssl_hash
|
SSL =:= ssl_hash ->
|
||||||
->
|
|
||||||
rabbit_ssl:info(SSL, {Sock, ProxySock});
|
rabbit_ssl:info(SSL, {Sock, ProxySock});
|
||||||
i(Cert, #state{socket = Sock}) when
|
i(Cert, #state{socket = Sock})
|
||||||
Cert =:= peer_cert_issuer;
|
when Cert =:= peer_cert_issuer;
|
||||||
Cert =:= peer_cert_subject;
|
Cert =:= peer_cert_subject;
|
||||||
Cert =:= peer_cert_validity
|
Cert =:= peer_cert_validity ->
|
||||||
->
|
|
||||||
rabbit_ssl:cert_info(Cert, Sock);
|
rabbit_ssl:cert_info(Cert, Sock);
|
||||||
i(timeout, #state{keepalive = KState}) ->
|
i(timeout, #state{keepalive = KState}) ->
|
||||||
rabbit_mqtt_keepalive:interval_secs(KState);
|
rabbit_mqtt_keepalive:interval_secs(KState);
|
||||||
|
@ -591,48 +514,40 @@ i(Key, #state{proc_state = ProcState}) ->
|
||||||
rabbit_mqtt_processor:info(Key, ProcState).
|
rabbit_mqtt_processor:info(Key, ProcState).
|
||||||
|
|
||||||
-spec format_status(Status) -> Status when
|
-spec format_status(Status) -> Status when
|
||||||
Status :: #{
|
Status :: #{state => term(),
|
||||||
state => term(),
|
message => term(),
|
||||||
message => term(),
|
reason => term(),
|
||||||
reason => term(),
|
log => [sys:system_event()]}.
|
||||||
log => [sys:system_event()]
|
|
||||||
}.
|
|
||||||
format_status(Status) ->
|
format_status(Status) ->
|
||||||
maps:map(
|
maps:map(
|
||||||
fun
|
fun(state, State) ->
|
||||||
(state, State) ->
|
format_state(State);
|
||||||
format_state(State);
|
(_, Value) ->
|
||||||
(_, Value) ->
|
Value
|
||||||
Value
|
end, Status).
|
||||||
end,
|
|
||||||
Status
|
|
||||||
).
|
|
||||||
|
|
||||||
-spec format_state(state()) -> map().
|
-spec format_state(state()) -> map().
|
||||||
format_state(#state{
|
format_state(#state{socket = Socket,
|
||||||
socket = Socket,
|
proxy_socket = ProxySock,
|
||||||
proxy_socket = ProxySock,
|
await_recv = AwaitRecv,
|
||||||
await_recv = AwaitRecv,
|
deferred_recv = DeferredRecv,
|
||||||
deferred_recv = DeferredRecv,
|
parse_state = _,
|
||||||
parse_state = _,
|
proc_state = PState,
|
||||||
proc_state = PState,
|
connection_state = ConnectionState,
|
||||||
connection_state = ConnectionState,
|
conserve = Conserve,
|
||||||
conserve = Conserve,
|
stats_timer = StatsTimer,
|
||||||
stats_timer = StatsTimer,
|
keepalive = Keepalive,
|
||||||
keepalive = Keepalive,
|
conn_name = ConnName,
|
||||||
conn_name = ConnName,
|
received_connect_packet = ReceivedConnectPacket
|
||||||
received_connect_packet = ReceivedConnectPacket
|
}) ->
|
||||||
}) ->
|
#{socket => Socket,
|
||||||
#{
|
proxy_socket => ProxySock,
|
||||||
socket => Socket,
|
await_recv => AwaitRecv,
|
||||||
proxy_socket => ProxySock,
|
deferred_recv => DeferredRecv =/= undefined,
|
||||||
await_recv => AwaitRecv,
|
proc_state => rabbit_mqtt_processor:format_status(PState),
|
||||||
deferred_recv => DeferredRecv =/= undefined,
|
connection_state => ConnectionState,
|
||||||
proc_state => rabbit_mqtt_processor:format_status(PState),
|
conserve => Conserve,
|
||||||
connection_state => ConnectionState,
|
stats_timer => StatsTimer,
|
||||||
conserve => Conserve,
|
keepalive => Keepalive,
|
||||||
stats_timer => StatsTimer,
|
conn_name => ConnName,
|
||||||
keepalive => Keepalive,
|
received_connect_packet => ReceivedConnectPacket}.
|
||||||
conn_name => ConnName,
|
|
||||||
received_connect_packet => ReceivedConnectPacket
|
|
||||||
}.
|
|
||||||
|
|
|
@ -13,57 +13,53 @@
|
||||||
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
|
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
|
||||||
|
|
||||||
-record(store_state, {
|
-record(store_state, {
|
||||||
%% DETS table name
|
%% DETS table name
|
||||||
table
|
table
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type store_state() :: #store_state{}.
|
-type store_state() :: #store_state{}.
|
||||||
|
|
||||||
-spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
|
-spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
|
||||||
new(Dir, VHost) ->
|
new(Dir, VHost) ->
|
||||||
Tid = open_table(Dir, VHost),
|
Tid = open_table(Dir, VHost),
|
||||||
#store_state{table = Tid}.
|
#store_state{table = Tid}.
|
||||||
|
|
||||||
-spec recover(file:name_all(), rabbit_types:vhost()) ->
|
-spec recover(file:name_all(), rabbit_types:vhost()) ->
|
||||||
{error, uninitialized} | {ok, store_state()}.
|
{error, uninitialized} | {ok, store_state()}.
|
||||||
recover(Dir, VHost) ->
|
recover(Dir, VHost) ->
|
||||||
case open_table(Dir, VHost) of
|
case open_table(Dir, VHost) of
|
||||||
{error, _} -> {error, uninitialized};
|
{error, _} -> {error, uninitialized};
|
||||||
{ok, Tid} -> {ok, #store_state{table = Tid}}
|
{ok, Tid} -> {ok, #store_state{table = Tid}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec insert(binary(), mqtt_msg(), store_state()) -> ok.
|
-spec insert(binary(), mqtt_msg(), store_state()) -> ok.
|
||||||
insert(Topic, Msg, #store_state{table = T}) ->
|
insert(Topic, Msg, #store_state{table = T}) ->
|
||||||
ok = dets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}).
|
ok = dets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}).
|
||||||
|
|
||||||
-spec lookup(binary(), store_state()) -> retained_message() | not_found.
|
-spec lookup(binary(), store_state()) -> retained_message() | not_found.
|
||||||
lookup(Topic, #store_state{table = T}) ->
|
lookup(Topic, #store_state{table = T}) ->
|
||||||
case dets:lookup(T, Topic) of
|
case dets:lookup(T, Topic) of
|
||||||
[] -> not_found;
|
[] -> not_found;
|
||||||
[Entry] -> Entry
|
[Entry] -> Entry
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec delete(binary(), store_state()) -> ok.
|
-spec delete(binary(), store_state()) -> ok.
|
||||||
delete(Topic, #store_state{table = T}) ->
|
delete(Topic, #store_state{table = T}) ->
|
||||||
ok = dets:delete(T, Topic).
|
ok = dets:delete(T, Topic).
|
||||||
|
|
||||||
-spec terminate(store_state()) -> ok.
|
-spec terminate(store_state()) -> ok.
|
||||||
terminate(#store_state{table = T}) ->
|
terminate(#store_state{table = T}) ->
|
||||||
ok = dets:close(T).
|
ok = dets:close(T).
|
||||||
|
|
||||||
open_table(Dir, VHost) ->
|
open_table(Dir, VHost) ->
|
||||||
dets:open_file(
|
dets:open_file(rabbit_mqtt_util:vhost_name_to_table_name(VHost),
|
||||||
rabbit_mqtt_util:vhost_name_to_table_name(VHost),
|
table_options(rabbit_mqtt_util:path_for(Dir, VHost, ".dets"))).
|
||||||
table_options(rabbit_mqtt_util:path_for(Dir, VHost, ".dets"))
|
|
||||||
).
|
|
||||||
|
|
||||||
table_options(Path) ->
|
table_options(Path) ->
|
||||||
[
|
[{type, set},
|
||||||
{type, set},
|
{keypos, #retained_message.topic},
|
||||||
{keypos, #retained_message.topic},
|
{file, Path},
|
||||||
{file, Path},
|
{ram_file, true},
|
||||||
{ram_file, true},
|
{repair, true},
|
||||||
{repair, true},
|
{auto_save, rabbit_misc:get_env(rabbit_mqtt, retained_message_store_dets_sync_interval, 2000)}
|
||||||
{auto_save,
|
|
||||||
rabbit_misc:get_env(rabbit_mqtt, retained_message_store_dets_sync_interval, 2000)}
|
|
||||||
].
|
].
|
||||||
|
|
|
@ -13,51 +13,49 @@
|
||||||
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
|
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
|
||||||
|
|
||||||
-record(store_state, {
|
-record(store_state, {
|
||||||
%% ETS table ID
|
%% ETS table ID
|
||||||
table,
|
table,
|
||||||
%% where the table is stored on disk
|
%% where the table is stored on disk
|
||||||
filename
|
filename
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type store_state() :: #store_state{}.
|
-type store_state() :: #store_state{}.
|
||||||
|
|
||||||
-spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
|
-spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
|
||||||
new(Dir, VHost) ->
|
new(Dir, VHost) ->
|
||||||
Path = rabbit_mqtt_util:path_for(Dir, VHost),
|
Path = rabbit_mqtt_util:path_for(Dir, VHost),
|
||||||
TableName = rabbit_mqtt_util:vhost_name_to_table_name(VHost),
|
TableName = rabbit_mqtt_util:vhost_name_to_table_name(VHost),
|
||||||
_ = file:delete(Path),
|
_ = file:delete(Path),
|
||||||
Tid = ets:new(TableName, [set, public, {keypos, #retained_message.topic}]),
|
Tid = ets:new(TableName, [set, public, {keypos, #retained_message.topic}]),
|
||||||
#store_state{table = Tid, filename = Path}.
|
#store_state{table = Tid, filename = Path}.
|
||||||
|
|
||||||
-spec recover(file:name_all(), rabbit_types:vhost()) ->
|
-spec recover(file:name_all(), rabbit_types:vhost()) ->
|
||||||
{error, uninitialized} | {ok, store_state()}.
|
{error, uninitialized} | {ok, store_state()}.
|
||||||
recover(Dir, VHost) ->
|
recover(Dir, VHost) ->
|
||||||
Path = rabbit_mqtt_util:path_for(Dir, VHost),
|
Path = rabbit_mqtt_util:path_for(Dir, VHost),
|
||||||
case ets:file2tab(Path) of
|
case ets:file2tab(Path) of
|
||||||
{ok, Tid} ->
|
{ok, Tid} -> _ = file:delete(Path),
|
||||||
_ = file:delete(Path),
|
{ok, #store_state{table = Tid, filename = Path}};
|
||||||
{ok, #store_state{table = Tid, filename = Path}};
|
{error, _} -> {error, uninitialized}
|
||||||
{error, _} ->
|
end.
|
||||||
{error, uninitialized}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec insert(binary(), mqtt_msg(), store_state()) -> ok.
|
-spec insert(binary(), mqtt_msg(), store_state()) -> ok.
|
||||||
insert(Topic, Msg, #store_state{table = T}) ->
|
insert(Topic, Msg, #store_state{table = T}) ->
|
||||||
true = ets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}),
|
true = ets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
-spec lookup(binary(), store_state()) -> retained_message() | not_found.
|
-spec lookup(binary(), store_state()) -> retained_message() | not_found.
|
||||||
lookup(Topic, #store_state{table = T}) ->
|
lookup(Topic, #store_state{table = T}) ->
|
||||||
case ets:lookup(T, Topic) of
|
case ets:lookup(T, Topic) of
|
||||||
[] -> not_found;
|
[] -> not_found;
|
||||||
[Entry] -> Entry
|
[Entry] -> Entry
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec delete(binary(), store_state()) -> ok.
|
-spec delete(binary(), store_state()) -> ok.
|
||||||
delete(Topic, #store_state{table = T}) ->
|
delete(Topic, #store_state{table = T}) ->
|
||||||
true = ets:delete(T, Topic),
|
true = ets:delete(T, Topic),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
-spec terminate(store_state()) -> ok.
|
-spec terminate(store_state()) -> ok.
|
||||||
terminate(#store_state{table = T, filename = Path}) ->
|
terminate(#store_state{table = T, filename = Path}) ->
|
||||||
ok = ets:tab2file(T, Path, [{extended_info, [object_count]}]).
|
ok = ets:tab2file(T, Path, [{extended_info, [object_count]}]).
|
||||||
|
|
|
@ -12,19 +12,19 @@
|
||||||
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
|
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
|
||||||
|
|
||||||
new(_Dir, _VHost) ->
|
new(_Dir, _VHost) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
recover(_Dir, _VHost) ->
|
recover(_Dir, _VHost) ->
|
||||||
{ok, ok}.
|
{ok, ok}.
|
||||||
|
|
||||||
insert(_Topic, _Msg, _State) ->
|
insert(_Topic, _Msg, _State) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
lookup(_Topic, _State) ->
|
lookup(_Topic, _State) ->
|
||||||
not_found.
|
not_found.
|
||||||
|
|
||||||
delete(_Topic, _State) ->
|
delete(_Topic, _State) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
terminate(_State) ->
|
terminate(_State) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -12,23 +12,15 @@
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-export([
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
init/1,
|
terminate/2, start_link/2]).
|
||||||
handle_call/3,
|
|
||||||
handle_cast/2,
|
|
||||||
handle_info/2,
|
|
||||||
terminate/2,
|
|
||||||
start_link/2
|
|
||||||
]).
|
|
||||||
|
|
||||||
-export([retain/3, fetch/2, clear/2, store_module/0]).
|
-export([retain/3, fetch/2, clear/2, store_module/0]).
|
||||||
|
|
||||||
-define(TIMEOUT, 30_000).
|
-define(TIMEOUT, 30_000).
|
||||||
|
|
||||||
-record(retainer_state, {
|
-record(retainer_state, {store_mod,
|
||||||
store_mod,
|
store}).
|
||||||
store
|
|
||||||
}).
|
|
||||||
|
|
||||||
%%----------------------------------------------------------------------------
|
%%----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -54,19 +46,12 @@ clear(Pid, Topic) ->
|
||||||
|
|
||||||
init([StoreMod, VHost]) ->
|
init([StoreMod, VHost]) ->
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
State =
|
State = case StoreMod:recover(store_dir(), VHost) of
|
||||||
case StoreMod:recover(store_dir(), VHost) of
|
{ok, Store} -> #retainer_state{store = Store,
|
||||||
{ok, Store} ->
|
store_mod = StoreMod};
|
||||||
#retainer_state{
|
{error, _} -> #retainer_state{store = StoreMod:new(store_dir(), VHost),
|
||||||
store = Store,
|
store_mod = StoreMod}
|
||||||
store_mod = StoreMod
|
end,
|
||||||
};
|
|
||||||
{error, _} ->
|
|
||||||
#retainer_state{
|
|
||||||
store = StoreMod:new(store_dir(), VHost),
|
|
||||||
store_mod = StoreMod
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
-spec store_module() -> undefined | module().
|
-spec store_module() -> undefined | module().
|
||||||
|
@ -78,33 +63,26 @@ store_module() ->
|
||||||
|
|
||||||
%%----------------------------------------------------------------------------
|
%%----------------------------------------------------------------------------
|
||||||
|
|
||||||
handle_cast(
|
handle_cast({retain, Topic, Msg},
|
||||||
{retain, Topic, Msg},
|
State = #retainer_state{store = Store, store_mod = Mod}) ->
|
||||||
State = #retainer_state{store = Store, store_mod = Mod}
|
|
||||||
) ->
|
|
||||||
ok = Mod:insert(Topic, Msg, Store),
|
ok = Mod:insert(Topic, Msg, Store),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
handle_cast(
|
handle_cast({clear, Topic},
|
||||||
{clear, Topic},
|
State = #retainer_state{store = Store, store_mod = Mod}) ->
|
||||||
State = #retainer_state{store = Store, store_mod = Mod}
|
|
||||||
) ->
|
|
||||||
ok = Mod:delete(Topic, Store),
|
ok = Mod:delete(Topic, Store),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_call(
|
handle_call({fetch, Topic}, _From,
|
||||||
{fetch, Topic},
|
State = #retainer_state{store = Store, store_mod = Mod}) ->
|
||||||
_From,
|
Reply = case Mod:lookup(Topic, Store) of
|
||||||
State = #retainer_state{store = Store, store_mod = Mod}
|
#retained_message{mqtt_msg = Msg} -> Msg;
|
||||||
) ->
|
not_found -> undefined
|
||||||
Reply =
|
end,
|
||||||
case Mod:lookup(Topic, Store) of
|
|
||||||
#retained_message{mqtt_msg = Msg} -> Msg;
|
|
||||||
not_found -> undefined
|
|
||||||
end,
|
|
||||||
{reply, Reply, State}.
|
{reply, Reply, State}.
|
||||||
|
|
||||||
handle_info(stop, State) ->
|
handle_info(stop, State) ->
|
||||||
{stop, normal, State};
|
{stop, normal, State};
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
{stop, {unknown_info, Info}, State}.
|
{stop, {unknown_info, Info}, State}.
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,8 @@
|
||||||
-module(rabbit_mqtt_retainer_sup).
|
-module(rabbit_mqtt_retainer_sup).
|
||||||
-behaviour(supervisor).
|
-behaviour(supervisor).
|
||||||
|
|
||||||
-export([
|
-export([start_link/1, init/1, start_child/2,start_child/1, child_for_vhost/1,
|
||||||
start_link/1,
|
delete_child/1]).
|
||||||
init/1,
|
|
||||||
start_child/2, start_child/1,
|
|
||||||
child_for_vhost/1,
|
|
||||||
delete_child/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-spec start_child(binary()) -> supervisor:startchild_ret().
|
-spec start_child(binary()) -> supervisor:startchild_ret().
|
||||||
-spec start_child(term(), binary()) -> supervisor:startchild_ret().
|
-spec start_child(term(), binary()) -> supervisor:startchild_ret().
|
||||||
|
@ -24,13 +19,13 @@ start_link(SupName) ->
|
||||||
|
|
||||||
-spec child_for_vhost(rabbit_types:vhost()) -> pid().
|
-spec child_for_vhost(rabbit_types:vhost()) -> pid().
|
||||||
child_for_vhost(VHost) when is_binary(VHost) ->
|
child_for_vhost(VHost) when is_binary(VHost) ->
|
||||||
case rabbit_mqtt_retainer_sup:start_child(VHost) of
|
case rabbit_mqtt_retainer_sup:start_child(VHost) of
|
||||||
{ok, Pid} -> Pid;
|
{ok, Pid} -> Pid;
|
||||||
{error, {already_started, Pid}} -> Pid
|
{error, {already_started, Pid}} -> Pid
|
||||||
end.
|
end.
|
||||||
|
|
||||||
start_child(VHost) when is_binary(VHost) ->
|
start_child(VHost) when is_binary(VHost) ->
|
||||||
start_child(rabbit_mqtt_retainer:store_module(), VHost).
|
start_child(rabbit_mqtt_retainer:store_module(), VHost).
|
||||||
|
|
||||||
start_child(RetainStoreMod, VHost) ->
|
start_child(RetainStoreMod, VHost) ->
|
||||||
supervisor:start_child(
|
supervisor:start_child(
|
||||||
|
@ -46,20 +41,18 @@ start_child(RetainStoreMod, VHost) ->
|
||||||
).
|
).
|
||||||
|
|
||||||
delete_child(VHost) ->
|
delete_child(VHost) ->
|
||||||
Id = vhost_to_atom(VHost),
|
Id = vhost_to_atom(VHost),
|
||||||
ok = supervisor:terminate_child(?MODULE, Id),
|
ok = supervisor:terminate_child(?MODULE, Id),
|
||||||
ok = supervisor:delete_child(?MODULE, Id).
|
ok = supervisor:delete_child(?MODULE, Id).
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
Mod = rabbit_mqtt_retainer:store_module(),
|
Mod = rabbit_mqtt_retainer:store_module(),
|
||||||
rabbit_log:info(
|
rabbit_log:info("MQTT retained message store: ~tp",
|
||||||
"MQTT retained message store: ~tp",
|
[Mod]),
|
||||||
[Mod]
|
{ok, {
|
||||||
),
|
#{strategy => one_for_one, intensity => 5, period => 5},
|
||||||
{ok, {
|
child_specs(Mod, rabbit_vhost:list_names())
|
||||||
#{strategy => one_for_one, intensity => 5, period => 5},
|
}}.
|
||||||
child_specs(Mod, rabbit_vhost:list_names())
|
|
||||||
}}.
|
|
||||||
|
|
||||||
child_specs(Mod, VHosts) ->
|
child_specs(Mod, VHosts) ->
|
||||||
%% see start_child/2
|
%% see start_child/2
|
||||||
|
|
|
@ -23,61 +23,52 @@ init([{Listeners, SslListeners0}]) ->
|
||||||
NumTcpAcceptors = application:get_env(?APP_NAME, num_tcp_acceptors, 10),
|
NumTcpAcceptors = application:get_env(?APP_NAME, num_tcp_acceptors, 10),
|
||||||
ConcurrentConnsSups = application:get_env(?APP_NAME, num_conns_sups, 1),
|
ConcurrentConnsSups = application:get_env(?APP_NAME, num_conns_sups, 1),
|
||||||
{ok, SocketOpts} = application:get_env(?APP_NAME, tcp_listen_options),
|
{ok, SocketOpts} = application:get_env(?APP_NAME, tcp_listen_options),
|
||||||
{SslOpts, NumSslAcceptors, SslListeners} =
|
{SslOpts, NumSslAcceptors, SslListeners}
|
||||||
case SslListeners0 of
|
= case SslListeners0 of
|
||||||
[] ->
|
[] -> {none, 0, []};
|
||||||
{none, 0, []};
|
_ -> {rabbit_networking:ensure_ssl(),
|
||||||
_ ->
|
application:get_env(?APP_NAME, num_ssl_acceptors, 10),
|
||||||
{
|
case rabbit_networking:poodle_check('MQTT') of
|
||||||
rabbit_networking:ensure_ssl(),
|
ok -> SslListeners0;
|
||||||
application:get_env(?APP_NAME, num_ssl_acceptors, 10),
|
danger -> []
|
||||||
case rabbit_networking:poodle_check('MQTT') of
|
end}
|
||||||
ok -> SslListeners0;
|
end,
|
||||||
danger -> []
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
%% Use separate process group scope per RabbitMQ node. This achieves a local-only
|
%% Use separate process group scope per RabbitMQ node. This achieves a local-only
|
||||||
%% process group which requires less memory with millions of connections.
|
%% process group which requires less memory with millions of connections.
|
||||||
PgScope = list_to_atom(io_lib:format("~s_~s", [?PG_SCOPE, node()])),
|
PgScope = list_to_atom(io_lib:format("~s_~s", [?PG_SCOPE, node()])),
|
||||||
persistent_term:put(?PG_SCOPE, PgScope),
|
persistent_term:put(?PG_SCOPE, PgScope),
|
||||||
{ok,
|
{ok,
|
||||||
{
|
{#{strategy => one_for_all,
|
||||||
#{
|
intensity => 10,
|
||||||
strategy => one_for_all,
|
period => 10},
|
||||||
intensity => 10,
|
[
|
||||||
period => 10
|
#{id => PgScope,
|
||||||
},
|
start => {pg, start_link, [PgScope]},
|
||||||
[
|
restart => transient,
|
||||||
#{
|
shutdown => ?WORKER_WAIT,
|
||||||
id => PgScope,
|
type => worker,
|
||||||
start => {pg, start_link, [PgScope]},
|
modules => [pg]
|
||||||
restart => transient,
|
},
|
||||||
shutdown => ?WORKER_WAIT,
|
#{
|
||||||
type => worker,
|
id => rabbit_mqtt_retainer_sup,
|
||||||
modules => [pg]
|
start => {rabbit_mqtt_retainer_sup, start_link,
|
||||||
},
|
[{local, rabbit_mqtt_retainer_sup}]},
|
||||||
#{
|
restart => transient,
|
||||||
id => rabbit_mqtt_retainer_sup,
|
shutdown => ?SUPERVISOR_WAIT,
|
||||||
start =>
|
type => supervisor,
|
||||||
{rabbit_mqtt_retainer_sup, start_link, [{local, rabbit_mqtt_retainer_sup}]},
|
modules => [rabbit_mqtt_retainer_sup]
|
||||||
restart => transient,
|
}
|
||||||
shutdown => ?SUPERVISOR_WAIT,
|
| listener_specs(
|
||||||
type => supervisor,
|
fun tcp_listener_spec/1,
|
||||||
modules => [rabbit_mqtt_retainer_sup]
|
[SocketOpts, NumTcpAcceptors, ConcurrentConnsSups],
|
||||||
}
|
Listeners
|
||||||
| listener_specs(
|
) ++
|
||||||
fun tcp_listener_spec/1,
|
listener_specs(
|
||||||
[SocketOpts, NumTcpAcceptors, ConcurrentConnsSups],
|
fun ssl_listener_spec/1,
|
||||||
Listeners
|
[SocketOpts, SslOpts, NumSslAcceptors, ConcurrentConnsSups],
|
||||||
) ++
|
SslListeners
|
||||||
listener_specs(
|
)
|
||||||
fun ssl_listener_spec/1,
|
]}}.
|
||||||
[SocketOpts, SslOpts, NumSslAcceptors, ConcurrentConnsSups],
|
|
||||||
SslListeners
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}}.
|
|
||||||
|
|
||||||
-spec stop_listeners() -> ok.
|
-spec stop_listeners() -> ok.
|
||||||
stop_listeners() ->
|
stop_listeners() ->
|
||||||
|
@ -98,33 +89,33 @@ listener_specs(Fun, Args, Listeners) ->
|
||||||
|
|
||||||
tcp_listener_spec([Address, SocketOpts, NumAcceptors, ConcurrentConnsSups]) ->
|
tcp_listener_spec([Address, SocketOpts, NumAcceptors, ConcurrentConnsSups]) ->
|
||||||
rabbit_networking:tcp_listener_spec(
|
rabbit_networking:tcp_listener_spec(
|
||||||
rabbit_mqtt_listener_sup,
|
rabbit_mqtt_listener_sup,
|
||||||
Address,
|
Address,
|
||||||
SocketOpts,
|
SocketOpts,
|
||||||
transport(?TCP_PROTOCOL),
|
transport(?TCP_PROTOCOL),
|
||||||
rabbit_mqtt_reader,
|
rabbit_mqtt_reader,
|
||||||
[],
|
[],
|
||||||
mqtt,
|
mqtt,
|
||||||
NumAcceptors,
|
NumAcceptors,
|
||||||
ConcurrentConnsSups,
|
ConcurrentConnsSups,
|
||||||
worker,
|
worker,
|
||||||
"MQTT TCP listener"
|
"MQTT TCP listener"
|
||||||
).
|
).
|
||||||
|
|
||||||
ssl_listener_spec([Address, SocketOpts, SslOpts, NumAcceptors, ConcurrentConnsSups]) ->
|
ssl_listener_spec([Address, SocketOpts, SslOpts, NumAcceptors, ConcurrentConnsSups]) ->
|
||||||
rabbit_networking:tcp_listener_spec(
|
rabbit_networking:tcp_listener_spec(
|
||||||
rabbit_mqtt_listener_sup,
|
rabbit_mqtt_listener_sup,
|
||||||
Address,
|
Address,
|
||||||
SocketOpts ++ SslOpts,
|
SocketOpts ++ SslOpts,
|
||||||
transport(?TLS_PROTOCOL),
|
transport(?TLS_PROTOCOL),
|
||||||
rabbit_mqtt_reader,
|
rabbit_mqtt_reader,
|
||||||
[],
|
[],
|
||||||
'mqtt/ssl',
|
'mqtt/ssl',
|
||||||
NumAcceptors,
|
NumAcceptors,
|
||||||
ConcurrentConnsSups,
|
ConcurrentConnsSups,
|
||||||
worker,
|
worker,
|
||||||
"MQTT TLS listener"
|
"MQTT TLS listener"
|
||||||
).
|
).
|
||||||
|
|
||||||
transport(?TCP_PROTOCOL) ->
|
transport(?TCP_PROTOCOL) ->
|
||||||
ranch_tcp;
|
ranch_tcp;
|
||||||
|
|
|
@ -11,21 +11,20 @@
|
||||||
-include("rabbit_mqtt.hrl").
|
-include("rabbit_mqtt.hrl").
|
||||||
-include("rabbit_mqtt_packet.hrl").
|
-include("rabbit_mqtt_packet.hrl").
|
||||||
|
|
||||||
-export([
|
-export([queue_name_bin/2,
|
||||||
queue_name_bin/2,
|
qos_from_queue_name/2,
|
||||||
qos_from_queue_name/2,
|
env/1,
|
||||||
env/1,
|
table_lookup/2,
|
||||||
table_lookup/2,
|
path_for/2,
|
||||||
path_for/2,
|
path_for/3,
|
||||||
path_for/3,
|
vhost_name_to_table_name/1,
|
||||||
vhost_name_to_table_name/1,
|
register_clientid/2,
|
||||||
register_clientid/2,
|
remove_duplicate_clientid_connections/2,
|
||||||
remove_duplicate_clientid_connections/2,
|
init_sparkplug/0,
|
||||||
init_sparkplug/0,
|
mqtt_to_amqp/1,
|
||||||
mqtt_to_amqp/1,
|
amqp_to_mqtt/1,
|
||||||
amqp_to_mqtt/1,
|
truncate_binary/2
|
||||||
truncate_binary/2
|
]).
|
||||||
]).
|
|
||||||
|
|
||||||
-define(MAX_TOPIC_TRANSLATION_CACHE_SIZE, 12).
|
-define(MAX_TOPIC_TRANSLATION_CACHE_SIZE, 12).
|
||||||
-define(SPARKPLUG_MP_MQTT_TO_AMQP, sparkplug_mp_mqtt_to_amqp).
|
-define(SPARKPLUG_MP_MQTT_TO_AMQP, sparkplug_mp_mqtt_to_amqp).
|
||||||
|
@ -73,8 +72,7 @@ init_sparkplug() ->
|
||||||
|
|
||||||
-spec mqtt_to_amqp(binary()) -> binary().
|
-spec mqtt_to_amqp(binary()) -> binary().
|
||||||
mqtt_to_amqp(Topic) ->
|
mqtt_to_amqp(Topic) ->
|
||||||
T =
|
T = case persistent_term:get(?SPARKPLUG_MP_MQTT_TO_AMQP, no_sparkplug) of
|
||||||
case persistent_term:get(?SPARKPLUG_MP_MQTT_TO_AMQP, no_sparkplug) of
|
|
||||||
no_sparkplug ->
|
no_sparkplug ->
|
||||||
Topic;
|
Topic;
|
||||||
M2A_SpRe ->
|
M2A_SpRe ->
|
||||||
|
@ -104,13 +102,12 @@ amqp_to_mqtt(Topic) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
cached(CacheName, Fun, Arg) ->
|
cached(CacheName, Fun, Arg) ->
|
||||||
Cache =
|
Cache = case get(CacheName) of
|
||||||
case get(CacheName) of
|
undefined ->
|
||||||
undefined ->
|
[];
|
||||||
[];
|
Other ->
|
||||||
Other ->
|
Other
|
||||||
Other
|
end,
|
||||||
end,
|
|
||||||
case lists:keyfind(Arg, 1, Cache) of
|
case lists:keyfind(Arg, 1, Cache) of
|
||||||
{_, V} ->
|
{_, V} ->
|
||||||
V;
|
V;
|
||||||
|
@ -145,11 +142,11 @@ env(Key) ->
|
||||||
|
|
||||||
coerce_env_value(default_pass, Val) -> rabbit_data_coercion:to_binary(Val);
|
coerce_env_value(default_pass, Val) -> rabbit_data_coercion:to_binary(Val);
|
||||||
coerce_env_value(default_user, Val) -> rabbit_data_coercion:to_binary(Val);
|
coerce_env_value(default_user, Val) -> rabbit_data_coercion:to_binary(Val);
|
||||||
coerce_env_value(exchange, Val) -> rabbit_data_coercion:to_binary(Val);
|
coerce_env_value(exchange, Val) -> rabbit_data_coercion:to_binary(Val);
|
||||||
coerce_env_value(vhost, Val) -> rabbit_data_coercion:to_binary(Val);
|
coerce_env_value(vhost, Val) -> rabbit_data_coercion:to_binary(Val);
|
||||||
coerce_env_value(_, Val) -> Val.
|
coerce_env_value(_, Val) -> Val.
|
||||||
|
|
||||||
-spec table_lookup(rabbit_framing:amqp_table() | undefined, binary()) ->
|
-spec table_lookup(rabbit_framing:amqp_table() | undefined, binary()) ->
|
||||||
tuple() | undefined.
|
tuple() | undefined.
|
||||||
table_lookup(undefined, _Key) ->
|
table_lookup(undefined, _Key) ->
|
||||||
undefined;
|
undefined;
|
||||||
|
@ -164,11 +161,11 @@ vhost_name_to_dir_name(VHost, Suffix) ->
|
||||||
|
|
||||||
-spec path_for(file:name_all(), rabbit_types:vhost()) -> file:filename_all().
|
-spec path_for(file:name_all(), rabbit_types:vhost()) -> file:filename_all().
|
||||||
path_for(Dir, VHost) ->
|
path_for(Dir, VHost) ->
|
||||||
filename:join(Dir, vhost_name_to_dir_name(VHost)).
|
filename:join(Dir, vhost_name_to_dir_name(VHost)).
|
||||||
|
|
||||||
-spec path_for(file:name_all(), rabbit_types:vhost(), string()) -> file:filename_all().
|
-spec path_for(file:name_all(), rabbit_types:vhost(), string()) -> file:filename_all().
|
||||||
path_for(Dir, VHost, Suffix) ->
|
path_for(Dir, VHost, Suffix) ->
|
||||||
filename:join(Dir, vhost_name_to_dir_name(VHost, Suffix)).
|
filename:join(Dir, vhost_name_to_dir_name(VHost, Suffix)).
|
||||||
|
|
||||||
-spec vhost_name_to_table_name(rabbit_types:vhost()) ->
|
-spec vhost_name_to_table_name(rabbit_types:vhost()) ->
|
||||||
atom().
|
atom().
|
||||||
|
@ -177,9 +174,8 @@ vhost_name_to_table_name(VHost) ->
|
||||||
list_to_atom("rabbit_mqtt_retained_" ++ rabbit_misc:format("~36.16.0b", [Num])).
|
list_to_atom("rabbit_mqtt_retained_" ++ rabbit_misc:format("~36.16.0b", [Num])).
|
||||||
|
|
||||||
-spec register_clientid(rabbit_types:vhost(), binary()) -> ok.
|
-spec register_clientid(rabbit_types:vhost(), binary()) -> ok.
|
||||||
register_clientid(Vhost, ClientId) when
|
register_clientid(Vhost, ClientId)
|
||||||
is_binary(Vhost), is_binary(ClientId)
|
when is_binary(Vhost), is_binary(ClientId) ->
|
||||||
->
|
|
||||||
PgGroup = {Vhost, ClientId},
|
PgGroup = {Vhost, ClientId},
|
||||||
ok = pg:join(persistent_term:get(?PG_SCOPE), PgGroup, self()),
|
ok = pg:join(persistent_term:get(?PG_SCOPE), PgGroup, self()),
|
||||||
case rabbit_mqtt_ff:track_client_id_in_ra() of
|
case rabbit_mqtt_ff:track_client_id_in_ra() of
|
||||||
|
@ -187,12 +183,10 @@ register_clientid(Vhost, ClientId) when
|
||||||
%% Ra node takes care of removing duplicate client ID connections.
|
%% Ra node takes care of removing duplicate client ID connections.
|
||||||
ok;
|
ok;
|
||||||
false ->
|
false ->
|
||||||
ok = erpc:multicast(
|
ok = erpc:multicast([node() | nodes()],
|
||||||
[node() | nodes()],
|
?MODULE,
|
||||||
?MODULE,
|
remove_duplicate_clientid_connections,
|
||||||
remove_duplicate_clientid_connections,
|
[PgGroup, self()])
|
||||||
[PgGroup, self()]
|
|
||||||
)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec remove_duplicate_clientid_connections({rabbit_types:vhost(), binary()}, pid()) -> ok.
|
-spec remove_duplicate_clientid_connections({rabbit_types:vhost(), binary()}, pid()) -> ok.
|
||||||
|
@ -200,24 +194,18 @@ remove_duplicate_clientid_connections(PgGroup, PidToKeep) ->
|
||||||
try persistent_term:get(?PG_SCOPE) of
|
try persistent_term:get(?PG_SCOPE) of
|
||||||
PgScope ->
|
PgScope ->
|
||||||
Pids = pg:get_local_members(PgScope, PgGroup),
|
Pids = pg:get_local_members(PgScope, PgGroup),
|
||||||
lists:foreach(
|
lists:foreach(fun(Pid) ->
|
||||||
fun(Pid) ->
|
gen_server:cast(Pid, duplicate_id)
|
||||||
gen_server:cast(Pid, duplicate_id)
|
end, Pids -- [PidToKeep])
|
||||||
end,
|
catch _:badarg ->
|
||||||
Pids -- [PidToKeep]
|
%% MQTT supervision tree on this node not fully started
|
||||||
)
|
ok
|
||||||
catch
|
|
||||||
_:badarg ->
|
|
||||||
%% MQTT supervision tree on this node not fully started
|
|
||||||
ok
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec truncate_binary(binary(), non_neg_integer()) -> binary().
|
-spec truncate_binary(binary(), non_neg_integer()) -> binary().
|
||||||
truncate_binary(Bin, Size) when
|
truncate_binary(Bin, Size)
|
||||||
is_binary(Bin) andalso byte_size(Bin) =< Size
|
when is_binary(Bin) andalso byte_size(Bin) =< Size ->
|
||||||
->
|
|
||||||
Bin;
|
Bin;
|
||||||
truncate_binary(Bin, Size) when
|
truncate_binary(Bin, Size)
|
||||||
is_binary(Bin)
|
when is_binary(Bin) ->
|
||||||
->
|
|
||||||
binary:part(Bin, 0, Size).
|
binary:part(Bin, 0, Size).
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,42 +9,36 @@
|
||||||
|
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-import(util, [
|
-import(util, [expect_publishes/3,
|
||||||
expect_publishes/3,
|
connect/3,
|
||||||
connect/3,
|
connect/4,
|
||||||
connect/4,
|
await_exit/1]).
|
||||||
await_exit/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-import(
|
-import(rabbit_ct_broker_helpers,
|
||||||
rabbit_ct_broker_helpers,
|
[setup_steps/0,
|
||||||
[
|
teardown_steps/0,
|
||||||
setup_steps/0,
|
get_node_config/3,
|
||||||
teardown_steps/0,
|
rabbitmqctl/3,
|
||||||
get_node_config/3,
|
rpc/4,
|
||||||
rabbitmqctl/3,
|
stop_node/2
|
||||||
rpc/4,
|
]).
|
||||||
stop_node/2
|
|
||||||
]
|
|
||||||
).
|
|
||||||
|
|
||||||
-define(OPTS, [
|
-define(OPTS, [{connect_timeout, 1},
|
||||||
{connect_timeout, 1},
|
{ack_timeout, 1}]).
|
||||||
{ack_timeout, 1}
|
|
||||||
]).
|
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
[
|
||||||
{group, cluster_size_5}
|
{group, cluster_size_5}
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[
|
[
|
||||||
{cluster_size_5, [], [
|
{cluster_size_5, [],
|
||||||
connection_id_tracking,
|
[
|
||||||
connection_id_tracking_on_nodedown,
|
connection_id_tracking,
|
||||||
connection_id_tracking_with_decommissioned_node
|
connection_id_tracking_on_nodedown,
|
||||||
]}
|
connection_id_tracking_with_decommissioned_node
|
||||||
|
]}
|
||||||
].
|
].
|
||||||
|
|
||||||
suite() ->
|
suite() ->
|
||||||
|
@ -56,12 +50,11 @@ suite() ->
|
||||||
|
|
||||||
merge_app_env(Config) ->
|
merge_app_env(Config) ->
|
||||||
rabbit_ct_helpers:merge_app_env(
|
rabbit_ct_helpers:merge_app_env(
|
||||||
Config,
|
Config,
|
||||||
{rabbit, [
|
{rabbit, [
|
||||||
{collect_statistics, basic},
|
{collect_statistics, basic},
|
||||||
{collect_statistics_interval, 100}
|
{collect_statistics_interval, 100}
|
||||||
]}
|
]}).
|
||||||
).
|
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:log_environment(),
|
rabbit_ct_helpers:log_environment(),
|
||||||
|
@ -72,8 +65,7 @@ end_per_suite(Config) ->
|
||||||
|
|
||||||
init_per_group(cluster_size_5, Config) ->
|
init_per_group(cluster_size_5, Config) ->
|
||||||
rabbit_ct_helpers:set_config(
|
rabbit_ct_helpers:set_config(
|
||||||
Config, [{rmq_nodes_count, 5}]
|
Config, [{rmq_nodes_count, 5}]).
|
||||||
).
|
|
||||||
|
|
||||||
end_per_group(_, Config) ->
|
end_per_group(_, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
@ -83,25 +75,19 @@ init_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_helpers:log_environment(),
|
rabbit_ct_helpers:log_environment(),
|
||||||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||||
{rmq_nodename_suffix, Testcase},
|
{rmq_nodename_suffix, Testcase},
|
||||||
{rmq_extra_tcp_ports, [
|
{rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
|
||||||
tcp_port_mqtt_extra,
|
tcp_port_mqtt_tls_extra]},
|
||||||
tcp_port_mqtt_tls_extra
|
|
||||||
]},
|
|
||||||
{rmq_nodes_clustered, true}
|
{rmq_nodes_clustered, true}
|
||||||
]),
|
]),
|
||||||
rabbit_ct_helpers:run_setup_steps(
|
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||||
Config1,
|
[ fun merge_app_env/1 ] ++
|
||||||
[fun merge_app_env/1] ++
|
setup_steps() ++
|
||||||
setup_steps() ++
|
rabbit_ct_client_helpers:setup_steps()).
|
||||||
rabbit_ct_client_helpers:setup_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
end_per_testcase(Testcase, Config) ->
|
end_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_helpers:run_teardown_steps(
|
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||||
Config,
|
rabbit_ct_client_helpers:teardown_steps() ++
|
||||||
rabbit_ct_client_helpers:teardown_steps() ++
|
teardown_steps()),
|
||||||
teardown_steps()
|
|
||||||
),
|
|
||||||
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
||||||
|
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
|
@ -179,15 +165,14 @@ connection_id_tracking_with_decommissioned_node(Config) ->
|
||||||
%% Helpers
|
%% Helpers
|
||||||
%%
|
%%
|
||||||
|
|
||||||
assert_connection_count(_Config, 0, _, NumElements) ->
|
assert_connection_count(_Config, 0, _, NumElements) ->
|
||||||
ct:fail("failed to match connection count ~b", [NumElements]);
|
ct:fail("failed to match connection count ~b", [NumElements]);
|
||||||
assert_connection_count(Config, Retries, NodeId, NumElements) ->
|
assert_connection_count(Config, Retries, NodeId, NumElements) ->
|
||||||
case util:all_connection_pids(Config) of
|
case util:all_connection_pids(Config) of
|
||||||
Pids when
|
Pids
|
||||||
length(Pids) =:= NumElements
|
when length(Pids) =:= NumElements ->
|
||||||
->
|
|
||||||
ok;
|
ok;
|
||||||
_ ->
|
_ ->
|
||||||
timer:sleep(500),
|
timer:sleep(500),
|
||||||
assert_connection_count(Config, Retries - 1, NodeId, NumElements)
|
assert_connection_count(Config, Retries-1, NodeId, NumElements)
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
%%
|
%%
|
||||||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
||||||
|
|
||||||
|
|
||||||
-module(command_SUITE).
|
-module(command_SUITE).
|
||||||
-compile([export_all, nowarn_export_all]).
|
-compile([export_all, nowarn_export_all]).
|
||||||
|
|
||||||
|
@ -16,45 +17,39 @@
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
[
|
||||||
{group, non_parallel_tests}
|
{group, non_parallel_tests}
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[
|
[
|
||||||
{non_parallel_tests, [], [
|
{non_parallel_tests, [], [
|
||||||
merge_defaults,
|
merge_defaults,
|
||||||
run
|
run
|
||||||
]}
|
]}
|
||||||
].
|
].
|
||||||
|
|
||||||
suite() ->
|
suite() ->
|
||||||
[
|
[
|
||||||
{timetrap, {minutes, 3}}
|
{timetrap, {minutes, 3}}
|
||||||
].
|
].
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:log_environment(),
|
rabbit_ct_helpers:log_environment(),
|
||||||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||||
{rmq_nodename_suffix, ?MODULE},
|
{rmq_nodename_suffix, ?MODULE},
|
||||||
{rmq_extra_tcp_ports, [
|
{rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
|
||||||
tcp_port_mqtt_extra,
|
tcp_port_mqtt_tls_extra]},
|
||||||
tcp_port_mqtt_tls_extra
|
|
||||||
]},
|
|
||||||
{rmq_nodes_clustered, true},
|
{rmq_nodes_clustered, true},
|
||||||
{rmq_nodes_count, 3}
|
{rmq_nodes_count, 3}
|
||||||
]),
|
]),
|
||||||
rabbit_ct_helpers:run_setup_steps(
|
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||||
Config1,
|
rabbit_ct_broker_helpers:setup_steps() ++
|
||||||
rabbit_ct_broker_helpers:setup_steps() ++
|
rabbit_ct_client_helpers:setup_steps()).
|
||||||
rabbit_ct_client_helpers:setup_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
end_per_suite(Config) ->
|
end_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:run_teardown_steps(
|
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||||
Config,
|
rabbit_ct_client_helpers:teardown_steps() ++
|
||||||
rabbit_ct_client_helpers:teardown_steps() ++
|
rabbit_ct_broker_helpers:teardown_steps()).
|
||||||
rabbit_ct_broker_helpers:teardown_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
init_per_group(_, Config) ->
|
init_per_group(_, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
@ -78,6 +73,7 @@ merge_defaults(_Config) ->
|
||||||
{[<<"other_key">>], #{verbose := false}} =
|
{[<<"other_key">>], #{verbose := false}} =
|
||||||
?COMMAND:merge_defaults([<<"other_key">>], #{verbose => false}).
|
?COMMAND:merge_defaults([<<"other_key">>], #{verbose => false}).
|
||||||
|
|
||||||
|
|
||||||
run(Config) ->
|
run(Config) ->
|
||||||
Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
|
Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
|
||||||
Opts = #{node => Node, timeout => 10_000, verbose => false},
|
Opts = #{node => Node, timeout => 10_000, verbose => false},
|
||||||
|
@ -95,27 +91,18 @@ run(Config) ->
|
||||||
C2 = connect(<<"simpleClient1">>, Config, [{ack_timeout, 1}]),
|
C2 = connect(<<"simpleClient1">>, Config, [{ack_timeout, 1}]),
|
||||||
timer:sleep(200),
|
timer:sleep(200),
|
||||||
|
|
||||||
[
|
[[{client_id, <<"simpleClient">>}, {user, <<"guest">>}],
|
||||||
[{client_id, <<"simpleClient">>}, {user, <<"guest">>}],
|
[{client_id, <<"simpleClient1">>}, {user, <<"guest">>}]] =
|
||||||
[{client_id, <<"simpleClient1">>}, {user, <<"guest">>}]
|
|
||||||
] =
|
|
||||||
lists:sort(
|
lists:sort(
|
||||||
'Elixir.Enum':to_list(
|
'Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>, <<"user">>],
|
||||||
?COMMAND:run(
|
Opts))),
|
||||||
[<<"client_id">>, <<"user">>],
|
|
||||||
Opts
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
|
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
|
||||||
start_amqp_connection(network, Node, Port),
|
start_amqp_connection(network, Node, Port),
|
||||||
|
|
||||||
%% There are still just two MQTT connections
|
%% There are still just two MQTT connections
|
||||||
[
|
[[{client_id, <<"simpleClient">>}],
|
||||||
[{client_id, <<"simpleClient">>}],
|
[{client_id, <<"simpleClient1">>}]] =
|
||||||
[{client_id, <<"simpleClient1">>}]
|
|
||||||
] =
|
|
||||||
lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts))),
|
lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts))),
|
||||||
|
|
||||||
start_amqp_connection(direct, Node, Port),
|
start_amqp_connection(direct, Node, Port),
|
||||||
|
@ -123,19 +110,14 @@ run(Config) ->
|
||||||
|
|
||||||
%% Still two MQTT connections
|
%% Still two MQTT connections
|
||||||
?assertEqual(
|
?assertEqual(
|
||||||
[
|
[[{client_id, <<"simpleClient">>}],
|
||||||
[{client_id, <<"simpleClient">>}],
|
[{client_id, <<"simpleClient1">>}]],
|
||||||
[{client_id, <<"simpleClient1">>}]
|
lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts)))),
|
||||||
],
|
|
||||||
lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts)))
|
|
||||||
),
|
|
||||||
|
|
||||||
%% Verbose returns all keys
|
%% Verbose returns all keys
|
||||||
AllKeys = lists:map(fun(I) -> atom_to_binary(I) end, ?INFO_ITEMS),
|
AllKeys = lists:map(fun(I) -> atom_to_binary(I) end, ?INFO_ITEMS),
|
||||||
[AllInfos1Con1, _AllInfos1Con2] = 'Elixir.Enum':to_list(?COMMAND:run(AllKeys, Opts)),
|
[AllInfos1Con1, _AllInfos1Con2] = 'Elixir.Enum':to_list(?COMMAND:run(AllKeys, Opts)),
|
||||||
[AllInfos2Con1, _AllInfos2Con2] = 'Elixir.Enum':to_list(
|
[AllInfos2Con1, _AllInfos2Con2] = 'Elixir.Enum':to_list(?COMMAND:run([], Opts#{verbose => true})),
|
||||||
?COMMAND:run([], Opts#{verbose => true})
|
|
||||||
),
|
|
||||||
|
|
||||||
%% Keys are INFO_ITEMS
|
%% Keys are INFO_ITEMS
|
||||||
InfoItemsSorted = lists:sort(?INFO_ITEMS),
|
InfoItemsSorted = lists:sort(?INFO_ITEMS),
|
||||||
|
|
|
@ -5,10 +5,8 @@
|
||||||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
||||||
|
|
||||||
-module(config_SUITE).
|
-module(config_SUITE).
|
||||||
-compile([
|
-compile([export_all,
|
||||||
export_all,
|
nowarn_export_all]).
|
||||||
nowarn_export_all
|
|
||||||
]).
|
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
@ -16,16 +14,17 @@
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
[
|
||||||
{group, mnesia}
|
{group, mnesia}
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[
|
[
|
||||||
{mnesia, [shuffle], [
|
{mnesia, [shuffle],
|
||||||
rabbitmq_default,
|
[
|
||||||
environment_set,
|
rabbitmq_default,
|
||||||
flag_set
|
environment_set,
|
||||||
]}
|
flag_set
|
||||||
|
]}
|
||||||
].
|
].
|
||||||
|
|
||||||
suite() ->
|
suite() ->
|
||||||
|
@ -46,12 +45,8 @@ init_per_testcase(rabbitmq_default = Test, Config) ->
|
||||||
init_per_testcase0(Test, Config);
|
init_per_testcase0(Test, Config);
|
||||||
init_per_testcase(environment_set = Test, Config0) ->
|
init_per_testcase(environment_set = Test, Config0) ->
|
||||||
Config = rabbit_ct_helpers:merge_app_env(
|
Config = rabbit_ct_helpers:merge_app_env(
|
||||||
Config0,
|
Config0, {mnesia, [{dump_log_write_threshold, 25000},
|
||||||
{mnesia, [
|
{dump_log_time_threshold, 60000}]}),
|
||||||
{dump_log_write_threshold, 25000},
|
|
||||||
{dump_log_time_threshold, 60000}
|
|
||||||
]}
|
|
||||||
),
|
|
||||||
init_per_testcase0(Test, Config);
|
init_per_testcase0(Test, Config);
|
||||||
init_per_testcase(flag_set = Test, Config0) ->
|
init_per_testcase(flag_set = Test, Config0) ->
|
||||||
Config = [{additional_erl_args, "-mnesia dump_log_write_threshold 15000"} | Config0],
|
Config = [{additional_erl_args, "-mnesia dump_log_write_threshold 15000"} | Config0],
|
||||||
|
@ -60,19 +55,17 @@ init_per_testcase(flag_set = Test, Config0) ->
|
||||||
init_per_testcase0(Testcase, Config0) ->
|
init_per_testcase0(Testcase, Config0) ->
|
||||||
Config1 = rabbit_ct_helpers:set_config(Config0, {rmq_nodename_suffix, Testcase}),
|
Config1 = rabbit_ct_helpers:set_config(Config0, {rmq_nodename_suffix, Testcase}),
|
||||||
Config = rabbit_ct_helpers:run_steps(
|
Config = rabbit_ct_helpers:run_steps(
|
||||||
Config1,
|
Config1,
|
||||||
rabbit_ct_broker_helpers:setup_steps() ++
|
rabbit_ct_broker_helpers:setup_steps() ++
|
||||||
rabbit_ct_client_helpers:setup_steps()
|
rabbit_ct_client_helpers:setup_steps()),
|
||||||
),
|
|
||||||
rabbit_ct_helpers:testcase_started(Config, Testcase).
|
rabbit_ct_helpers:testcase_started(Config, Testcase).
|
||||||
|
|
||||||
end_per_testcase(Testcase, Config0) ->
|
end_per_testcase(Testcase, Config0) ->
|
||||||
Config = rabbit_ct_helpers:testcase_finished(Config0, Testcase),
|
Config = rabbit_ct_helpers:testcase_finished(Config0, Testcase),
|
||||||
rabbit_ct_helpers:run_teardown_steps(
|
rabbit_ct_helpers:run_teardown_steps(
|
||||||
Config,
|
Config,
|
||||||
rabbit_ct_client_helpers:teardown_steps() ++
|
rabbit_ct_client_helpers:teardown_steps() ++
|
||||||
rabbit_ct_broker_helpers:teardown_steps()
|
rabbit_ct_broker_helpers:teardown_steps()).
|
||||||
).
|
|
||||||
|
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
%% Testsuite cases
|
%% Testsuite cases
|
||||||
|
@ -81,33 +74,21 @@ end_per_testcase(Testcase, Config0) ->
|
||||||
%% The MQTT plugin expects Mnesia dump_log_write_threshold to be increased
|
%% The MQTT plugin expects Mnesia dump_log_write_threshold to be increased
|
||||||
%% from 1000 (Mnesia default) to 5000 (RabbitMQ default).
|
%% from 1000 (Mnesia default) to 5000 (RabbitMQ default).
|
||||||
rabbitmq_default(Config) ->
|
rabbitmq_default(Config) ->
|
||||||
?assertEqual(
|
?assertEqual(5_000,
|
||||||
5_000,
|
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])),
|
||||||
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])
|
?assertEqual(90_000,
|
||||||
),
|
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])).
|
||||||
?assertEqual(
|
|
||||||
90_000,
|
|
||||||
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
|
|
||||||
).
|
|
||||||
|
|
||||||
%% User configured setting in advanced.config should be respected.
|
%% User configured setting in advanced.config should be respected.
|
||||||
environment_set(Config) ->
|
environment_set(Config) ->
|
||||||
?assertEqual(
|
?assertEqual(25_000,
|
||||||
25_000,
|
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])),
|
||||||
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])
|
?assertEqual(60_000,
|
||||||
),
|
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])).
|
||||||
?assertEqual(
|
|
||||||
60_000,
|
|
||||||
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
|
|
||||||
).
|
|
||||||
|
|
||||||
%% User configured setting in RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS should be respected.
|
%% User configured setting in RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS should be respected.
|
||||||
flag_set(Config) ->
|
flag_set(Config) ->
|
||||||
?assertEqual(
|
?assertEqual(15_000,
|
||||||
15_000,
|
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])),
|
||||||
rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])
|
?assertEqual(90_000,
|
||||||
),
|
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])).
|
||||||
?assertEqual(
|
|
||||||
90_000,
|
|
||||||
rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
|
|
||||||
).
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ init_per_suite(Config) ->
|
||||||
Config1 = rabbit_ct_helpers:run_setup_steps(Config),
|
Config1 = rabbit_ct_helpers:run_setup_steps(Config),
|
||||||
rabbit_ct_config_schema:init_schemas(rabbitmq_mqtt, Config1).
|
rabbit_ct_config_schema:init_schemas(rabbitmq_mqtt, Config1).
|
||||||
|
|
||||||
|
|
||||||
end_per_suite(Config) ->
|
end_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:run_teardown_steps(Config).
|
rabbit_ct_helpers:run_teardown_steps(Config).
|
||||||
|
|
||||||
|
@ -30,19 +31,15 @@ init_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||||
{rmq_nodename_suffix, Testcase}
|
{rmq_nodename_suffix, Testcase}
|
||||||
]),
|
]),
|
||||||
rabbit_ct_helpers:run_steps(
|
rabbit_ct_helpers:run_steps(Config1,
|
||||||
Config1,
|
rabbit_ct_broker_helpers:setup_steps() ++
|
||||||
rabbit_ct_broker_helpers:setup_steps() ++
|
rabbit_ct_client_helpers:setup_steps()).
|
||||||
rabbit_ct_client_helpers:setup_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
end_per_testcase(Testcase, Config) ->
|
end_per_testcase(Testcase, Config) ->
|
||||||
Config1 = rabbit_ct_helpers:run_steps(
|
Config1 = rabbit_ct_helpers:run_steps(Config,
|
||||||
Config,
|
rabbit_ct_client_helpers:teardown_steps() ++
|
||||||
rabbit_ct_client_helpers:teardown_steps() ++
|
rabbit_ct_broker_helpers:teardown_steps()),
|
||||||
rabbit_ct_broker_helpers:teardown_steps()
|
|
||||||
),
|
|
||||||
rabbit_ct_helpers:testcase_finished(Config1, Testcase).
|
rabbit_ct_helpers:testcase_finished(Config1, Testcase).
|
||||||
|
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
|
@ -50,13 +47,9 @@ end_per_testcase(Testcase, Config) ->
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
|
|
||||||
run_snippets(Config) ->
|
run_snippets(Config) ->
|
||||||
ok = rabbit_ct_broker_helpers:rpc(
|
ok = rabbit_ct_broker_helpers:rpc(Config, 0,
|
||||||
Config,
|
?MODULE, run_snippets1, [Config]).
|
||||||
0,
|
|
||||||
?MODULE,
|
|
||||||
run_snippets1,
|
|
||||||
[Config]
|
|
||||||
).
|
|
||||||
|
|
||||||
run_snippets1(Config) ->
|
run_snippets1(Config) ->
|
||||||
rabbit_ct_config_schema:run_snippets(Config).
|
rabbit_ct_config_schema:run_snippets(Config).
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,10 @@
|
||||||
init(_) ->
|
init(_) ->
|
||||||
{ok, ?INIT_STATE}.
|
{ok, ?INIT_STATE}.
|
||||||
|
|
||||||
handle_event(#event{type = T}, State) when
|
handle_event(#event{type = T}, State)
|
||||||
T =:= node_stats orelse
|
when T =:= node_stats orelse
|
||||||
T =:= node_node_stats orelse
|
T =:= node_node_stats orelse
|
||||||
T =:= node_node_deleted
|
T =:= node_node_deleted ->
|
||||||
->
|
|
||||||
{ok, State};
|
{ok, State};
|
||||||
handle_event(Event, State) ->
|
handle_event(Event, State) ->
|
||||||
{ok, [Event | State]}.
|
{ok, [Event | State]}.
|
||||||
|
|
|
@ -13,31 +13,27 @@
|
||||||
|
|
||||||
-import(rabbit_ct_broker_helpers, [rpc/5]).
|
-import(rabbit_ct_broker_helpers, [rpc/5]).
|
||||||
-import(rabbit_ct_helpers, [eventually/1]).
|
-import(rabbit_ct_helpers, [eventually/1]).
|
||||||
-import(util, [
|
-import(util, [expect_publishes/3,
|
||||||
expect_publishes/3,
|
get_global_counters/4,
|
||||||
get_global_counters/4,
|
connect/2,
|
||||||
connect/2,
|
connect/4]).
|
||||||
connect/4
|
|
||||||
]).
|
|
||||||
|
|
||||||
-define(PROTO_VER, v4).
|
-define(PROTO_VER, v4).
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
[
|
||||||
{group, cluster_size_3}
|
{group, cluster_size_3}
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[
|
[
|
||||||
{cluster_size_3, [], [
|
{cluster_size_3, [], [delete_ra_cluster_mqtt_node,
|
||||||
delete_ra_cluster_mqtt_node,
|
rabbit_mqtt_qos0_queue]}
|
||||||
rabbit_mqtt_qos0_queue
|
|
||||||
]}
|
|
||||||
].
|
].
|
||||||
|
|
||||||
suite() ->
|
suite() ->
|
||||||
[
|
[
|
||||||
{timetrap, {minutes, 2}}
|
{timetrap, {minutes, 2}}
|
||||||
].
|
].
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
|
@ -48,36 +44,26 @@ end_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:run_teardown_steps(Config).
|
rabbit_ct_helpers:run_teardown_steps(Config).
|
||||||
|
|
||||||
init_per_group(Group = cluster_size_3, Config0) ->
|
init_per_group(Group = cluster_size_3, Config0) ->
|
||||||
Config1 = rabbit_ct_helpers:set_config(Config0, [
|
Config1 = rabbit_ct_helpers:set_config(Config0, [{rmq_nodes_count, 3},
|
||||||
{rmq_nodes_count, 3},
|
{rmq_nodename_suffix, Group}]),
|
||||||
{rmq_nodename_suffix, Group}
|
|
||||||
]),
|
|
||||||
Config = rabbit_ct_helpers:merge_app_env(
|
Config = rabbit_ct_helpers:merge_app_env(
|
||||||
Config1, {rabbit, [{forced_feature_flags_on_init, []}]}
|
Config1, {rabbit, [{forced_feature_flags_on_init, []}]}),
|
||||||
),
|
rabbit_ct_helpers:run_steps(Config,
|
||||||
rabbit_ct_helpers:run_steps(
|
rabbit_ct_broker_helpers:setup_steps() ++
|
||||||
Config,
|
rabbit_ct_client_helpers:setup_steps()).
|
||||||
rabbit_ct_broker_helpers:setup_steps() ++
|
|
||||||
rabbit_ct_client_helpers:setup_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
end_per_group(_Group, Config) ->
|
end_per_group(_Group, Config) ->
|
||||||
rabbit_ct_helpers:run_steps(
|
rabbit_ct_helpers:run_steps(Config,
|
||||||
Config,
|
rabbit_ct_client_helpers:teardown_steps() ++
|
||||||
rabbit_ct_client_helpers:teardown_steps() ++
|
rabbit_ct_broker_helpers:teardown_steps()).
|
||||||
rabbit_ct_broker_helpers:teardown_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
init_per_testcase(TestCase, Config) ->
|
init_per_testcase(TestCase, Config) ->
|
||||||
case rabbit_ct_broker_helpers:is_feature_flag_supported(Config, TestCase) of
|
case rabbit_ct_broker_helpers:is_feature_flag_supported(Config, TestCase) of
|
||||||
true ->
|
true ->
|
||||||
Config;
|
Config;
|
||||||
false ->
|
false ->
|
||||||
{skip,
|
{skip, io_lib:format("feature flag ~s is unsupported",
|
||||||
io_lib:format(
|
[TestCase])}
|
||||||
"feature flag ~s is unsupported",
|
|
||||||
[TestCase]
|
|
||||||
)}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
end_per_testcase(_TestCase, Config) ->
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
@ -90,27 +76,16 @@ delete_ra_cluster_mqtt_node(Config) ->
|
||||||
%% old client ID tracking works
|
%% old client ID tracking works
|
||||||
?assertEqual(1, length(util:all_connection_pids(Config))),
|
?assertEqual(1, length(util:all_connection_pids(Config))),
|
||||||
%% Ra processes are alive
|
%% Ra processes are alive
|
||||||
?assert(
|
?assert(lists:all(fun erlang:is_pid/1,
|
||||||
lists:all(
|
rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node]))),
|
||||||
fun erlang:is_pid/1,
|
|
||||||
rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
?assertEqual(
|
?assertEqual(ok,
|
||||||
ok,
|
rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)),
|
||||||
rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)
|
|
||||||
),
|
|
||||||
|
|
||||||
%% Ra processes should be gone
|
%% Ra processes should be gone
|
||||||
rabbit_ct_helpers:eventually(
|
rabbit_ct_helpers:eventually(
|
||||||
?_assert(
|
?_assert(lists:all(fun(Pid) -> Pid =:= undefined end,
|
||||||
lists:all(
|
rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])))),
|
||||||
fun(Pid) -> Pid =:= undefined end,
|
|
||||||
rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
%% new client ID tracking works
|
%% new client ID tracking works
|
||||||
?assertEqual(1, length(util:all_connection_pids(Config))),
|
?assertEqual(1, length(util:all_connection_pids(Config))),
|
||||||
?assert(erlang:is_process_alive(C)),
|
?assert(erlang:is_process_alive(C)),
|
||||||
|
@ -124,30 +99,20 @@ rabbit_mqtt_qos0_queue(Config) ->
|
||||||
{ok, _, [0]} = emqtt:subscribe(C1, Topic, qos0),
|
{ok, _, [0]} = emqtt:subscribe(C1, Topic, qos0),
|
||||||
ok = emqtt:publish(C1, Topic, Msg, qos0),
|
ok = emqtt:publish(C1, Topic, Msg, qos0),
|
||||||
ok = expect_publishes(C1, Topic, [Msg]),
|
ok = expect_publishes(C1, Topic, [Msg]),
|
||||||
?assertEqual(
|
?assertEqual(1,
|
||||||
1,
|
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))),
|
||||||
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))
|
|
||||||
),
|
|
||||||
|
|
||||||
?assertEqual(
|
?assertEqual(ok,
|
||||||
ok,
|
rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)),
|
||||||
rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)
|
|
||||||
),
|
|
||||||
|
|
||||||
%% Queue type does not chanage for existing connection.
|
%% Queue type does not chanage for existing connection.
|
||||||
?assertEqual(
|
?assertEqual(1,
|
||||||
1,
|
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))),
|
||||||
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))
|
|
||||||
),
|
|
||||||
ok = emqtt:publish(C1, Topic, Msg, qos0),
|
ok = emqtt:publish(C1, Topic, Msg, qos0),
|
||||||
ok = expect_publishes(C1, Topic, [Msg]),
|
ok = expect_publishes(C1, Topic, [Msg]),
|
||||||
?assertMatch(
|
?assertMatch(#{messages_delivered_total := 2,
|
||||||
#{
|
messages_delivered_consume_auto_ack_total := 2},
|
||||||
messages_delivered_total := 2,
|
get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, rabbit_classic_queue}])),
|
||||||
messages_delivered_consume_auto_ack_total := 2
|
|
||||||
},
|
|
||||||
get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, rabbit_classic_queue}])
|
|
||||||
),
|
|
||||||
|
|
||||||
%% Reconnecting with the same client ID will terminate the old connection.
|
%% Reconnecting with the same client ID will terminate the old connection.
|
||||||
true = unlink(C1),
|
true = unlink(C1),
|
||||||
|
@ -155,22 +120,13 @@ rabbit_mqtt_qos0_queue(Config) ->
|
||||||
{ok, _, [0]} = emqtt:subscribe(C2, Topic, qos0),
|
{ok, _, [0]} = emqtt:subscribe(C2, Topic, qos0),
|
||||||
%% This time, we get the new queue type.
|
%% This time, we get the new queue type.
|
||||||
eventually(
|
eventually(
|
||||||
?_assertEqual(
|
?_assertEqual(0,
|
||||||
0,
|
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue])))),
|
||||||
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))
|
?assertEqual(1,
|
||||||
)
|
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [FeatureFlag]))),
|
||||||
),
|
|
||||||
?assertEqual(
|
|
||||||
1,
|
|
||||||
length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [FeatureFlag]))
|
|
||||||
),
|
|
||||||
ok = emqtt:publish(C2, Topic, Msg, qos0),
|
ok = emqtt:publish(C2, Topic, Msg, qos0),
|
||||||
ok = expect_publishes(C2, Topic, [Msg]),
|
ok = expect_publishes(C2, Topic, [Msg]),
|
||||||
?assertMatch(
|
?assertMatch(#{messages_delivered_total := 1,
|
||||||
#{
|
messages_delivered_consume_auto_ack_total := 1},
|
||||||
messages_delivered_total := 1,
|
get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, FeatureFlag}])),
|
||||||
messages_delivered_consume_auto_ack_total := 1
|
|
||||||
},
|
|
||||||
get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, FeatureFlag}])
|
|
||||||
),
|
|
||||||
ok = emqtt:disconnect(C2).
|
ok = emqtt:disconnect(C2).
|
||||||
|
|
|
@ -12,25 +12,24 @@
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
-define(BASE_CONF_MQTT,
|
-define(BASE_CONF_MQTT,
|
||||||
{rabbitmq_mqtt, [
|
{rabbitmq_mqtt, [
|
||||||
{ssl_cert_login, true},
|
{ssl_cert_login, true},
|
||||||
{allow_anonymous, false},
|
{allow_anonymous, false},
|
||||||
{sparkplug, true},
|
{sparkplug, true},
|
||||||
{tcp_listeners, []},
|
{tcp_listeners, []},
|
||||||
{ssl_listeners, []}
|
{ssl_listeners, []}
|
||||||
]}
|
]}).
|
||||||
).
|
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
[
|
||||||
{group, non_parallel_tests}
|
{group, non_parallel_tests}
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[
|
[
|
||||||
{non_parallel_tests, [], [
|
{non_parallel_tests, [], [
|
||||||
java
|
java
|
||||||
]}
|
]}
|
||||||
].
|
].
|
||||||
|
|
||||||
suite() ->
|
suite() ->
|
||||||
|
@ -53,20 +52,16 @@ init_per_suite(Config) ->
|
||||||
{rmq_certspwd, "bunnychow"},
|
{rmq_certspwd, "bunnychow"},
|
||||||
{rmq_nodes_clustered, true},
|
{rmq_nodes_clustered, true},
|
||||||
{rmq_nodes_count, 3}
|
{rmq_nodes_count, 3}
|
||||||
]),
|
]),
|
||||||
rabbit_ct_helpers:run_setup_steps(
|
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||||
Config1,
|
[ fun merge_app_env/1 ] ++
|
||||||
[fun merge_app_env/1] ++
|
rabbit_ct_broker_helpers:setup_steps() ++
|
||||||
rabbit_ct_broker_helpers:setup_steps() ++
|
rabbit_ct_client_helpers:setup_steps()).
|
||||||
rabbit_ct_client_helpers:setup_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
end_per_suite(Config) ->
|
end_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:run_teardown_steps(
|
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||||
Config,
|
rabbit_ct_client_helpers:teardown_steps() ++
|
||||||
rabbit_ct_client_helpers:teardown_steps() ++
|
rabbit_ct_broker_helpers:teardown_steps()).
|
||||||
rabbit_ct_broker_helpers:teardown_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
init_per_group(_, Config) ->
|
init_per_group(_, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
@ -79,38 +74,25 @@ init_per_testcase(Testcase, Config) ->
|
||||||
CertFile = filename:join([CertsDir, "client", "cert.pem"]),
|
CertFile = filename:join([CertsDir, "client", "cert.pem"]),
|
||||||
{ok, CertBin} = file:read_file(CertFile),
|
{ok, CertBin} = file:read_file(CertFile),
|
||||||
[{'Certificate', Cert, not_encrypted}] = public_key:pem_decode(CertBin),
|
[{'Certificate', Cert, not_encrypted}] = public_key:pem_decode(CertBin),
|
||||||
UserBin = rabbit_ct_broker_helpers:rpc(
|
UserBin = rabbit_ct_broker_helpers:rpc(Config, 0,
|
||||||
Config,
|
rabbit_ssl,
|
||||||
0,
|
peer_cert_auth_name,
|
||||||
rabbit_ssl,
|
[Cert]),
|
||||||
peer_cert_auth_name,
|
|
||||||
[Cert]
|
|
||||||
),
|
|
||||||
User = binary_to_list(UserBin),
|
User = binary_to_list(UserBin),
|
||||||
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["add_user", User, ""]),
|
{ok,_} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["add_user", User, ""]),
|
||||||
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, [
|
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["set_permissions", "-p", "/", User, ".*", ".*", ".*"]),
|
||||||
"set_permissions", "-p", "/", User, ".*", ".*", ".*"
|
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0,
|
||||||
]),
|
["set_topic_permissions", "-p", "/", "guest", "amq.topic",
|
||||||
{ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(
|
|
||||||
Config,
|
|
||||||
0,
|
|
||||||
[
|
|
||||||
"set_topic_permissions",
|
|
||||||
"-p",
|
|
||||||
"/",
|
|
||||||
"guest",
|
|
||||||
"amq.topic",
|
|
||||||
% Write permission
|
% Write permission
|
||||||
"test-topic|test-retained-topic|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+",
|
"test-topic|test-retained-topic|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+",
|
||||||
% Read permission
|
% Read permission
|
||||||
"test-topic|test-retained-topic|last-will|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+"
|
"test-topic|test-retained-topic|last-will|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+"]),
|
||||||
]
|
|
||||||
),
|
|
||||||
rabbit_ct_helpers:testcase_started(Config, Testcase).
|
rabbit_ct_helpers:testcase_started(Config, Testcase).
|
||||||
|
|
||||||
end_per_testcase(Testcase, Config) ->
|
end_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
||||||
|
|
||||||
|
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
%% Testsuite cases
|
%% Testsuite cases
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
|
@ -140,5 +122,5 @@ q(P, [K | Rem]) ->
|
||||||
undefined -> undefined;
|
undefined -> undefined;
|
||||||
V -> q(V, Rem)
|
V -> q(V, Rem)
|
||||||
end;
|
end;
|
||||||
q(P, []) ->
|
q(P, []) -> {ok, P}.
|
||||||
{ok, P}.
|
|
||||||
|
|
|
@ -12,19 +12,20 @@
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
[
|
||||||
{group, tests}
|
{group, tests}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
|
||||||
all_tests() ->
|
all_tests() ->
|
||||||
[
|
[
|
||||||
basics,
|
basics,
|
||||||
machine_upgrade,
|
machine_upgrade,
|
||||||
many_downs
|
many_downs
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[
|
[
|
||||||
{tests, [], all_tests()}
|
{tests, [], all_tests()}
|
||||||
].
|
].
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
|
@ -52,16 +53,13 @@ end_per_testcase(_TestCase, _Config) ->
|
||||||
basics(_Config) ->
|
basics(_Config) ->
|
||||||
S0 = mqtt_machine:init(#{}),
|
S0 = mqtt_machine:init(#{}),
|
||||||
ClientId = <<"id1">>,
|
ClientId = <<"id1">>,
|
||||||
OthPid = spawn(fun() -> ok end),
|
OthPid = spawn(fun () -> ok end),
|
||||||
{S1, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, self()}, S0),
|
{S1, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, self()}, S0),
|
||||||
?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 1, S1),
|
?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 1, S1),
|
||||||
?assertMatch(#machine_state{pids = Pids} when map_size(Pids) == 1, S1),
|
?assertMatch(#machine_state{pids = Pids} when map_size(Pids) == 1, S1),
|
||||||
{S2, ok, _} = mqtt_machine:apply(meta(2), {register, ClientId, OthPid}, S1),
|
{S2, ok, _} = mqtt_machine:apply(meta(2), {register, ClientId, OthPid}, S1),
|
||||||
?assertMatch(
|
?assertMatch(#machine_state{client_ids = #{ClientId := OthPid} = Ids}
|
||||||
#machine_state{client_ids = #{ClientId := OthPid} = Ids} when
|
when map_size(Ids) == 1, S2),
|
||||||
map_size(Ids) == 1,
|
|
||||||
S2
|
|
||||||
),
|
|
||||||
{S3, ok, _} = mqtt_machine:apply(meta(3), {down, OthPid, noproc}, S2),
|
{S3, ok, _} = mqtt_machine:apply(meta(3), {down, OthPid, noproc}, S2),
|
||||||
?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 0, S3),
|
?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 0, S3),
|
||||||
{S4, ok, _} = mqtt_machine:apply(meta(3), {unregister, ClientId, OthPid}, S2),
|
{S4, ok, _} = mqtt_machine:apply(meta(3), {unregister, ClientId, OthPid}, S2),
|
||||||
|
@ -76,63 +74,41 @@ machine_upgrade(_Config) ->
|
||||||
{S1, ok, _} = mqtt_machine_v0:apply(meta(1), {register, ClientId, self()}, S0),
|
{S1, ok, _} = mqtt_machine_v0:apply(meta(1), {register, ClientId, self()}, S0),
|
||||||
?assertMatch({machine_state, Ids} when map_size(Ids) == 1, S1),
|
?assertMatch({machine_state, Ids} when map_size(Ids) == 1, S1),
|
||||||
{S2, ok, _} = mqtt_machine:apply(meta(2), {machine_version, 0, 1}, S1),
|
{S2, ok, _} = mqtt_machine:apply(meta(2), {machine_version, 0, 1}, S1),
|
||||||
?assertMatch(
|
?assertMatch(#machine_state{client_ids = #{ClientId := Self},
|
||||||
#machine_state{
|
pids = #{Self := [ClientId]} = Pids}
|
||||||
client_ids = #{ClientId := Self},
|
when map_size(Pids) == 1, S2),
|
||||||
pids = #{Self := [ClientId]} = Pids
|
|
||||||
} when
|
|
||||||
map_size(Pids) == 1,
|
|
||||||
S2
|
|
||||||
),
|
|
||||||
{S3, ok, _} = mqtt_machine:apply(meta(3), {down, self(), noproc}, S2),
|
{S3, ok, _} = mqtt_machine:apply(meta(3), {down, self(), noproc}, S2),
|
||||||
?assertMatch(
|
?assertMatch(#machine_state{client_ids = Ids,
|
||||||
#machine_state{
|
pids = Pids}
|
||||||
client_ids = Ids,
|
when map_size(Ids) == 0 andalso map_size(Pids) == 0, S3),
|
||||||
pids = Pids
|
|
||||||
} when
|
|
||||||
map_size(Ids) == 0 andalso map_size(Pids) == 0,
|
|
||||||
S3
|
|
||||||
),
|
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
many_downs(_Config) ->
|
many_downs(_Config) ->
|
||||||
S0 = mqtt_machine:init(#{}),
|
S0 = mqtt_machine:init(#{}),
|
||||||
Clients = [
|
Clients = [{list_to_binary(integer_to_list(I)), spawn(fun() -> ok end)}
|
||||||
{list_to_binary(integer_to_list(I)), spawn(fun() -> ok end)}
|
|| I <- lists:seq(1, 10000)],
|
||||||
|| I <- lists:seq(1, 10000)
|
|
||||||
],
|
|
||||||
S1 = lists:foldl(
|
S1 = lists:foldl(
|
||||||
fun({ClientId, Pid}, Acc0) ->
|
fun ({ClientId, Pid}, Acc0) ->
|
||||||
{Acc, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, Pid}, Acc0),
|
{Acc, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, Pid}, Acc0),
|
||||||
Acc
|
Acc
|
||||||
end,
|
end, S0, Clients),
|
||||||
S0,
|
|
||||||
Clients
|
|
||||||
),
|
|
||||||
_ = lists:foldl(
|
_ = lists:foldl(
|
||||||
fun({_ClientId, Pid}, Acc0) ->
|
fun ({_ClientId, Pid}, Acc0) ->
|
||||||
{Acc, ok, _} = mqtt_machine:apply(meta(1), {down, Pid, noproc}, Acc0),
|
{Acc, ok, _} = mqtt_machine:apply(meta(1), {down, Pid, noproc}, Acc0),
|
||||||
Acc
|
Acc
|
||||||
end,
|
end, S1, Clients),
|
||||||
S1,
|
|
||||||
Clients
|
|
||||||
),
|
|
||||||
_ = lists:foldl(
|
_ = lists:foldl(
|
||||||
fun({ClientId, Pid}, Acc0) ->
|
fun ({ClientId, Pid}, Acc0) ->
|
||||||
{Acc, ok, _} = mqtt_machine:apply(meta(1), {unregister, ClientId, Pid}, Acc0),
|
{Acc, ok, _} = mqtt_machine:apply(meta(1), {unregister, ClientId,
|
||||||
Acc
|
Pid}, Acc0),
|
||||||
end,
|
Acc
|
||||||
S0,
|
end, S0, Clients),
|
||||||
Clients
|
|
||||||
),
|
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
%% Utility
|
%% Utility
|
||||||
|
|
||||||
meta(Idx) ->
|
meta(Idx) ->
|
||||||
#{
|
#{index => Idx,
|
||||||
index => Idx,
|
term => 1,
|
||||||
term => 1,
|
ts => erlang:system_time(millisecond)}.
|
||||||
ts => erlang:system_time(millisecond)
|
|
||||||
}.
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
%%
|
%%
|
||||||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
||||||
|
|
||||||
|
|
||||||
-module(processor_SUITE).
|
-module(processor_SUITE).
|
||||||
-compile([export_all, nowarn_export_all]).
|
-compile([export_all, nowarn_export_all]).
|
||||||
|
|
||||||
|
@ -13,17 +14,17 @@
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
[
|
||||||
{group, non_parallel_tests}
|
{group, non_parallel_tests}
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[
|
[
|
||||||
{non_parallel_tests, [], [
|
{non_parallel_tests, [], [
|
||||||
ignores_colons_in_username_if_option_set,
|
ignores_colons_in_username_if_option_set,
|
||||||
interprets_colons_in_username_if_option_not_set,
|
interprets_colons_in_username_if_option_not_set,
|
||||||
get_vhosts_from_global_runtime_parameter,
|
get_vhosts_from_global_runtime_parameter,
|
||||||
get_vhost
|
get_vhost
|
||||||
]}
|
]}
|
||||||
].
|
].
|
||||||
|
|
||||||
suite() ->
|
suite() ->
|
||||||
|
@ -41,50 +42,35 @@ init_per_testcase(get_vhost, Config) ->
|
||||||
mnesia:start(),
|
mnesia:start(),
|
||||||
mnesia:create_table(rabbit_runtime_parameters, [
|
mnesia:create_table(rabbit_runtime_parameters, [
|
||||||
{attributes, record_info(fields, runtime_parameters)},
|
{attributes, record_info(fields, runtime_parameters)},
|
||||||
{record_name, runtime_parameters}
|
{record_name, runtime_parameters}]),
|
||||||
]),
|
|
||||||
Config;
|
Config;
|
||||||
init_per_testcase(_, Config) ->
|
init_per_testcase(_, Config) -> Config.
|
||||||
Config.
|
|
||||||
end_per_testcase(get_vhost, Config) ->
|
end_per_testcase(get_vhost, Config) ->
|
||||||
mnesia:stop(),
|
mnesia:stop(),
|
||||||
Config;
|
Config;
|
||||||
end_per_testcase(_, Config) ->
|
end_per_testcase(_, Config) -> Config.
|
||||||
Config.
|
|
||||||
|
|
||||||
ignore_colons(B) -> application:set_env(rabbitmq_mqtt, ignore_colons_in_username, B).
|
ignore_colons(B) -> application:set_env(rabbitmq_mqtt, ignore_colons_in_username, B).
|
||||||
|
|
||||||
ignores_colons_in_username_if_option_set(_Config) ->
|
ignores_colons_in_username_if_option_set(_Config) ->
|
||||||
ignore_colons(true),
|
ignore_colons(true),
|
||||||
?assertEqual(
|
?assertEqual({rabbit_mqtt_util:env(vhost), <<"a:b:c">>},
|
||||||
{rabbit_mqtt_util:env(vhost), <<"a:b:c">>},
|
rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)).
|
||||||
rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)
|
|
||||||
).
|
|
||||||
|
|
||||||
interprets_colons_in_username_if_option_not_set(_Config) ->
|
interprets_colons_in_username_if_option_not_set(_Config) ->
|
||||||
ignore_colons(false),
|
ignore_colons(false),
|
||||||
?assertEqual(
|
?assertEqual({<<"a:b">>, <<"c">>},
|
||||||
{<<"a:b">>, <<"c">>},
|
rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)).
|
||||||
rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)
|
|
||||||
).
|
|
||||||
|
|
||||||
get_vhosts_from_global_runtime_parameter(_Config) ->
|
get_vhosts_from_global_runtime_parameter(_Config) ->
|
||||||
MappingParameter = [
|
MappingParameter = [
|
||||||
{<<"O=client,CN=dummy1">>, <<"vhost1">>},
|
{<<"O=client,CN=dummy1">>, <<"vhost1">>},
|
||||||
{<<"O=client,CN=dummy2">>, <<"vhost2">>}
|
{<<"O=client,CN=dummy2">>, <<"vhost2">>}
|
||||||
],
|
],
|
||||||
<<"vhost1">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(
|
<<"vhost1">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy1">>, MappingParameter),
|
||||||
<<"O=client,CN=dummy1">>, MappingParameter
|
<<"vhost2">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy2">>, MappingParameter),
|
||||||
),
|
undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy3">>, MappingParameter),
|
||||||
<<"vhost2">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(
|
undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy3">>, not_found).
|
||||||
<<"O=client,CN=dummy2">>, MappingParameter
|
|
||||||
),
|
|
||||||
undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(
|
|
||||||
<<"O=client,CN=dummy3">>, MappingParameter
|
|
||||||
),
|
|
||||||
undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(
|
|
||||||
<<"O=client,CN=dummy3">>, not_found
|
|
||||||
).
|
|
||||||
|
|
||||||
get_vhost(_Config) ->
|
get_vhost(_Config) ->
|
||||||
clear_vhost_global_parameters(),
|
clear_vhost_global_parameters(),
|
||||||
|
@ -97,35 +83,27 @@ get_vhost(_Config) ->
|
||||||
|
|
||||||
%% not a certificate user, no cert/vhost mapping, vhost in user
|
%% not a certificate user, no cert/vhost mapping, vhost in user
|
||||||
%% should use vhost in user
|
%% should use vhost in user
|
||||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"somevhost:guest">>, none, 1883),
|
||||||
<<"somevhost:guest">>, none, 1883
|
|
||||||
),
|
|
||||||
clear_vhost_global_parameters(),
|
clear_vhost_global_parameters(),
|
||||||
|
|
||||||
%% certificate user, no cert/vhost mapping
|
%% certificate user, no cert/vhost mapping
|
||||||
%% should use default vhost
|
%% should use default vhost
|
||||||
{_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
{_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
|
||||||
),
|
|
||||||
clear_vhost_global_parameters(),
|
clear_vhost_global_parameters(),
|
||||||
|
|
||||||
%% certificate user, cert/vhost mapping with global runtime parameter
|
%% certificate user, cert/vhost mapping with global runtime parameter
|
||||||
%% should use mapping
|
%% should use mapping
|
||||||
set_global_parameter(mqtt_default_vhosts, [
|
set_global_parameter(mqtt_default_vhosts, [
|
||||||
{<<"O=client,CN=dummy">>, <<"somevhost">>},
|
{<<"O=client,CN=dummy">>, <<"somevhost">>},
|
||||||
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
||||||
]),
|
]),
|
||||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
|
||||||
),
|
|
||||||
clear_vhost_global_parameters(),
|
clear_vhost_global_parameters(),
|
||||||
|
|
||||||
%% certificate user, cert/vhost mapping with global runtime parameter, but no key for the user
|
%% certificate user, cert/vhost mapping with global runtime parameter, but no key for the user
|
||||||
%% should use default vhost
|
%% should use default vhost
|
||||||
set_global_parameter(mqtt_default_vhosts, [{<<"O=client,CN=otheruser">>, <<"somevhost">>}]),
|
set_global_parameter(mqtt_default_vhosts, [{<<"O=client,CN=otheruser">>, <<"somevhost">>}]),
|
||||||
{_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
{_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
|
||||||
),
|
|
||||||
clear_vhost_global_parameters(),
|
clear_vhost_global_parameters(),
|
||||||
|
|
||||||
%% not a certificate user, port/vhost mapping
|
%% not a certificate user, port/vhost mapping
|
||||||
|
@ -143,9 +121,7 @@ get_vhost(_Config) ->
|
||||||
{<<"1883">>, <<"somevhost">>},
|
{<<"1883">>, <<"somevhost">>},
|
||||||
{<<"1884">>, <<"othervhost">>}
|
{<<"1884">>, <<"othervhost">>}
|
||||||
]),
|
]),
|
||||||
{_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
{_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"vhostinusername:guest">>, none, 1883),
|
||||||
<<"vhostinusername:guest">>, none, 1883
|
|
||||||
),
|
|
||||||
clear_vhost_global_parameters(),
|
clear_vhost_global_parameters(),
|
||||||
|
|
||||||
%% not a certificate user, port/vhost mapping, but no mapping for this port
|
%% not a certificate user, port/vhost mapping, but no mapping for this port
|
||||||
|
@ -162,50 +138,42 @@ get_vhost(_Config) ->
|
||||||
{<<"1883">>, <<"somevhost">>},
|
{<<"1883">>, <<"somevhost">>},
|
||||||
{<<"1884">>, <<"othervhost">>}
|
{<<"1884">>, <<"othervhost">>}
|
||||||
]),
|
]),
|
||||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
|
||||||
),
|
|
||||||
clear_vhost_global_parameters(),
|
clear_vhost_global_parameters(),
|
||||||
|
|
||||||
%% certificate user, port/vhost parameter but no mapping, cert/vhost mapping
|
%% certificate user, port/vhost parameter but no mapping, cert/vhost mapping
|
||||||
%% should use cert/vhost mapping
|
%% should use cert/vhost mapping
|
||||||
set_global_parameter(mqtt_default_vhosts, [
|
set_global_parameter(mqtt_default_vhosts, [
|
||||||
{<<"O=client,CN=dummy">>, <<"somevhost">>},
|
{<<"O=client,CN=dummy">>, <<"somevhost">>},
|
||||||
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
||||||
]),
|
]),
|
||||||
set_global_parameter(mqtt_port_to_vhost_mapping, [
|
set_global_parameter(mqtt_port_to_vhost_mapping, [
|
||||||
{<<"1884">>, <<"othervhost">>}
|
{<<"1884">>, <<"othervhost">>}
|
||||||
]),
|
]),
|
||||||
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
{_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
|
||||||
),
|
|
||||||
clear_vhost_global_parameters(),
|
clear_vhost_global_parameters(),
|
||||||
|
|
||||||
%% certificate user, port/vhost parameter, cert/vhost parameter
|
%% certificate user, port/vhost parameter, cert/vhost parameter
|
||||||
%% cert/vhost parameter takes precedence
|
%% cert/vhost parameter takes precedence
|
||||||
set_global_parameter(mqtt_default_vhosts, [
|
set_global_parameter(mqtt_default_vhosts, [
|
||||||
{<<"O=client,CN=dummy">>, <<"cert-somevhost">>},
|
{<<"O=client,CN=dummy">>, <<"cert-somevhost">>},
|
||||||
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
||||||
]),
|
]),
|
||||||
set_global_parameter(mqtt_port_to_vhost_mapping, [
|
set_global_parameter(mqtt_port_to_vhost_mapping, [
|
||||||
{<<"1883">>, <<"port-vhost">>},
|
{<<"1883">>, <<"port-vhost">>},
|
||||||
{<<"1884">>, <<"othervhost">>}
|
{<<"1884">>, <<"othervhost">>}
|
||||||
]),
|
]),
|
||||||
{_, {<<"cert-somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
{_, {<<"cert-somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||||
<<"guest">>, <<"O=client,CN=dummy">>, 1883
|
|
||||||
),
|
|
||||||
clear_vhost_global_parameters(),
|
clear_vhost_global_parameters(),
|
||||||
|
|
||||||
%% certificate user, no port/vhost or cert/vhost mapping, vhost in username
|
%% certificate user, no port/vhost or cert/vhost mapping, vhost in username
|
||||||
%% should use vhost in username
|
%% should use vhost in username
|
||||||
{_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
|
{_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"vhostinusername:guest">>, <<"O=client,CN=dummy">>, 1883),
|
||||||
<<"vhostinusername:guest">>, <<"O=client,CN=dummy">>, 1883
|
|
||||||
),
|
|
||||||
|
|
||||||
%% not a certificate user, port/vhost parameter, cert/vhost parameter
|
%% not a certificate user, port/vhost parameter, cert/vhost parameter
|
||||||
%% port/vhost mapping is used, as cert/vhost should not be used
|
%% port/vhost mapping is used, as cert/vhost should not be used
|
||||||
set_global_parameter(mqtt_default_vhosts, [
|
set_global_parameter(mqtt_default_vhosts, [
|
||||||
{<<"O=cert">>, <<"cert-somevhost">>},
|
{<<"O=cert">>, <<"cert-somevhost">>},
|
||||||
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
{<<"O=client,CN=otheruser">>, <<"othervhost">>}
|
||||||
]),
|
]),
|
||||||
set_global_parameter(mqtt_port_to_vhost_mapping, [
|
set_global_parameter(mqtt_port_to_vhost_mapping, [
|
||||||
|
@ -217,15 +185,15 @@ get_vhost(_Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
set_global_parameter(Key, Term) ->
|
set_global_parameter(Key, Term) ->
|
||||||
InsertParameterFun = fun() ->
|
InsertParameterFun = fun () ->
|
||||||
mnesia:write(rabbit_runtime_parameters, #runtime_parameters{key = Key, value = Term}, write)
|
mnesia:write(rabbit_runtime_parameters, #runtime_parameters{key = Key, value = Term}, write)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
{atomic, ok} = mnesia:transaction(InsertParameterFun).
|
{atomic, ok} = mnesia:transaction(InsertParameterFun).
|
||||||
|
|
||||||
clear_vhost_global_parameters() ->
|
clear_vhost_global_parameters() ->
|
||||||
DeleteParameterFun = fun() ->
|
DeleteParameterFun = fun () ->
|
||||||
ok = mnesia:delete(rabbit_runtime_parameters, mqtt_default_vhosts, write),
|
ok = mnesia:delete(rabbit_runtime_parameters, mqtt_default_vhosts, write),
|
||||||
ok = mnesia:delete(rabbit_runtime_parameters, mqtt_port_to_vhost_mapping, write)
|
ok = mnesia:delete(rabbit_runtime_parameters, mqtt_port_to_vhost_mapping, write)
|
||||||
end,
|
end,
|
||||||
{atomic, ok} = mnesia:transaction(DeleteParameterFun).
|
{atomic, ok} = mnesia:transaction(DeleteParameterFun).
|
||||||
|
|
|
@ -34,26 +34,21 @@ init_per_suite(Config) ->
|
||||||
{rabbitmq_ct_tls_verify, verify_none}
|
{rabbitmq_ct_tls_verify, verify_none}
|
||||||
]),
|
]),
|
||||||
MqttConfig = mqtt_config(),
|
MqttConfig = mqtt_config(),
|
||||||
rabbit_ct_helpers:run_setup_steps(
|
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||||
Config1,
|
[ fun(Conf) -> merge_app_env(MqttConfig, Conf) end ] ++
|
||||||
[fun(Conf) -> merge_app_env(MqttConfig, Conf) end] ++
|
|
||||||
rabbit_ct_broker_helpers:setup_steps() ++
|
rabbit_ct_broker_helpers:setup_steps() ++
|
||||||
rabbit_ct_client_helpers:setup_steps()
|
rabbit_ct_client_helpers:setup_steps()).
|
||||||
).
|
|
||||||
|
|
||||||
mqtt_config() ->
|
mqtt_config() ->
|
||||||
{rabbitmq_mqtt, [
|
{rabbitmq_mqtt, [
|
||||||
{proxy_protocol, true},
|
{proxy_protocol, true},
|
||||||
{ssl_cert_login, true},
|
{ssl_cert_login, true},
|
||||||
{allow_anonymous, true}
|
{allow_anonymous, true}]}.
|
||||||
]}.
|
|
||||||
|
|
||||||
end_per_suite(Config) ->
|
end_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:run_teardown_steps(
|
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||||
Config,
|
|
||||||
rabbit_ct_client_helpers:teardown_steps() ++
|
rabbit_ct_client_helpers:teardown_steps() ++
|
||||||
rabbit_ct_broker_helpers:teardown_steps()
|
rabbit_ct_broker_helpers:teardown_steps()).
|
||||||
).
|
|
||||||
|
|
||||||
init_per_group(_, Config) -> Config.
|
init_per_group(_, Config) -> Config.
|
||||||
end_per_group(_, Config) -> Config.
|
end_per_group(_, Config) -> Config.
|
||||||
|
@ -66,11 +61,8 @@ end_per_testcase(Testcase, Config) ->
|
||||||
|
|
||||||
proxy_protocol(Config) ->
|
proxy_protocol(Config) ->
|
||||||
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt),
|
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt),
|
||||||
{ok, Socket} = gen_tcp:connect(
|
{ok, Socket} = gen_tcp:connect({127,0,0,1}, Port,
|
||||||
{127, 0, 0, 1},
|
[binary, {active, false}, {packet, raw}]),
|
||||||
Port,
|
|
||||||
[binary, {active, false}, {packet, raw}]
|
|
||||||
),
|
|
||||||
ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
|
ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
|
||||||
ok = inet:send(Socket, mqtt_3_1_1_connect_packet()),
|
ok = inet:send(Socket, mqtt_3_1_1_connect_packet()),
|
||||||
{ok, _Packet} = gen_tcp:recv(Socket, 0, ?TIMEOUT),
|
{ok, _Packet} = gen_tcp:recv(Socket, 0, ?TIMEOUT),
|
||||||
|
@ -83,11 +75,8 @@ proxy_protocol(Config) ->
|
||||||
proxy_protocol_tls(Config) ->
|
proxy_protocol_tls(Config) ->
|
||||||
app_utils:start_applications([asn1, crypto, public_key, ssl]),
|
app_utils:start_applications([asn1, crypto, public_key, ssl]),
|
||||||
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt_tls),
|
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt_tls),
|
||||||
{ok, Socket} = gen_tcp:connect(
|
{ok, Socket} = gen_tcp:connect({127,0,0,1}, Port,
|
||||||
{127, 0, 0, 1},
|
[binary, {active, false}, {packet, raw}]),
|
||||||
Port,
|
|
||||||
[binary, {active, false}, {packet, raw}]
|
|
||||||
),
|
|
||||||
ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
|
ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
|
||||||
{ok, SslSocket} = ssl:connect(Socket, [], ?TIMEOUT),
|
{ok, SslSocket} = ssl:connect(Socket, [], ?TIMEOUT),
|
||||||
ok = ssl:send(SslSocket, mqtt_3_1_1_connect_packet()),
|
ok = ssl:send(SslSocket, mqtt_3_1_1_connect_packet()),
|
||||||
|
@ -107,5 +96,29 @@ merge_app_env(MqttConfig, Config) ->
|
||||||
rabbit_ct_helpers:merge_app_env(Config, MqttConfig).
|
rabbit_ct_helpers:merge_app_env(Config, MqttConfig).
|
||||||
|
|
||||||
mqtt_3_1_1_connect_packet() ->
|
mqtt_3_1_1_connect_packet() ->
|
||||||
<<16, 24, 0, 4, 77, 81, 84, 84, 4, 2, 0, 60, 0, 12, 84, 101, 115, 116, 67, 111, 110, 115, 117,
|
<<16,
|
||||||
109, 101, 114>>.
|
24,
|
||||||
|
0,
|
||||||
|
4,
|
||||||
|
77,
|
||||||
|
81,
|
||||||
|
84,
|
||||||
|
84,
|
||||||
|
4,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
60,
|
||||||
|
0,
|
||||||
|
12,
|
||||||
|
84,
|
||||||
|
101,
|
||||||
|
115,
|
||||||
|
116,
|
||||||
|
67,
|
||||||
|
111,
|
||||||
|
110,
|
||||||
|
115,
|
||||||
|
117,
|
||||||
|
109,
|
||||||
|
101,
|
||||||
|
114>>.
|
||||||
|
|
|
@ -13,16 +13,11 @@
|
||||||
-behaviour(rabbit_authn_backend).
|
-behaviour(rabbit_authn_backend).
|
||||||
-behaviour(rabbit_authz_backend).
|
-behaviour(rabbit_authz_backend).
|
||||||
|
|
||||||
-export([
|
-export([setup/1,
|
||||||
setup/1,
|
user_login_authentication/2, user_login_authorization/2,
|
||||||
user_login_authentication/2,
|
check_vhost_access/3, check_resource_access/4, check_topic_access/4,
|
||||||
user_login_authorization/2,
|
state_can_expire/0,
|
||||||
check_vhost_access/3,
|
get/1]).
|
||||||
check_resource_access/4,
|
|
||||||
check_topic_access/4,
|
|
||||||
state_can_expire/0,
|
|
||||||
get/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
setup(CallerPid) ->
|
setup(CallerPid) ->
|
||||||
ets:new(?MODULE, [set, public, named_table]),
|
ets:new(?MODULE, [set, public, named_table]),
|
||||||
|
@ -31,13 +26,12 @@ setup(CallerPid) ->
|
||||||
stop -> ok
|
stop -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
user_login_authentication(_, AuthProps) ->
|
user_login_authentication(_, AuthProps) ->
|
||||||
ets:insert(?MODULE, {authentication, AuthProps}),
|
ets:insert(?MODULE, {authentication, AuthProps}),
|
||||||
{ok, #auth_user{
|
{ok, #auth_user{username = <<"dummy">>,
|
||||||
username = <<"dummy">>,
|
tags = [],
|
||||||
tags = [],
|
impl = none}}.
|
||||||
impl = none
|
|
||||||
}}.
|
|
||||||
|
|
||||||
user_login_authorization(_, _) ->
|
user_login_authorization(_, _) ->
|
||||||
io:format("login authorization"),
|
io:format("login authorization"),
|
||||||
|
|
|
@ -5,45 +5,42 @@
|
||||||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved.
|
||||||
%%
|
%%
|
||||||
-module(reader_SUITE).
|
-module(reader_SUITE).
|
||||||
-compile([
|
-compile([export_all,
|
||||||
export_all,
|
nowarn_export_all]).
|
||||||
nowarn_export_all
|
|
||||||
]).
|
|
||||||
|
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
-import(rabbit_ct_broker_helpers, [rpc/4]).
|
-import(rabbit_ct_broker_helpers, [rpc/4]).
|
||||||
-import(rabbit_ct_helpers, [eventually/3]).
|
-import(rabbit_ct_helpers, [eventually/3]).
|
||||||
-import(util, [
|
-import(util, [all_connection_pids/1,
|
||||||
all_connection_pids/1,
|
publish_qos1_timeout/4,
|
||||||
publish_qos1_timeout/4,
|
expect_publishes/3,
|
||||||
expect_publishes/3,
|
connect/2,
|
||||||
connect/2,
|
connect/3,
|
||||||
connect/3,
|
await_exit/1]).
|
||||||
await_exit/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
[
|
||||||
{group, tests}
|
{group, tests}
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[
|
[
|
||||||
{tests, [], [
|
{tests, [],
|
||||||
block_connack_timeout,
|
[
|
||||||
handle_invalid_packets,
|
block_connack_timeout,
|
||||||
login_timeout,
|
handle_invalid_packets,
|
||||||
stats,
|
login_timeout,
|
||||||
quorum_clean_session_false,
|
stats,
|
||||||
quorum_clean_session_true,
|
quorum_clean_session_false,
|
||||||
classic_clean_session_true,
|
quorum_clean_session_true,
|
||||||
classic_clean_session_false,
|
classic_clean_session_true,
|
||||||
non_clean_sess_empty_client_id,
|
classic_clean_session_false,
|
||||||
event_authentication_failure,
|
non_clean_sess_empty_client_id,
|
||||||
rabbit_mqtt_qos0_queue_overflow
|
event_authentication_failure,
|
||||||
]}
|
rabbit_mqtt_qos0_queue_overflow
|
||||||
|
]}
|
||||||
].
|
].
|
||||||
|
|
||||||
suite() ->
|
suite() ->
|
||||||
|
@ -54,36 +51,28 @@ suite() ->
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
|
|
||||||
merge_app_env(Config) ->
|
merge_app_env(Config) ->
|
||||||
rabbit_ct_helpers:merge_app_env(
|
rabbit_ct_helpers:merge_app_env(Config,
|
||||||
Config,
|
{rabbit, [
|
||||||
{rabbit, [
|
{collect_statistics, basic},
|
||||||
{collect_statistics, basic},
|
{collect_statistics_interval, 100}
|
||||||
{collect_statistics_interval, 100}
|
]}).
|
||||||
]}
|
|
||||||
).
|
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:log_environment(),
|
rabbit_ct_helpers:log_environment(),
|
||||||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||||
{rmq_nodename_suffix, ?MODULE},
|
{rmq_nodename_suffix, ?MODULE},
|
||||||
{rmq_extra_tcp_ports, [
|
{rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
|
||||||
tcp_port_mqtt_extra,
|
tcp_port_mqtt_tls_extra]}
|
||||||
tcp_port_mqtt_tls_extra
|
]),
|
||||||
]}
|
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||||
]),
|
[ fun merge_app_env/1 ] ++
|
||||||
rabbit_ct_helpers:run_setup_steps(
|
rabbit_ct_broker_helpers:setup_steps() ++
|
||||||
Config1,
|
rabbit_ct_client_helpers:setup_steps()).
|
||||||
[fun merge_app_env/1] ++
|
|
||||||
rabbit_ct_broker_helpers:setup_steps() ++
|
|
||||||
rabbit_ct_client_helpers:setup_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
end_per_suite(Config) ->
|
end_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:run_teardown_steps(
|
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||||
Config,
|
rabbit_ct_client_helpers:teardown_steps() ++
|
||||||
rabbit_ct_client_helpers:teardown_steps() ++
|
rabbit_ct_broker_helpers:teardown_steps()).
|
||||||
rabbit_ct_broker_helpers:teardown_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
init_per_group(_, Config) ->
|
init_per_group(_, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
@ -97,6 +86,7 @@ init_per_testcase(Testcase, Config) ->
|
||||||
end_per_testcase(Testcase, Config) ->
|
end_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
||||||
|
|
||||||
|
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
%% Testsuite cases
|
%% Testsuite cases
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
|
@ -110,13 +100,11 @@ block_connack_timeout(Config) ->
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
|
|
||||||
%% We can still connect via TCP, but CONNECT packet will not be processed on the server.
|
%% We can still connect via TCP, but CONNECT packet will not be processed on the server.
|
||||||
{ok, Client} = emqtt:start_link([
|
{ok, Client} = emqtt:start_link([{host, "localhost"},
|
||||||
{host, "localhost"},
|
{port, P},
|
||||||
{port, P},
|
{clientid, atom_to_binary(?FUNCTION_NAME)},
|
||||||
{clientid, atom_to_binary(?FUNCTION_NAME)},
|
{proto_ver, v4},
|
||||||
{proto_ver, v4},
|
{connect_timeout, 1}]),
|
||||||
{connect_timeout, 1}
|
|
||||||
]),
|
|
||||||
unlink(Client),
|
unlink(Client),
|
||||||
ClientMRef = monitor(process, Client),
|
ClientMRef = monitor(process, Client),
|
||||||
{error, connack_timeout} = emqtt:connect(Client),
|
{error, connack_timeout} = emqtt:connect(Client),
|
||||||
|
@ -124,7 +112,7 @@ block_connack_timeout(Config) ->
|
||||||
{'DOWN', ClientMRef, process, Client, connack_timeout} ->
|
{'DOWN', ClientMRef, process, Client, connack_timeout} ->
|
||||||
ok
|
ok
|
||||||
after 200 ->
|
after 200 ->
|
||||||
ct:fail("missing connack_timeout in client")
|
ct:fail("missing connack_timeout in client")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Ports = rpc(Config, erlang, ports, []),
|
Ports = rpc(Config, erlang, ports, []),
|
||||||
|
@ -142,7 +130,7 @@ block_connack_timeout(Config) ->
|
||||||
%% because our client already disconnected.
|
%% because our client already disconnected.
|
||||||
ok
|
ok
|
||||||
after 2000 ->
|
after 2000 ->
|
||||||
ct:fail("missing peername_not_known from server")
|
ct:fail("missing peername_not_known from server")
|
||||||
end,
|
end,
|
||||||
%% Ensure that our client is not registered.
|
%% Ensure that our client is not registered.
|
||||||
?assertEqual([], all_connection_pids(Config)),
|
?assertEqual([], all_connection_pids(Config)),
|
||||||
|
@ -182,12 +170,8 @@ stats(Config) ->
|
||||||
[{Pid, Props}] = rpc(Config, ets, lookup, [connection_metrics, Pid]),
|
[{Pid, Props}] = rpc(Config, ets, lookup, [connection_metrics, Pid]),
|
||||||
true = proplists:is_defined(garbage_collection, Props),
|
true = proplists:is_defined(garbage_collection, Props),
|
||||||
%% If the coarse entry is present, stats were successfully emitted
|
%% If the coarse entry is present, stats were successfully emitted
|
||||||
[{Pid, _, _, _, _}] = rpc(
|
[{Pid, _, _, _, _}] = rpc(Config, ets, lookup,
|
||||||
Config,
|
[connection_coarse_metrics, Pid]),
|
||||||
ets,
|
|
||||||
lookup,
|
|
||||||
[connection_coarse_metrics, Pid]
|
|
||||||
),
|
|
||||||
ok = emqtt:disconnect(C).
|
ok = emqtt:disconnect(C).
|
||||||
|
|
||||||
get_durable_queue_type(Server, QNameBin) ->
|
get_durable_queue_type(Server, QNameBin) ->
|
||||||
|
@ -232,41 +216,32 @@ classic_clean_session_true(Config) ->
|
||||||
validate_durable_queue_type(Config, <<"classicCleanSessionTrue">>, true, rabbit_classic_queue).
|
validate_durable_queue_type(Config, <<"classicCleanSessionTrue">>, true, rabbit_classic_queue).
|
||||||
|
|
||||||
classic_clean_session_false(Config) ->
|
classic_clean_session_false(Config) ->
|
||||||
validate_durable_queue_type(
|
validate_durable_queue_type(Config, <<"classicCleanSessionFalse">>, false, rabbit_classic_queue).
|
||||||
Config, <<"classicCleanSessionFalse">>, false, rabbit_classic_queue
|
|
||||||
).
|
|
||||||
|
|
||||||
%% "If the Client supplies a zero-byte ClientId with CleanSession set to 0,
|
%% "If the Client supplies a zero-byte ClientId with CleanSession set to 0,
|
||||||
%% the Server MUST respond to the CONNECT Packet with a CONNACK return code 0x02
|
%% the Server MUST respond to the CONNECT Packet with a CONNACK return code 0x02
|
||||||
%% (Identifier rejected) and then close the Network Connection" [MQTT-3.1.3-8].
|
%% (Identifier rejected) and then close the Network Connection" [MQTT-3.1.3-8].
|
||||||
non_clean_sess_empty_client_id(Config) ->
|
non_clean_sess_empty_client_id(Config) ->
|
||||||
{ok, C} = emqtt:start_link(
|
{ok, C} = emqtt:start_link(
|
||||||
[
|
[{clientid, <<>>},
|
||||||
{clientid, <<>>},
|
{clean_start, false},
|
||||||
{clean_start, false},
|
{proto_ver, v4},
|
||||||
{proto_ver, v4},
|
{host, "localhost"},
|
||||||
{host, "localhost"},
|
{port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)}
|
||||||
{port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)}
|
]),
|
||||||
]
|
|
||||||
),
|
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
?assertMatch(
|
?assertMatch({error, {client_identifier_not_valid, _}},
|
||||||
{error, {client_identifier_not_valid, _}},
|
emqtt:connect(C)),
|
||||||
emqtt:connect(C)
|
|
||||||
),
|
|
||||||
ok = await_exit(C).
|
ok = await_exit(C).
|
||||||
|
|
||||||
event_authentication_failure(Config) ->
|
event_authentication_failure(Config) ->
|
||||||
{ok, C} = emqtt:start_link(
|
{ok, C} = emqtt:start_link(
|
||||||
[
|
[{username, <<"Trudy">>},
|
||||||
{username, <<"Trudy">>},
|
{password, <<"fake-password">>},
|
||||||
{password, <<"fake-password">>},
|
{host, "localhost"},
|
||||||
{host, "localhost"},
|
{port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)},
|
||||||
{port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)},
|
{clientid, atom_to_binary(?FUNCTION_NAME)},
|
||||||
{clientid, atom_to_binary(?FUNCTION_NAME)},
|
{proto_ver, v4}]),
|
||||||
{proto_ver, v4}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
true = unlink(C),
|
true = unlink(C),
|
||||||
|
|
||||||
ok = rabbit_ct_broker_helpers:add_code_path_to_all_nodes(Config, event_recorder),
|
ok = rabbit_ct_broker_helpers:add_code_path_to_all_nodes(Config, event_recorder),
|
||||||
|
@ -277,13 +252,9 @@ event_authentication_failure(Config) ->
|
||||||
|
|
||||||
[E, _ConnectionClosedEvent] = util:get_events(Server),
|
[E, _ConnectionClosedEvent] = util:get_events(Server),
|
||||||
util:assert_event_type(user_authentication_failure, E),
|
util:assert_event_type(user_authentication_failure, E),
|
||||||
util:assert_event_prop(
|
util:assert_event_prop([{name, <<"Trudy">>},
|
||||||
[
|
{connection_type, network}],
|
||||||
{name, <<"Trudy">>},
|
E),
|
||||||
{connection_type, network}
|
|
||||||
],
|
|
||||||
E
|
|
||||||
),
|
|
||||||
|
|
||||||
ok = gen_event:delete_handler({rabbit_event, Server}, event_recorder, []).
|
ok = gen_event:delete_handler({rabbit_event, Server}, event_recorder, []).
|
||||||
|
|
||||||
|
@ -295,12 +266,8 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
|
||||||
NumMsgs = 10_000,
|
NumMsgs = 10_000,
|
||||||
|
|
||||||
%% Provoke TCP back-pressure from client to server by using very small buffers.
|
%% Provoke TCP back-pressure from client to server by using very small buffers.
|
||||||
Opts = [
|
Opts = [{tcp_opts, [{recbuf, 512},
|
||||||
{tcp_opts, [
|
{buffer, 512}]}],
|
||||||
{recbuf, 512},
|
|
||||||
{buffer, 512}
|
|
||||||
]}
|
|
||||||
],
|
|
||||||
Sub = connect(<<"subscriber">>, Config, Opts),
|
Sub = connect(<<"subscriber">>, Config, Opts),
|
||||||
{ok, _, [0]} = emqtt:subscribe(Sub, Topic, qos0),
|
{ok, _, [0]} = emqtt:subscribe(Sub, Topic, qos0),
|
||||||
[ServerConnectionPid] = all_connection_pids(Config),
|
[ServerConnectionPid] = all_connection_pids(Config),
|
||||||
|
@ -312,12 +279,9 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
|
||||||
%% Let's overflow the receiving server MQTT connection process
|
%% Let's overflow the receiving server MQTT connection process
|
||||||
%% (i.e. the rabbit_mqtt_qos0_queue) by sending many large messages.
|
%% (i.e. the rabbit_mqtt_qos0_queue) by sending many large messages.
|
||||||
Pub = connect(<<"publisher">>, Config),
|
Pub = connect(<<"publisher">>, Config),
|
||||||
lists:foreach(
|
lists:foreach(fun(_) ->
|
||||||
fun(_) ->
|
ok = emqtt:publish(Pub, Topic, Msg, qos0)
|
||||||
ok = emqtt:publish(Pub, Topic, Msg, qos0)
|
end, lists:seq(1, NumMsgs)),
|
||||||
end,
|
|
||||||
lists:seq(1, NumMsgs)
|
|
||||||
),
|
|
||||||
|
|
||||||
%% Give the server some time to process (either send or drop) the messages.
|
%% Give the server some time to process (either send or drop) the messages.
|
||||||
timer:sleep(2000),
|
timer:sleep(2000),
|
||||||
|
@ -354,11 +318,9 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
|
||||||
|
|
||||||
num_received(Topic, Payload, N) ->
|
num_received(Topic, Payload, N) ->
|
||||||
receive
|
receive
|
||||||
{publish, #{
|
{publish, #{topic := Topic,
|
||||||
topic := Topic,
|
payload := Payload}} ->
|
||||||
payload := Payload
|
|
||||||
}} ->
|
|
||||||
num_received(Topic, Payload, N + 1)
|
num_received(Topic, Payload, N + 1)
|
||||||
after 1000 ->
|
after 1000 ->
|
||||||
N
|
N
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -8,31 +8,29 @@
|
||||||
-compile([export_all, nowarn_export_all]).
|
-compile([export_all, nowarn_export_all]).
|
||||||
|
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
-import(util, [
|
-import(util, [expect_publishes/3,
|
||||||
expect_publishes/3,
|
connect/3]).
|
||||||
connect/3
|
|
||||||
]).
|
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
[
|
||||||
{group, dets},
|
{group, dets},
|
||||||
{group, ets},
|
{group, ets},
|
||||||
{group, noop}
|
{group, noop}
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[
|
[
|
||||||
{dets, [], tests()},
|
{dets, [], tests()},
|
||||||
{ets, [], tests()},
|
{ets, [], tests()},
|
||||||
{noop, [], [does_not_retain]}
|
{noop, [], [does_not_retain]}
|
||||||
].
|
].
|
||||||
|
|
||||||
tests() ->
|
tests() ->
|
||||||
[
|
[
|
||||||
coerce_configuration_data,
|
coerce_configuration_data,
|
||||||
should_translate_amqp2mqtt_on_publish,
|
should_translate_amqp2mqtt_on_publish,
|
||||||
should_translate_amqp2mqtt_on_retention,
|
should_translate_amqp2mqtt_on_retention,
|
||||||
should_translate_amqp2mqtt_on_retention_search
|
should_translate_amqp2mqtt_on_retention_search
|
||||||
].
|
].
|
||||||
|
|
||||||
suite() ->
|
suite() ->
|
||||||
|
@ -51,38 +49,31 @@ end_per_suite(Config) ->
|
||||||
|
|
||||||
init_per_group(Group, Config0) ->
|
init_per_group(Group, Config0) ->
|
||||||
Config = rabbit_ct_helpers:set_config(
|
Config = rabbit_ct_helpers:set_config(
|
||||||
Config0,
|
Config0,
|
||||||
[
|
[
|
||||||
{rmq_nodename_suffix, Group},
|
{rmq_nodename_suffix, Group},
|
||||||
{rmq_extra_tcp_ports, [
|
{rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
|
||||||
tcp_port_mqtt_extra,
|
tcp_port_mqtt_tls_extra]}
|
||||||
tcp_port_mqtt_tls_extra
|
]),
|
||||||
]}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
Mod = list_to_atom("rabbit_mqtt_retained_msg_store_" ++ atom_to_list(Group)),
|
Mod = list_to_atom("rabbit_mqtt_retained_msg_store_" ++ atom_to_list(Group)),
|
||||||
Env = [
|
Env = [{rabbitmq_mqtt, [{retained_message_store, Mod}]},
|
||||||
{rabbitmq_mqtt, [{retained_message_store, Mod}]},
|
{rabbit, [
|
||||||
{rabbit, [
|
{default_user, "guest"},
|
||||||
{default_user, "guest"},
|
{default_pass, "guest"},
|
||||||
{default_pass, "guest"},
|
{default_vhost, "/"},
|
||||||
{default_vhost, "/"},
|
{default_permissions, [".*", ".*", ".*"]}
|
||||||
{default_permissions, [".*", ".*", ".*"]}
|
]}],
|
||||||
]}
|
|
||||||
],
|
|
||||||
rabbit_ct_helpers:run_setup_steps(
|
rabbit_ct_helpers:run_setup_steps(
|
||||||
Config,
|
Config,
|
||||||
[fun(Conf) -> rabbit_ct_helpers:merge_app_env(Conf, Env) end] ++
|
[fun(Conf) -> rabbit_ct_helpers:merge_app_env(Conf, Env) end] ++
|
||||||
rabbit_ct_broker_helpers:setup_steps() ++
|
rabbit_ct_broker_helpers:setup_steps() ++
|
||||||
rabbit_ct_client_helpers:setup_steps()
|
rabbit_ct_client_helpers:setup_steps()).
|
||||||
).
|
|
||||||
|
|
||||||
end_per_group(_, Config) ->
|
end_per_group(_, Config) ->
|
||||||
rabbit_ct_helpers:run_teardown_steps(
|
rabbit_ct_helpers:run_teardown_steps(
|
||||||
Config,
|
Config,
|
||||||
rabbit_ct_client_helpers:teardown_steps() ++
|
rabbit_ct_client_helpers:teardown_steps() ++
|
||||||
rabbit_ct_broker_helpers:teardown_steps()
|
rabbit_ct_broker_helpers:teardown_steps()).
|
||||||
).
|
|
||||||
|
|
||||||
init_per_testcase(Testcase, Config) ->
|
init_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_helpers:testcase_started(Config, Testcase).
|
rabbit_ct_helpers:testcase_started(Config, Testcase).
|
||||||
|
@ -90,6 +81,7 @@ init_per_testcase(Testcase, Config) ->
|
||||||
end_per_testcase(Testcase, Config) ->
|
end_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
rabbit_ct_helpers:testcase_finished(Config, Testcase).
|
||||||
|
|
||||||
|
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
%% Testsuite cases
|
%% Testsuite cases
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
|
@ -112,7 +104,7 @@ should_translate_amqp2mqtt_on_publish(Config) ->
|
||||||
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
|
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
|
||||||
%% there's an active consumer
|
%% there's an active consumer
|
||||||
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
|
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
|
||||||
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
|
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
|
||||||
ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
|
ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
|
||||||
ok = emqtt:disconnect(C).
|
ok = emqtt:disconnect(C).
|
||||||
|
|
||||||
|
@ -124,7 +116,7 @@ should_translate_amqp2mqtt_on_publish(Config) ->
|
||||||
should_translate_amqp2mqtt_on_retention(Config) ->
|
should_translate_amqp2mqtt_on_retention(Config) ->
|
||||||
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
|
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
|
||||||
%% publish with retain = true before a consumer comes around
|
%% publish with retain = true before a consumer comes around
|
||||||
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
|
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
|
||||||
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
|
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
|
||||||
ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
|
ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
|
||||||
ok = emqtt:disconnect(C).
|
ok = emqtt:disconnect(C).
|
||||||
|
@ -136,19 +128,19 @@ should_translate_amqp2mqtt_on_retention(Config) ->
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
should_translate_amqp2mqtt_on_retention_search(Config) ->
|
should_translate_amqp2mqtt_on_retention_search(Config) ->
|
||||||
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
|
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
|
||||||
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
|
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
|
||||||
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device/Field">>, qos1),
|
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device/Field">>, qos1),
|
||||||
ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
|
ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
|
||||||
ok = emqtt:disconnect(C).
|
ok = emqtt:disconnect(C).
|
||||||
|
|
||||||
does_not_retain(Config) ->
|
does_not_retain(Config) ->
|
||||||
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
|
C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
|
||||||
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
|
ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
|
||||||
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
|
{ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
|
||||||
receive
|
receive
|
||||||
Unexpected ->
|
Unexpected ->
|
||||||
ct:fail("Unexpected message: ~p", [Unexpected])
|
ct:fail("Unexpected message: ~p", [Unexpected])
|
||||||
after 1000 ->
|
after 1000 ->
|
||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
ok = emqtt:disconnect(C).
|
ok = emqtt:disconnect(C).
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,58 +4,46 @@
|
||||||
-include_lib("rabbit_common/include/rabbit.hrl").
|
-include_lib("rabbit_common/include/rabbit.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
-export([
|
-export([all_connection_pids/1,
|
||||||
all_connection_pids/1,
|
publish_qos1_timeout/4,
|
||||||
publish_qos1_timeout/4,
|
sync_publish_result/3,
|
||||||
sync_publish_result/3,
|
get_global_counters/2,
|
||||||
get_global_counters/2,
|
get_global_counters/3,
|
||||||
get_global_counters/3,
|
get_global_counters/4,
|
||||||
get_global_counters/4,
|
expect_publishes/3,
|
||||||
expect_publishes/3,
|
connect/2,
|
||||||
connect/2,
|
connect/3,
|
||||||
connect/3,
|
connect/4,
|
||||||
connect/4,
|
get_events/1,
|
||||||
get_events/1,
|
assert_event_type/2,
|
||||||
assert_event_type/2,
|
assert_event_prop/2,
|
||||||
assert_event_prop/2,
|
await_exit/1,
|
||||||
await_exit/1,
|
await_exit/2
|
||||||
await_exit/2
|
]).
|
||||||
]).
|
|
||||||
|
|
||||||
all_connection_pids(Config) ->
|
all_connection_pids(Config) ->
|
||||||
Nodes = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
|
Nodes = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
|
||||||
Result = erpc:multicall(Nodes, rabbit_mqtt, local_connection_pids, [], 5000),
|
Result = erpc:multicall(Nodes, rabbit_mqtt, local_connection_pids, [], 5000),
|
||||||
lists:foldl(
|
lists:foldl(fun({ok, Pids}, Acc) ->
|
||||||
fun
|
Pids ++ Acc;
|
||||||
({ok, Pids}, Acc) ->
|
(_, Acc) ->
|
||||||
Pids ++ Acc;
|
Acc
|
||||||
(_, Acc) ->
|
end, [], Result).
|
||||||
Acc
|
|
||||||
end,
|
|
||||||
[],
|
|
||||||
Result
|
|
||||||
).
|
|
||||||
|
|
||||||
publish_qos1_timeout(Client, Topic, Payload, Timeout) ->
|
publish_qos1_timeout(Client, Topic, Payload, Timeout) ->
|
||||||
Mref = erlang:monitor(process, Client),
|
Mref = erlang:monitor(process, Client),
|
||||||
ok = emqtt:publish_async(
|
ok = emqtt:publish_async(Client, Topic, #{}, Payload, [{qos, 1}], infinity,
|
||||||
Client,
|
{fun ?MODULE:sync_publish_result/3, [self(), Mref]}),
|
||||||
Topic,
|
|
||||||
#{},
|
|
||||||
Payload,
|
|
||||||
[{qos, 1}],
|
|
||||||
infinity,
|
|
||||||
{fun ?MODULE:sync_publish_result/3, [self(), Mref]}
|
|
||||||
),
|
|
||||||
receive
|
receive
|
||||||
{Mref, Reply} ->
|
{Mref, Reply} ->
|
||||||
erlang:demonitor(Mref, [flush]),
|
erlang:demonitor(Mref, [flush]),
|
||||||
Reply;
|
Reply;
|
||||||
{'DOWN', Mref, process, Client, Reason} ->
|
{'DOWN', Mref, process, Client, Reason} ->
|
||||||
ct:fail("client is down: ~tp", [Reason])
|
ct:fail("client is down: ~tp", [Reason])
|
||||||
after Timeout ->
|
after
|
||||||
erlang:demonitor(Mref, [flush]),
|
Timeout ->
|
||||||
puback_timeout
|
erlang:demonitor(Mref, [flush]),
|
||||||
|
puback_timeout
|
||||||
end.
|
end.
|
||||||
|
|
||||||
sync_publish_result(Caller, Mref, Result) ->
|
sync_publish_result(Caller, Mref, Result) ->
|
||||||
|
@ -63,27 +51,20 @@ sync_publish_result(Caller, Mref, Result) ->
|
||||||
|
|
||||||
expect_publishes(_, _, []) ->
|
expect_publishes(_, _, []) ->
|
||||||
ok;
|
ok;
|
||||||
expect_publishes(Client, Topic, [Payload | Rest]) when
|
expect_publishes(Client, Topic, [Payload|Rest])
|
||||||
is_pid(Client)
|
when is_pid(Client) ->
|
||||||
->
|
|
||||||
receive
|
receive
|
||||||
{publish, #{
|
{publish, #{client_pid := Client,
|
||||||
client_pid := Client,
|
topic := Topic,
|
||||||
topic := Topic,
|
payload := Payload}} ->
|
||||||
payload := Payload
|
|
||||||
}} ->
|
|
||||||
expect_publishes(Client, Topic, Rest);
|
expect_publishes(Client, Topic, Rest);
|
||||||
{publish, #{
|
{publish, #{client_pid := Client,
|
||||||
client_pid := Client,
|
topic := Topic,
|
||||||
topic := Topic,
|
payload := Other}} ->
|
||||||
payload := Other
|
ct:fail("Received unexpected PUBLISH payload. Expected: ~p Got: ~p",
|
||||||
}} ->
|
[Payload, Other])
|
||||||
ct:fail(
|
|
||||||
"Received unexpected PUBLISH payload. Expected: ~p Got: ~p",
|
|
||||||
[Payload, Other]
|
|
||||||
)
|
|
||||||
after 3000 ->
|
after 3000 ->
|
||||||
{publish_not_received, Payload}
|
{publish_not_received, Payload}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_global_counters(Config, ProtoVer) ->
|
get_global_counters(Config, ProtoVer) ->
|
||||||
|
@ -97,14 +78,11 @@ get_global_counters(Config, v3, Node, QType) ->
|
||||||
get_global_counters(Config, v4, Node, QType) ->
|
get_global_counters(Config, v4, Node, QType) ->
|
||||||
get_global_counters(Config, ?MQTT_PROTO_V4, Node, QType);
|
get_global_counters(Config, ?MQTT_PROTO_V4, Node, QType);
|
||||||
get_global_counters(Config, Proto, Node, QType) ->
|
get_global_counters(Config, Proto, Node, QType) ->
|
||||||
maps:get(
|
maps:get([{protocol, Proto}] ++ QType,
|
||||||
[{protocol, Proto}] ++ QType,
|
rabbit_ct_broker_helpers:rpc(Config, Node, rabbit_global_counters, overview, [])).
|
||||||
rabbit_ct_broker_helpers:rpc(Config, Node, rabbit_global_counters, overview, [])
|
|
||||||
).
|
|
||||||
|
|
||||||
get_events(Node) ->
|
get_events(Node) ->
|
||||||
%% events are sent and processed asynchronously
|
timer:sleep(300), %% events are sent and processed asynchronously
|
||||||
timer:sleep(300),
|
|
||||||
Result = gen_event:call({rabbit_event, Node}, event_recorder, take_state),
|
Result = gen_event:call({rabbit_event, Node}, event_recorder, take_state),
|
||||||
?assert(is_list(Result)),
|
?assert(is_list(Result)),
|
||||||
Result.
|
Result.
|
||||||
|
@ -114,26 +92,24 @@ assert_event_type(ExpectedType, #event{type = ActualType}) ->
|
||||||
|
|
||||||
assert_event_prop(ExpectedProp = {Key, _Value}, #event{props = Props}) ->
|
assert_event_prop(ExpectedProp = {Key, _Value}, #event{props = Props}) ->
|
||||||
?assertEqual(ExpectedProp, lists:keyfind(Key, 1, Props));
|
?assertEqual(ExpectedProp, lists:keyfind(Key, 1, Props));
|
||||||
assert_event_prop(ExpectedProps, Event) when
|
assert_event_prop(ExpectedProps, Event)
|
||||||
is_list(ExpectedProps)
|
when is_list(ExpectedProps) ->
|
||||||
->
|
lists:foreach(fun(P) ->
|
||||||
lists:foreach(
|
assert_event_prop(P, Event)
|
||||||
fun(P) ->
|
end, ExpectedProps).
|
||||||
assert_event_prop(P, Event)
|
|
||||||
end,
|
|
||||||
ExpectedProps
|
|
||||||
).
|
|
||||||
|
|
||||||
await_exit(Pid) ->
|
await_exit(Pid) ->
|
||||||
receive
|
receive
|
||||||
{'EXIT', Pid, _} -> ok
|
{'EXIT', Pid, _} -> ok
|
||||||
after 20_000 -> ct:fail({missing_exit, Pid})
|
after
|
||||||
|
20_000 -> ct:fail({missing_exit, Pid})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
await_exit(Pid, Reason) ->
|
await_exit(Pid, Reason) ->
|
||||||
receive
|
receive
|
||||||
{'EXIT', Pid, Reason} -> ok
|
{'EXIT', Pid, Reason} -> ok
|
||||||
after 20_000 -> ct:fail({missing_exit, Pid})
|
after
|
||||||
|
20_000 -> ct:fail({missing_exit, Pid})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
connect(ClientId, Config) ->
|
connect(ClientId, Config) ->
|
||||||
|
@ -144,27 +120,21 @@ connect(ClientId, Config, AdditionalOpts) ->
|
||||||
|
|
||||||
connect(ClientId, Config, Node, AdditionalOpts) ->
|
connect(ClientId, Config, Node, AdditionalOpts) ->
|
||||||
{Port, WsOpts, Connect} =
|
{Port, WsOpts, Connect} =
|
||||||
case rabbit_ct_helpers:get_config(Config, websocket, false) of
|
case rabbit_ct_helpers:get_config(Config, websocket, false) of
|
||||||
false ->
|
false ->
|
||||||
{
|
{rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_mqtt),
|
||||||
rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_mqtt),
|
[],
|
||||||
[],
|
fun emqtt:connect/1};
|
||||||
fun emqtt:connect/1
|
true ->
|
||||||
};
|
{rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_web_mqtt),
|
||||||
true ->
|
[{ws_path, "/ws"}],
|
||||||
{
|
fun emqtt:ws_connect/1}
|
||||||
rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_web_mqtt),
|
end,
|
||||||
[{ws_path, "/ws"}],
|
Options = [{host, "localhost"},
|
||||||
fun emqtt:ws_connect/1
|
{port, Port},
|
||||||
}
|
{proto_ver, v4},
|
||||||
end,
|
{clientid, rabbit_data_coercion:to_binary(ClientId)}
|
||||||
Options =
|
] ++ WsOpts ++ AdditionalOpts,
|
||||||
[
|
|
||||||
{host, "localhost"},
|
|
||||||
{port, Port},
|
|
||||||
{proto_ver, v4},
|
|
||||||
{clientid, rabbit_data_coercion:to_binary(ClientId)}
|
|
||||||
] ++ WsOpts ++ AdditionalOpts,
|
|
||||||
{ok, C} = emqtt:start_link(Options),
|
{ok, C} = emqtt:start_link(Options),
|
||||||
{ok, _Properties} = Connect(C),
|
{ok, _Properties} = Connect(C),
|
||||||
C.
|
C.
|
||||||
|
|
|
@ -12,18 +12,19 @@
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
[
|
||||||
{group, tests}
|
{group, tests}
|
||||||
].
|
].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[
|
[
|
||||||
{tests, [parallel], [
|
{tests, [parallel], [
|
||||||
coerce_exchange,
|
coerce_exchange,
|
||||||
coerce_vhost,
|
coerce_vhost,
|
||||||
coerce_default_user,
|
coerce_default_user,
|
||||||
coerce_default_pass,
|
coerce_default_pass,
|
||||||
mqtt_amqp_topic_translation
|
mqtt_amqp_topic_translation
|
||||||
]}
|
]
|
||||||
|
}
|
||||||
].
|
].
|
||||||
|
|
||||||
suite() ->
|
suite() ->
|
||||||
|
|
|
@ -49,7 +49,7 @@ init([]) -> {ok, {{one_for_one, 1, 5}, []}}.
|
||||||
-spec list_connections() -> [pid()].
|
-spec list_connections() -> [pid()].
|
||||||
list_connections() ->
|
list_connections() ->
|
||||||
PlainPids = connection_pids_of_protocol(?TCP_PROTOCOL),
|
PlainPids = connection_pids_of_protocol(?TCP_PROTOCOL),
|
||||||
TLSPids = connection_pids_of_protocol(?TLS_PROTOCOL),
|
TLSPids = connection_pids_of_protocol(?TLS_PROTOCOL),
|
||||||
PlainPids ++ TLSPids.
|
PlainPids ++ TLSPids.
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
@ -58,8 +58,7 @@ list_connections() ->
|
||||||
|
|
||||||
connection_pids_of_protocol(Protocol) ->
|
connection_pids_of_protocol(Protocol) ->
|
||||||
case rabbit_networking:ranch_ref_of_protocol(Protocol) of
|
case rabbit_networking:ranch_ref_of_protocol(Protocol) of
|
||||||
undefined ->
|
undefined -> [];
|
||||||
[];
|
|
||||||
AcceptorRef ->
|
AcceptorRef ->
|
||||||
lists:map(fun cowboy_ws_connection_pid/1, ranch:procs(AcceptorRef, connections))
|
lists:map(fun cowboy_ws_connection_pid/1, ranch:procs(AcceptorRef, connections))
|
||||||
end.
|
end.
|
||||||
|
@ -71,38 +70,36 @@ cowboy_ws_connection_pid(RanchConnPid) ->
|
||||||
Pid.
|
Pid.
|
||||||
|
|
||||||
mqtt_init() ->
|
mqtt_init() ->
|
||||||
CowboyOpts0 = maps:from_list(get_env(cowboy_opts, [])),
|
CowboyOpts0 = maps:from_list(get_env(cowboy_opts, [])),
|
||||||
CowboyWsOpts = maps:from_list(get_env(cowboy_ws_opts, [])),
|
CowboyWsOpts = maps:from_list(get_env(cowboy_ws_opts, [])),
|
||||||
Routes = cowboy_router:compile([
|
Routes = cowboy_router:compile([{'_', [
|
||||||
{'_', [
|
{get_env(ws_path, "/ws"), rabbit_web_mqtt_handler, [{ws_opts, CowboyWsOpts}]}
|
||||||
{get_env(ws_path, "/ws"), rabbit_web_mqtt_handler, [{ws_opts, CowboyWsOpts}]}
|
]}]),
|
||||||
]}
|
CowboyOpts = CowboyOpts0#{
|
||||||
]),
|
env => #{dispatch => Routes},
|
||||||
CowboyOpts = CowboyOpts0#{
|
proxy_header => get_env(proxy_protocol, false),
|
||||||
env => #{dispatch => Routes},
|
stream_handlers => [rabbit_web_mqtt_stream_handler, cowboy_stream_h]
|
||||||
proxy_header => get_env(proxy_protocol, false),
|
},
|
||||||
stream_handlers => [rabbit_web_mqtt_stream_handler, cowboy_stream_h]
|
case get_env(tcp_config, []) of
|
||||||
},
|
[] -> ok;
|
||||||
case get_env(tcp_config, []) of
|
TCPConf0 -> start_tcp_listener(TCPConf0, CowboyOpts)
|
||||||
[] -> ok;
|
end,
|
||||||
TCPConf0 -> start_tcp_listener(TCPConf0, CowboyOpts)
|
case get_env(ssl_config, []) of
|
||||||
end,
|
[] -> ok;
|
||||||
case get_env(ssl_config, []) of
|
TLSConf0 -> start_tls_listener(TLSConf0, CowboyOpts)
|
||||||
[] -> ok;
|
end,
|
||||||
TLSConf0 -> start_tls_listener(TLSConf0, CowboyOpts)
|
ok.
|
||||||
end,
|
|
||||||
ok.
|
|
||||||
|
|
||||||
start_tcp_listener(TCPConf0, CowboyOpts) ->
|
start_tcp_listener(TCPConf0, CowboyOpts) ->
|
||||||
{TCPConf, IpStr, Port} = get_tcp_conf(TCPConf0),
|
{TCPConf, IpStr, Port} = get_tcp_conf(TCPConf0),
|
||||||
RanchRef = rabbit_networking:ranch_ref(TCPConf),
|
RanchRef = rabbit_networking:ranch_ref(TCPConf),
|
||||||
RanchTransportOpts =
|
RanchTransportOpts =
|
||||||
#{
|
#{
|
||||||
socket_opts => TCPConf,
|
socket_opts => TCPConf,
|
||||||
max_connections => get_max_connections(),
|
max_connections => get_max_connections(),
|
||||||
num_acceptors => get_env(num_tcp_acceptors, 10),
|
num_acceptors => get_env(num_tcp_acceptors, 10),
|
||||||
num_conns_sups => get_env(num_conns_sup, 1)
|
num_conns_sups => get_env(num_conns_sup, 1)
|
||||||
},
|
},
|
||||||
case cowboy:start_clear(RanchRef, RanchTransportOpts, CowboyOpts) of
|
case cowboy:start_clear(RanchRef, RanchTransportOpts, CowboyOpts) of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
ok;
|
ok;
|
||||||
|
@ -110,28 +107,25 @@ start_tcp_listener(TCPConf0, CowboyOpts) ->
|
||||||
ok;
|
ok;
|
||||||
{error, ErrTCP} ->
|
{error, ErrTCP} ->
|
||||||
rabbit_log:error(
|
rabbit_log:error(
|
||||||
"Failed to start a WebSocket (HTTP) listener. Error: ~p, listener settings: ~p",
|
"Failed to start a WebSocket (HTTP) listener. Error: ~p, listener settings: ~p",
|
||||||
[ErrTCP, TCPConf]
|
[ErrTCP, TCPConf]),
|
||||||
),
|
|
||||||
throw(ErrTCP)
|
throw(ErrTCP)
|
||||||
end,
|
end,
|
||||||
listener_started(?TCP_PROTOCOL, TCPConf),
|
listener_started(?TCP_PROTOCOL, TCPConf),
|
||||||
rabbit_log:info(
|
rabbit_log:info("rabbit_web_mqtt: listening for HTTP connections on ~s:~w",
|
||||||
"rabbit_web_mqtt: listening for HTTP connections on ~s:~w",
|
[IpStr, Port]).
|
||||||
[IpStr, Port]
|
|
||||||
).
|
|
||||||
|
|
||||||
start_tls_listener(TLSConf0, CowboyOpts) ->
|
start_tls_listener(TLSConf0, CowboyOpts) ->
|
||||||
_ = rabbit_networking:ensure_ssl(),
|
_ = rabbit_networking:ensure_ssl(),
|
||||||
{TLSConf, TLSIpStr, TLSPort} = get_tls_conf(TLSConf0),
|
{TLSConf, TLSIpStr, TLSPort} = get_tls_conf(TLSConf0),
|
||||||
RanchRef = rabbit_networking:ranch_ref(TLSConf),
|
RanchRef = rabbit_networking:ranch_ref(TLSConf),
|
||||||
RanchTransportOpts =
|
RanchTransportOpts =
|
||||||
#{
|
#{
|
||||||
socket_opts => TLSConf,
|
socket_opts => TLSConf,
|
||||||
max_connections => get_max_connections(),
|
max_connections => get_max_connections(),
|
||||||
num_acceptors => get_env(num_ssl_acceptors, 10),
|
num_acceptors => get_env(num_ssl_acceptors, 10),
|
||||||
num_conns_sups => get_env(num_conns_sup, 1)
|
num_conns_sups => get_env(num_conns_sup, 1)
|
||||||
},
|
},
|
||||||
case cowboy:start_tls(RanchRef, RanchTransportOpts, CowboyOpts) of
|
case cowboy:start_tls(RanchRef, RanchTransportOpts, CowboyOpts) of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
ok;
|
ok;
|
||||||
|
@ -139,45 +133,34 @@ start_tls_listener(TLSConf0, CowboyOpts) ->
|
||||||
ok;
|
ok;
|
||||||
{error, ErrTLS} ->
|
{error, ErrTLS} ->
|
||||||
rabbit_log:error(
|
rabbit_log:error(
|
||||||
"Failed to start a TLS WebSocket (HTTPS) listener. Error: ~p, listener settings: ~p",
|
"Failed to start a TLS WebSocket (HTTPS) listener. Error: ~p, listener settings: ~p",
|
||||||
[ErrTLS, TLSConf]
|
[ErrTLS, TLSConf]),
|
||||||
),
|
|
||||||
throw(ErrTLS)
|
throw(ErrTLS)
|
||||||
end,
|
end,
|
||||||
listener_started(?TLS_PROTOCOL, TLSConf),
|
listener_started(?TLS_PROTOCOL, TLSConf),
|
||||||
rabbit_log:info(
|
rabbit_log:info("rabbit_web_mqtt: listening for HTTPS connections on ~s:~w",
|
||||||
"rabbit_web_mqtt: listening for HTTPS connections on ~s:~w",
|
[TLSIpStr, TLSPort]).
|
||||||
[TLSIpStr, TLSPort]
|
|
||||||
).
|
|
||||||
|
|
||||||
listener_started(Protocol, Listener) ->
|
listener_started(Protocol, Listener) ->
|
||||||
Port = rabbit_misc:pget(port, Listener),
|
Port = rabbit_misc:pget(port, Listener),
|
||||||
[
|
[rabbit_networking:tcp_listener_started(Protocol, Listener,
|
||||||
rabbit_networking:tcp_listener_started(
|
IPAddress, Port)
|
||||||
Protocol,
|
|| {IPAddress, _Port, _Family}
|
||||||
Listener,
|
<- rabbit_networking:tcp_listener_addresses(Port)],
|
||||||
IPAddress,
|
|
||||||
Port
|
|
||||||
)
|
|
||||||
|| {IPAddress, _Port, _Family} <-
|
|
||||||
rabbit_networking:tcp_listener_addresses(Port)
|
|
||||||
],
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
get_tcp_conf(TCPConf0) ->
|
get_tcp_conf(TCPConf0) ->
|
||||||
TCPConf1 =
|
TCPConf1 = case proplists:get_value(port, TCPConf0) of
|
||||||
case proplists:get_value(port, TCPConf0) of
|
undefined -> [{port, 15675}|TCPConf0];
|
||||||
undefined -> [{port, 15675} | TCPConf0];
|
_ -> TCPConf0
|
||||||
_ -> TCPConf0
|
end,
|
||||||
end,
|
|
||||||
get_ip_port(TCPConf1).
|
get_ip_port(TCPConf1).
|
||||||
|
|
||||||
get_tls_conf(TLSConf0) ->
|
get_tls_conf(TLSConf0) ->
|
||||||
TLSConf1 =
|
TLSConf1 = case proplists:get_value(port, TLSConf0) of
|
||||||
case proplists:get_value(port, TLSConf0) of
|
undefined -> [{port, 15675}|proplists:delete(port, TLSConf0)];
|
||||||
undefined -> [{port, 15675} | proplists:delete(port, TLSConf0)];
|
_ -> TLSConf0
|
||||||
_ -> TLSConf0
|
end,
|
||||||
end,
|
|
||||||
get_ip_port(TLSConf1).
|
get_ip_port(TLSConf1).
|
||||||
|
|
||||||
get_ip_port(Conf0) ->
|
get_ip_port(Conf0) ->
|
||||||
|
@ -194,7 +177,7 @@ normalize_ip(Ip) ->
|
||||||
Ip.
|
Ip.
|
||||||
|
|
||||||
get_max_connections() ->
|
get_max_connections() ->
|
||||||
get_env(max_connections, infinity).
|
get_env(max_connections, infinity).
|
||||||
|
|
||||||
get_env(Key, Default) ->
|
get_env(Key, Default) ->
|
||||||
rabbit_misc:get_env(rabbitmq_web_mqtt, Key, Default).
|
rabbit_misc:get_env(rabbitmq_web_mqtt, Key, Default).
|
||||||
|
|
|
@ -24,25 +24,23 @@
|
||||||
-export([conserve_resources/3]).
|
-export([conserve_resources/3]).
|
||||||
|
|
||||||
%% cowboy_sub_protocol
|
%% cowboy_sub_protocol
|
||||||
-export([
|
-export([upgrade/4,
|
||||||
upgrade/4,
|
upgrade/5,
|
||||||
upgrade/5,
|
takeover/7]).
|
||||||
takeover/7
|
|
||||||
]).
|
|
||||||
|
|
||||||
-type option(T) :: undefined | T.
|
-type option(T) :: undefined | T.
|
||||||
|
|
||||||
-record(state, {
|
-record(state, {
|
||||||
socket :: {rabbit_proxy_socket, any(), any()} | rabbit_net:socket(),
|
socket :: {rabbit_proxy_socket, any(), any()} | rabbit_net:socket(),
|
||||||
parse_state = rabbit_mqtt_packet:initial_state() :: rabbit_mqtt_packet:state(),
|
parse_state = rabbit_mqtt_packet:initial_state() :: rabbit_mqtt_packet:state(),
|
||||||
proc_state :: undefined | rabbit_mqtt_processor:state(),
|
proc_state :: undefined | rabbit_mqtt_processor:state(),
|
||||||
connection_state = running :: running | blocked,
|
connection_state = running :: running | blocked,
|
||||||
conserve = false :: boolean(),
|
conserve = false :: boolean(),
|
||||||
stats_timer :: option(rabbit_event:state()),
|
stats_timer :: option(rabbit_event:state()),
|
||||||
keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
|
keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
|
||||||
conn_name :: option(binary()),
|
conn_name :: option(binary()),
|
||||||
received_connect_packet = false :: boolean()
|
received_connect_packet = false :: boolean()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type state() :: #state{}.
|
-type state() :: #state{}.
|
||||||
|
|
||||||
|
@ -60,22 +58,14 @@ upgrade(Req, Env, Handler, HandlerState, Opts) ->
|
||||||
cowboy_websocket:upgrade(Req, Env, Handler, HandlerState, Opts).
|
cowboy_websocket:upgrade(Req, Env, Handler, HandlerState, Opts).
|
||||||
|
|
||||||
takeover(Parent, Ref, Socket, Transport, Opts, Buffer, {Handler, {HandlerState, PeerAddr}}) ->
|
takeover(Parent, Ref, Socket, Transport, Opts, Buffer, {Handler, {HandlerState, PeerAddr}}) ->
|
||||||
Sock =
|
Sock = case HandlerState#state.socket of
|
||||||
case HandlerState#state.socket of
|
undefined ->
|
||||||
undefined ->
|
Socket;
|
||||||
Socket;
|
ProxyInfo ->
|
||||||
ProxyInfo ->
|
{rabbit_proxy_socket, Socket, ProxyInfo}
|
||||||
{rabbit_proxy_socket, Socket, ProxyInfo}
|
end,
|
||||||
end,
|
cowboy_websocket:takeover(Parent, Ref, Socket, Transport, Opts, Buffer,
|
||||||
cowboy_websocket:takeover(
|
{Handler, {HandlerState#state{socket = Sock}, PeerAddr}}).
|
||||||
Parent,
|
|
||||||
Ref,
|
|
||||||
Socket,
|
|
||||||
Transport,
|
|
||||||
Opts,
|
|
||||||
Buffer,
|
|
||||||
{Handler, {HandlerState#state{socket = Sock}, PeerAddr}}
|
|
||||||
).
|
|
||||||
|
|
||||||
%% cowboy_websocket
|
%% cowboy_websocket
|
||||||
init(Req, Opts) ->
|
init(Req, Opts) ->
|
||||||
|
@ -85,20 +75,22 @@ init(Req, Opts) ->
|
||||||
Protocol ->
|
Protocol ->
|
||||||
{PeerAddr, _PeerPort} = maps:get(peer, Req),
|
{PeerAddr, _PeerPort} = maps:get(peer, Req),
|
||||||
WsOpts0 = proplists:get_value(ws_opts, Opts, #{}),
|
WsOpts0 = proplists:get_value(ws_opts, Opts, #{}),
|
||||||
WsOpts = maps:merge(#{compress => true}, WsOpts0),
|
WsOpts = maps:merge(#{compress => true}, WsOpts0),
|
||||||
case lists:member(<<"mqtt">>, Protocol) of
|
case lists:member(<<"mqtt">>, Protocol) of
|
||||||
false ->
|
false ->
|
||||||
no_supported_sub_protocol(Protocol, Req);
|
no_supported_sub_protocol(Protocol, Req);
|
||||||
true ->
|
true ->
|
||||||
{?MODULE,
|
{?MODULE,
|
||||||
cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, <<"mqtt">>, Req),
|
cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, <<"mqtt">>, Req),
|
||||||
{#state{socket = maps:get(proxy_header, Req, undefined)}, PeerAddr}, WsOpts}
|
{#state{socket = maps:get(proxy_header, Req, undefined)},
|
||||||
|
PeerAddr},
|
||||||
|
WsOpts}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec websocket_init({state(), PeerAddr :: binary()}) ->
|
-spec websocket_init({state(), PeerAddr :: binary()}) ->
|
||||||
{cowboy_websocket:commands(), state()}
|
{cowboy_websocket:commands(), state()} |
|
||||||
| {cowboy_websocket:commands(), state(), hibernate}.
|
{cowboy_websocket:commands(), state(), hibernate}.
|
||||||
websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
|
websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
|
||||||
logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [web_mqtt]}),
|
logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [web_mqtt]}),
|
||||||
ok = file_handle_cache:obtain(),
|
ok = file_handle_cache:obtain(),
|
||||||
|
@ -108,15 +100,12 @@ websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
|
||||||
?LOG_INFO("Accepting Web MQTT connection ~s", [ConnName]),
|
?LOG_INFO("Accepting Web MQTT connection ~s", [ConnName]),
|
||||||
_ = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}),
|
_ = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}),
|
||||||
PState = rabbit_mqtt_processor:initial_state(
|
PState = rabbit_mqtt_processor:initial_state(
|
||||||
rabbit_net:unwrap_socket(Sock),
|
rabbit_net:unwrap_socket(Sock),
|
||||||
ConnName,
|
ConnName,
|
||||||
fun send_reply/2,
|
fun send_reply/2,
|
||||||
PeerAddr
|
PeerAddr),
|
||||||
),
|
State1 = State0#state{conn_name = ConnName,
|
||||||
State1 = State0#state{
|
proc_state = PState},
|
||||||
conn_name = ConnName,
|
|
||||||
proc_state = PState
|
|
||||||
},
|
|
||||||
State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
|
State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
{[], State, hibernate};
|
{[], State, hibernate};
|
||||||
|
@ -124,28 +113,24 @@ websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
|
||||||
{[{shutdown_reason, Reason}], State0}
|
{[{shutdown_reason, Reason}], State0}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec conserve_resources(
|
-spec conserve_resources(pid(),
|
||||||
pid(),
|
rabbit_alarm:resource_alarm_source(),
|
||||||
rabbit_alarm:resource_alarm_source(),
|
rabbit_alarm:resource_alert()) -> ok.
|
||||||
rabbit_alarm:resource_alert()
|
|
||||||
) -> ok.
|
|
||||||
conserve_resources(Pid, _, {_, Conserve, _}) ->
|
conserve_resources(Pid, _, {_, Conserve, _}) ->
|
||||||
Pid ! {conserve_resources, Conserve},
|
Pid ! {conserve_resources, Conserve},
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
-spec websocket_handle(ping | pong | {text | binary | ping | pong, binary()}, State) ->
|
-spec websocket_handle(ping | pong | {text | binary | ping | pong, binary()}, State) ->
|
||||||
{cowboy_websocket:commands(), State}
|
{cowboy_websocket:commands(), State} |
|
||||||
| {cowboy_websocket:commands(), State, hibernate}.
|
{cowboy_websocket:commands(), State, hibernate}.
|
||||||
websocket_handle({binary, Data}, State) ->
|
websocket_handle({binary, Data}, State) ->
|
||||||
handle_data(Data, State);
|
handle_data(Data, State);
|
||||||
%% Silently ignore ping and pong frames as Cowboy will automatically reply to ping frames.
|
%% Silently ignore ping and pong frames as Cowboy will automatically reply to ping frames.
|
||||||
websocket_handle({Ping, _}, State) when
|
websocket_handle({Ping, _}, State)
|
||||||
Ping =:= ping orelse Ping =:= pong
|
when Ping =:= ping orelse Ping =:= pong ->
|
||||||
->
|
|
||||||
{[], State, hibernate};
|
{[], State, hibernate};
|
||||||
websocket_handle(Ping, State) when
|
websocket_handle(Ping, State)
|
||||||
Ping =:= ping orelse Ping =:= pong
|
when Ping =:= ping orelse Ping =:= pong ->
|
||||||
->
|
|
||||||
{[], State, hibernate};
|
{[], State, hibernate};
|
||||||
%% Log and close connection when receiving any other unexpected frames.
|
%% Log and close connection when receiving any other unexpected frames.
|
||||||
websocket_handle(Frame, State) ->
|
websocket_handle(Frame, State) ->
|
||||||
|
@ -153,8 +138,8 @@ websocket_handle(Frame, State) ->
|
||||||
stop(State, ?CLOSE_UNACCEPTABLE_DATA_TYPE, <<"unexpected WebSocket frame">>).
|
stop(State, ?CLOSE_UNACCEPTABLE_DATA_TYPE, <<"unexpected WebSocket frame">>).
|
||||||
|
|
||||||
-spec websocket_info(any(), State) ->
|
-spec websocket_info(any(), State) ->
|
||||||
{cowboy_websocket:commands(), State}
|
{cowboy_websocket:commands(), State} |
|
||||||
| {cowboy_websocket:commands(), State, hibernate}.
|
{cowboy_websocket:commands(), State, hibernate}.
|
||||||
websocket_info({conserve_resources, Conserve}, State) ->
|
websocket_info({conserve_resources, Conserve}, State) ->
|
||||||
handle_credits(State#state{conserve = Conserve});
|
handle_credits(State#state{conserve = Conserve});
|
||||||
websocket_info({bump_credit, Msg}, State) ->
|
websocket_info({bump_credit, Msg}, State) ->
|
||||||
|
@ -164,66 +149,39 @@ websocket_info({reply, Data}, State) ->
|
||||||
{[{binary, Data}], State, hibernate};
|
{[{binary, Data}], State, hibernate};
|
||||||
websocket_info({'EXIT', _, _}, State) ->
|
websocket_info({'EXIT', _, _}, State) ->
|
||||||
stop(State);
|
stop(State);
|
||||||
websocket_info(
|
websocket_info({'$gen_cast', QueueEvent = {queue_event, _, _}},
|
||||||
{'$gen_cast', QueueEvent = {queue_event, _, _}},
|
State = #state{proc_state = PState0}) ->
|
||||||
State = #state{proc_state = PState0}
|
|
||||||
) ->
|
|
||||||
case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
|
case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
|
||||||
{ok, PState} ->
|
{ok, PState} ->
|
||||||
handle_credits(State#state{proc_state = PState});
|
handle_credits(State#state{proc_state = PState});
|
||||||
{error, Reason, PState} ->
|
{error, Reason, PState} ->
|
||||||
?LOG_ERROR(
|
?LOG_ERROR("Web MQTT connection ~p failed to handle queue event: ~p",
|
||||||
"Web MQTT connection ~p failed to handle queue event: ~p",
|
[State#state.conn_name, Reason]),
|
||||||
[State#state.conn_name, Reason]
|
|
||||||
),
|
|
||||||
stop(State#state{proc_state = PState})
|
stop(State#state{proc_state = PState})
|
||||||
end;
|
end;
|
||||||
websocket_info(
|
websocket_info({'$gen_cast', duplicate_id}, State = #state{ proc_state = ProcState,
|
||||||
{'$gen_cast', duplicate_id},
|
conn_name = ConnName }) ->
|
||||||
State = #state{
|
?LOG_WARNING("Web MQTT disconnecting a client with duplicate ID '~s' (~p)",
|
||||||
proc_state = ProcState,
|
[rabbit_mqtt_processor:info(client_id, ProcState), ConnName]),
|
||||||
conn_name = ConnName
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
?LOG_WARNING(
|
|
||||||
"Web MQTT disconnecting a client with duplicate ID '~s' (~p)",
|
|
||||||
[rabbit_mqtt_processor:info(client_id, ProcState), ConnName]
|
|
||||||
),
|
|
||||||
stop(State);
|
stop(State);
|
||||||
websocket_info(
|
websocket_info({'$gen_cast', {close_connection, Reason}}, State = #state{ proc_state = ProcState,
|
||||||
{'$gen_cast', {close_connection, Reason}},
|
conn_name = ConnName }) ->
|
||||||
State = #state{
|
?LOG_WARNING("Web MQTT disconnecting client with ID '~s' (~p), reason: ~s",
|
||||||
proc_state = ProcState,
|
[rabbit_mqtt_processor:info(client_id, ProcState), ConnName, Reason]),
|
||||||
conn_name = ConnName
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
?LOG_WARNING(
|
|
||||||
"Web MQTT disconnecting client with ID '~s' (~p), reason: ~s",
|
|
||||||
[rabbit_mqtt_processor:info(client_id, ProcState), ConnName, Reason]
|
|
||||||
),
|
|
||||||
stop(State);
|
stop(State);
|
||||||
websocket_info({'$gen_cast', {force_event_refresh, Ref}}, State0) ->
|
websocket_info({'$gen_cast', {force_event_refresh, Ref}}, State0) ->
|
||||||
Infos = infos(?CREATION_EVENT_KEYS, State0),
|
Infos = infos(?CREATION_EVENT_KEYS, State0),
|
||||||
rabbit_event:notify(connection_created, Infos, Ref),
|
rabbit_event:notify(connection_created, Infos, Ref),
|
||||||
State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
|
State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
|
||||||
{[], State, hibernate};
|
{[], State, hibernate};
|
||||||
websocket_info(
|
websocket_info({'$gen_cast', refresh_config},
|
||||||
{'$gen_cast', refresh_config},
|
State0 = #state{proc_state = PState0,
|
||||||
State0 = #state{
|
conn_name = ConnName}) ->
|
||||||
proc_state = PState0,
|
|
||||||
conn_name = ConnName
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
|
PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
|
||||||
State = State0#state{proc_state = PState},
|
State = State0#state{proc_state = PState},
|
||||||
{[], State, hibernate};
|
{[], State, hibernate};
|
||||||
websocket_info(
|
websocket_info({keepalive, Req}, State = #state{keepalive = KState0,
|
||||||
{keepalive, Req},
|
conn_name = ConnName}) ->
|
||||||
State = #state{
|
|
||||||
keepalive = KState0,
|
|
||||||
conn_name = ConnName
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
case rabbit_mqtt_keepalive:handle(Req, KState0) of
|
case rabbit_mqtt_keepalive:handle(Req, KState0) of
|
||||||
{ok, KState} ->
|
{ok, KState} ->
|
||||||
{[], State#state{keepalive = KState}, hibernate};
|
{[], State#state{keepalive = KState}, hibernate};
|
||||||
|
@ -231,24 +189,18 @@ websocket_info(
|
||||||
?LOG_ERROR("keepalive timeout in Web MQTT connection ~p", [ConnName]),
|
?LOG_ERROR("keepalive timeout in Web MQTT connection ~p", [ConnName]),
|
||||||
stop(State, ?CLOSE_NORMAL, <<"MQTT keepalive timeout">>);
|
stop(State, ?CLOSE_NORMAL, <<"MQTT keepalive timeout">>);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?LOG_ERROR(
|
?LOG_ERROR("keepalive error in Web MQTT connection ~p: ~p",
|
||||||
"keepalive error in Web MQTT connection ~p: ~p",
|
[ConnName, Reason]),
|
||||||
[ConnName, Reason]
|
|
||||||
),
|
|
||||||
stop(State)
|
stop(State)
|
||||||
end;
|
end;
|
||||||
websocket_info(emit_stats, State) ->
|
websocket_info(emit_stats, State) ->
|
||||||
{[], emit_stats(State), hibernate};
|
{[], emit_stats(State), hibernate};
|
||||||
websocket_info(
|
websocket_info({ra_event, _From, Evt},
|
||||||
{ra_event, _From, Evt},
|
#state{proc_state = PState0} = State) ->
|
||||||
#state{proc_state = PState0} = State
|
|
||||||
) ->
|
|
||||||
PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
|
PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
|
||||||
{[], State#state{proc_state = PState}, hibernate};
|
{[], State#state{proc_state = PState}, hibernate};
|
||||||
websocket_info(
|
websocket_info({{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
|
||||||
{{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
|
State = #state{proc_state = PState0}) ->
|
||||||
State = #state{proc_state = PState0}
|
|
||||||
) ->
|
|
||||||
case rabbit_mqtt_processor:handle_down(Evt, PState0) of
|
case rabbit_mqtt_processor:handle_down(Evt, PState0) of
|
||||||
{ok, PState} ->
|
{ok, PState} ->
|
||||||
handle_credits(State#state{proc_state = PState});
|
handle_credits(State#state{proc_state = PState});
|
||||||
|
@ -275,16 +227,10 @@ terminate(_Reason, _Req, #state{proc_state = undefined}) ->
|
||||||
ok;
|
ok;
|
||||||
terminate(Reason, Request, #state{} = State) ->
|
terminate(Reason, Request, #state{} = State) ->
|
||||||
terminate(Reason, Request, {true, State});
|
terminate(Reason, Request, {true, State});
|
||||||
terminate(
|
terminate(_Reason, _Request,
|
||||||
_Reason,
|
{SendWill, #state{conn_name = ConnName,
|
||||||
_Request,
|
proc_state = PState,
|
||||||
{SendWill,
|
keepalive = KState} = State}) ->
|
||||||
#state{
|
|
||||||
conn_name = ConnName,
|
|
||||||
proc_state = PState,
|
|
||||||
keepalive = KState
|
|
||||||
} = State}
|
|
||||||
) ->
|
|
||||||
?LOG_INFO("Web MQTT closing connection ~ts", [ConnName]),
|
?LOG_INFO("Web MQTT closing connection ~ts", [ConnName]),
|
||||||
maybe_emit_stats(State),
|
maybe_emit_stats(State),
|
||||||
_ = rabbit_mqtt_keepalive:cancel_timer(KState),
|
_ = rabbit_mqtt_keepalive:cancel_timer(KState),
|
||||||
|
@ -306,49 +252,29 @@ handle_data(Data, State0 = #state{}) ->
|
||||||
Other
|
Other
|
||||||
end.
|
end.
|
||||||
|
|
||||||
handle_data1(
|
handle_data1(<<>>, State0 = #state{received_connect_packet = false,
|
||||||
<<>>,
|
proc_state = PState,
|
||||||
State0 = #state{
|
conn_name = ConnName}) ->
|
||||||
received_connect_packet = false,
|
?LOG_INFO("Accepted web MQTT connection ~p (~s, client ID: ~s)",
|
||||||
proc_state = PState,
|
[self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
|
||||||
conn_name = ConnName
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
?LOG_INFO(
|
|
||||||
"Accepted web MQTT connection ~p (~s, client ID: ~s)",
|
|
||||||
[self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]
|
|
||||||
),
|
|
||||||
State = State0#state{received_connect_packet = true},
|
State = State0#state{received_connect_packet = true},
|
||||||
{ok, ensure_stats_timer(control_throttle(State)), hibernate};
|
{ok, ensure_stats_timer(control_throttle(State)), hibernate};
|
||||||
handle_data1(<<>>, State) ->
|
handle_data1(<<>>, State) ->
|
||||||
{ok, ensure_stats_timer(control_throttle(State)), hibernate};
|
{ok, ensure_stats_timer(control_throttle(State)), hibernate};
|
||||||
handle_data1(
|
handle_data1(Data, State = #state{ parse_state = ParseState,
|
||||||
Data,
|
proc_state = ProcState,
|
||||||
State = #state{
|
conn_name = ConnName }) ->
|
||||||
parse_state = ParseState,
|
|
||||||
proc_state = ProcState,
|
|
||||||
conn_name = ConnName
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
case parse(Data, ParseState) of
|
case parse(Data, ParseState) of
|
||||||
{more, ParseState1} ->
|
{more, ParseState1} ->
|
||||||
{ok,
|
{ok, ensure_stats_timer(control_throttle(
|
||||||
ensure_stats_timer(
|
State #state{ parse_state = ParseState1 })), hibernate};
|
||||||
control_throttle(
|
|
||||||
State#state{parse_state = ParseState1}
|
|
||||||
)
|
|
||||||
),
|
|
||||||
hibernate};
|
|
||||||
{ok, Packet, Rest} ->
|
{ok, Packet, Rest} ->
|
||||||
case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
|
case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
|
||||||
{ok, ProcState1} ->
|
{ok, ProcState1} ->
|
||||||
handle_data1(
|
handle_data1(
|
||||||
Rest,
|
Rest,
|
||||||
State#state{
|
State#state{parse_state = rabbit_mqtt_packet:initial_state(),
|
||||||
parse_state = rabbit_mqtt_packet:initial_state(),
|
proc_state = ProcState1});
|
||||||
proc_state = ProcState1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
{error, Reason, _} ->
|
{error, Reason, _} ->
|
||||||
stop_mqtt_protocol_error(State, Reason, ConnName);
|
stop_mqtt_protocol_error(State, Reason, ConnName);
|
||||||
{stop, disconnect, ProcState1} ->
|
{stop, disconnect, ProcState1} ->
|
||||||
|
@ -363,11 +289,9 @@ parse(Data, ParseState) ->
|
||||||
rabbit_mqtt_packet:parse(Data, ParseState)
|
rabbit_mqtt_packet:parse(Data, ParseState)
|
||||||
catch
|
catch
|
||||||
_:Reason:Stacktrace ->
|
_:Reason:Stacktrace ->
|
||||||
?LOG_ERROR(
|
?LOG_ERROR("Web MQTT cannot parse a packet, reason: ~tp, stacktrace: ~tp, "
|
||||||
"Web MQTT cannot parse a packet, reason: ~tp, stacktrace: ~tp, "
|
"payload (first 100 bytes): ~tp",
|
||||||
"payload (first 100 bytes): ~tp",
|
[Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Data, 100)]),
|
||||||
[Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Data, 100)]
|
|
||||||
),
|
|
||||||
{error, cannot_parse}
|
{error, cannot_parse}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -384,34 +308,26 @@ stop(State, CloseCode, Error0) ->
|
||||||
|
|
||||||
handle_credits(State0) ->
|
handle_credits(State0) ->
|
||||||
State = #state{connection_state = CS} = control_throttle(State0),
|
State = #state{connection_state = CS} = control_throttle(State0),
|
||||||
Active =
|
Active = case CS of
|
||||||
case CS of
|
running -> true;
|
||||||
running -> true;
|
blocked -> false
|
||||||
blocked -> false
|
end,
|
||||||
end,
|
|
||||||
{[{active, Active}], State, hibernate}.
|
{[{active, Active}], State, hibernate}.
|
||||||
|
|
||||||
control_throttle(
|
control_throttle(State = #state{connection_state = ConnState,
|
||||||
State = #state{
|
conserve = Conserve,
|
||||||
connection_state = ConnState,
|
received_connect_packet = Connected,
|
||||||
conserve = Conserve,
|
proc_state = PState,
|
||||||
received_connect_packet = Connected,
|
keepalive = KState
|
||||||
proc_state = PState,
|
}) ->
|
||||||
keepalive = KState
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
|
Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
|
||||||
case {ConnState, Throttle} of
|
case {ConnState, Throttle} of
|
||||||
{running, true} ->
|
{running, true} ->
|
||||||
State#state{
|
State#state{connection_state = blocked,
|
||||||
connection_state = blocked,
|
keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)};
|
||||||
keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)
|
{blocked,false} ->
|
||||||
};
|
State#state{connection_state = running,
|
||||||
{blocked, false} ->
|
keepalive = rabbit_mqtt_keepalive:start_timer(KState)};
|
||||||
State#state{
|
|
||||||
connection_state = running,
|
|
||||||
keepalive = rabbit_mqtt_keepalive:start_timer(KState)
|
|
||||||
};
|
|
||||||
{_, _} ->
|
{_, _} ->
|
||||||
State
|
State
|
||||||
end.
|
end.
|
||||||
|
@ -425,23 +341,18 @@ ensure_stats_timer(State) ->
|
||||||
maybe_emit_stats(#state{stats_timer = undefined}) ->
|
maybe_emit_stats(#state{stats_timer = undefined}) ->
|
||||||
ok;
|
ok;
|
||||||
maybe_emit_stats(State) ->
|
maybe_emit_stats(State) ->
|
||||||
rabbit_event:if_enabled(
|
rabbit_event:if_enabled(State, #state.stats_timer,
|
||||||
State,
|
fun() -> emit_stats(State) end).
|
||||||
#state.stats_timer,
|
|
||||||
fun() -> emit_stats(State) end
|
|
||||||
).
|
|
||||||
|
|
||||||
emit_stats(State = #state{received_connect_packet = false}) ->
|
emit_stats(State=#state{received_connect_packet = false}) ->
|
||||||
%% Avoid emitting stats on terminate when the connection has not yet been
|
%% Avoid emitting stats on terminate when the connection has not yet been
|
||||||
%% established, as this causes orphan entries on the stats database
|
%% established, as this causes orphan entries on the stats database
|
||||||
rabbit_event:reset_stats_timer(State, #state.stats_timer);
|
rabbit_event:reset_stats_timer(State, #state.stats_timer);
|
||||||
emit_stats(State) ->
|
emit_stats(State) ->
|
||||||
[
|
[{_, Pid},
|
||||||
{_, Pid},
|
{_, RecvOct},
|
||||||
{_, RecvOct},
|
{_, SendOct},
|
||||||
{_, SendOct},
|
{_, Reductions}] = infos(?SIMPLE_METRICS, State),
|
||||||
{_, Reductions}
|
|
||||||
] = infos(?SIMPLE_METRICS, State),
|
|
||||||
Infos = infos(?OTHER_METRICS, State),
|
Infos = infos(?OTHER_METRICS, State),
|
||||||
rabbit_core_metrics:connection_stats(Pid, Infos),
|
rabbit_core_metrics:connection_stats(Pid, Infos),
|
||||||
rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
|
rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
|
||||||
|
@ -453,13 +364,12 @@ infos(Items, State) ->
|
||||||
|
|
||||||
i(pid, _) ->
|
i(pid, _) ->
|
||||||
self();
|
self();
|
||||||
i(SockStat, #state{socket = Sock}) when
|
i(SockStat, #state{socket = Sock})
|
||||||
SockStat =:= recv_oct;
|
when SockStat =:= recv_oct;
|
||||||
SockStat =:= recv_cnt;
|
SockStat =:= recv_cnt;
|
||||||
SockStat =:= send_oct;
|
SockStat =:= send_oct;
|
||||||
SockStat =:= send_cnt;
|
SockStat =:= send_cnt;
|
||||||
SockStat =:= send_pend
|
SockStat =:= send_pend ->
|
||||||
->
|
|
||||||
case rabbit_net:getstat(Sock, [SockStat]) of
|
case rabbit_net:getstat(Sock, [SockStat]) of
|
||||||
{ok, [{_, N}]} when is_number(N) ->
|
{ok, [{_, N}]} when is_number(N) ->
|
||||||
N;
|
N;
|
||||||
|
@ -473,23 +383,22 @@ i(garbage_collection, _) ->
|
||||||
rabbit_misc:get_gc_info(self());
|
rabbit_misc:get_gc_info(self());
|
||||||
i(protocol, #state{proc_state = PState}) ->
|
i(protocol, #state{proc_state = PState}) ->
|
||||||
{?PROTO_FAMILY, rabbit_mqtt_processor:proto_version_tuple(PState)};
|
{?PROTO_FAMILY, rabbit_mqtt_processor:proto_version_tuple(PState)};
|
||||||
i(SSL, #state{socket = Sock}) when
|
i(SSL, #state{socket = Sock})
|
||||||
SSL =:= ssl;
|
when SSL =:= ssl;
|
||||||
SSL =:= ssl_protocol;
|
SSL =:= ssl_protocol;
|
||||||
SSL =:= ssl_key_exchange;
|
SSL =:= ssl_key_exchange;
|
||||||
SSL =:= ssl_cipher;
|
SSL =:= ssl_cipher;
|
||||||
SSL =:= ssl_hash
|
SSL =:= ssl_hash ->
|
||||||
->
|
rabbit_ssl:info(SSL, {rabbit_net:unwrap_socket(Sock),
|
||||||
rabbit_ssl:info(SSL, {rabbit_net:unwrap_socket(Sock), rabbit_net:maybe_get_proxy_socket(Sock)});
|
rabbit_net:maybe_get_proxy_socket(Sock)});
|
||||||
i(name, S) ->
|
i(name, S) ->
|
||||||
i(conn_name, S);
|
i(conn_name, S);
|
||||||
i(conn_name, #state{conn_name = Val}) ->
|
i(conn_name, #state{conn_name = Val}) ->
|
||||||
Val;
|
Val;
|
||||||
i(Cert, #state{socket = Sock}) when
|
i(Cert, #state{socket = Sock})
|
||||||
Cert =:= peer_cert_issuer;
|
when Cert =:= peer_cert_issuer;
|
||||||
Cert =:= peer_cert_subject;
|
Cert =:= peer_cert_subject;
|
||||||
Cert =:= peer_cert_validity
|
Cert =:= peer_cert_validity ->
|
||||||
->
|
|
||||||
rabbit_ssl:cert_info(Cert, rabbit_net:unwrap_socket(Sock));
|
rabbit_ssl:cert_info(Cert, rabbit_net:unwrap_socket(Sock));
|
||||||
i(state, S) ->
|
i(state, S) ->
|
||||||
i(connection_state, S);
|
i(connection_state, S);
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
-export([terminate/3]).
|
-export([terminate/3]).
|
||||||
-export([early_error/5]).
|
-export([early_error/5]).
|
||||||
|
|
||||||
|
|
||||||
-record(state, {next}).
|
-record(state, {next}).
|
||||||
|
|
||||||
init(StreamID, Req, Opts) ->
|
init(StreamID, Req, Opts) ->
|
||||||
|
|
|
@ -23,6 +23,7 @@ init_per_suite(Config) ->
|
||||||
Config1 = rabbit_ct_helpers:run_setup_steps(Config),
|
Config1 = rabbit_ct_helpers:run_setup_steps(Config),
|
||||||
rabbit_ct_config_schema:init_schemas(rabbitmq_web_mqtt, Config1).
|
rabbit_ct_config_schema:init_schemas(rabbitmq_web_mqtt, Config1).
|
||||||
|
|
||||||
|
|
||||||
end_per_suite(Config) ->
|
end_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:run_teardown_steps(Config).
|
rabbit_ct_helpers:run_teardown_steps(Config).
|
||||||
|
|
||||||
|
@ -30,19 +31,15 @@ init_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
rabbit_ct_helpers:testcase_started(Config, Testcase),
|
||||||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||||
{rmq_nodename_suffix, Testcase}
|
{rmq_nodename_suffix, Testcase}
|
||||||
]),
|
]),
|
||||||
rabbit_ct_helpers:run_steps(
|
rabbit_ct_helpers:run_steps(Config1,
|
||||||
Config1,
|
rabbit_ct_broker_helpers:setup_steps() ++
|
||||||
rabbit_ct_broker_helpers:setup_steps() ++
|
rabbit_ct_client_helpers:setup_steps()).
|
||||||
rabbit_ct_client_helpers:setup_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
end_per_testcase(Testcase, Config) ->
|
end_per_testcase(Testcase, Config) ->
|
||||||
Config1 = rabbit_ct_helpers:run_steps(
|
Config1 = rabbit_ct_helpers:run_steps(Config,
|
||||||
Config,
|
rabbit_ct_client_helpers:teardown_steps() ++
|
||||||
rabbit_ct_client_helpers:teardown_steps() ++
|
rabbit_ct_broker_helpers:teardown_steps()),
|
||||||
rabbit_ct_broker_helpers:teardown_steps()
|
|
||||||
),
|
|
||||||
rabbit_ct_helpers:testcase_finished(Config1, Testcase).
|
rabbit_ct_helpers:testcase_finished(Config1, Testcase).
|
||||||
|
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
|
@ -50,13 +47,9 @@ end_per_testcase(Testcase, Config) ->
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
|
|
||||||
run_snippets(Config) ->
|
run_snippets(Config) ->
|
||||||
ok = rabbit_ct_broker_helpers:rpc(
|
ok = rabbit_ct_broker_helpers:rpc(Config, 0,
|
||||||
Config,
|
?MODULE, run_snippets1, [Config]).
|
||||||
0,
|
|
||||||
?MODULE,
|
|
||||||
run_snippets1,
|
|
||||||
[Config]
|
|
||||||
).
|
|
||||||
|
|
||||||
run_snippets1(Config) ->
|
run_snippets1(Config) ->
|
||||||
rabbit_ct_config_schema:run_snippets(Config).
|
rabbit_ct_config_schema:run_snippets(Config).
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
-module(proxy_protocol_SUITE).
|
-module(proxy_protocol_SUITE).
|
||||||
|
|
||||||
|
|
||||||
-compile([export_all, nowarn_export_all]).
|
-compile([export_all, nowarn_export_all]).
|
||||||
|
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
@ -14,24 +15,20 @@
|
||||||
|
|
||||||
suite() ->
|
suite() ->
|
||||||
[
|
[
|
||||||
%% If a test hangs, no need to wait for 30 minutes.
|
%% If a test hangs, no need to wait for 30 minutes.
|
||||||
{timetrap, {minutes, 2}}
|
{timetrap, {minutes, 2}}
|
||||||
].
|
].
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[
|
[{group, http_tests},
|
||||||
{group, http_tests},
|
{group, https_tests}].
|
||||||
{group, https_tests}
|
|
||||||
].
|
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
Tests = [
|
Tests = [
|
||||||
proxy_protocol
|
proxy_protocol
|
||||||
],
|
],
|
||||||
[
|
[{https_tests, [], Tests},
|
||||||
{https_tests, [], Tests},
|
{http_tests, [], Tests}].
|
||||||
{http_tests, [], Tests}
|
|
||||||
].
|
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:log_environment(),
|
rabbit_ct_helpers:log_environment(),
|
||||||
|
@ -41,29 +38,22 @@ end_per_suite(Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
init_per_group(Group, Config) ->
|
init_per_group(Group, Config) ->
|
||||||
Protocol =
|
Protocol = case Group of
|
||||||
case Group of
|
http_tests -> "ws";
|
||||||
http_tests -> "ws";
|
https_tests -> "wss"
|
||||||
https_tests -> "wss"
|
end,
|
||||||
end,
|
Config1 = rabbit_ct_helpers:set_config(Config,
|
||||||
Config1 = rabbit_ct_helpers:set_config(
|
[{rmq_nodename_suffix, ?MODULE},
|
||||||
Config,
|
{protocol, Protocol},
|
||||||
[
|
{rabbitmq_ct_tls_verify, verify_none},
|
||||||
{rmq_nodename_suffix, ?MODULE},
|
{rabbitmq_ct_tls_fail_if_no_peer_cert, false}]),
|
||||||
{protocol, Protocol},
|
|
||||||
{rabbitmq_ct_tls_verify, verify_none},
|
|
||||||
{rabbitmq_ct_tls_fail_if_no_peer_cert, false}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
|
|
||||||
rabbit_ct_helpers:run_setup_steps(
|
rabbit_ct_helpers:run_setup_steps(
|
||||||
Config1,
|
Config1,
|
||||||
rabbit_ct_broker_helpers:setup_steps() ++
|
rabbit_ct_broker_helpers:setup_steps() ++ [
|
||||||
[
|
fun configure_proxy_protocol/1,
|
||||||
fun configure_proxy_protocol/1,
|
fun configure_ssl/1
|
||||||
fun configure_ssl/1
|
]).
|
||||||
]
|
|
||||||
).
|
|
||||||
|
|
||||||
configure_proxy_protocol(Config) ->
|
configure_proxy_protocol(Config) ->
|
||||||
rabbit_ws_test_util:update_app_env(Config, proxy_protocol, true),
|
rabbit_ws_test_util:update_app_env(Config, proxy_protocol, true),
|
||||||
|
@ -74,16 +64,12 @@ configure_ssl(Config) ->
|
||||||
RabbitAppConfig = proplists:get_value(rabbit, ErlangConfig, []),
|
RabbitAppConfig = proplists:get_value(rabbit, ErlangConfig, []),
|
||||||
RabbitSslConfig = proplists:get_value(ssl_options, RabbitAppConfig, []),
|
RabbitSslConfig = proplists:get_value(ssl_options, RabbitAppConfig, []),
|
||||||
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_web_mqtt_tls),
|
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_web_mqtt_tls),
|
||||||
rabbit_ws_test_util:update_app_env(Config, ssl_config, [
|
rabbit_ws_test_util:update_app_env(Config, ssl_config, [{port, Port} | lists:keydelete(port, 1, RabbitSslConfig)]),
|
||||||
{port, Port} | lists:keydelete(port, 1, RabbitSslConfig)
|
|
||||||
]),
|
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_group(_Group, Config) ->
|
end_per_group(_Group, Config) ->
|
||||||
rabbit_ct_helpers:run_teardown_steps(
|
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||||
Config,
|
rabbit_ct_broker_helpers:teardown_steps()).
|
||||||
rabbit_ct_broker_helpers:teardown_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
init_per_testcase(Testcase, Config) ->
|
init_per_testcase(Testcase, Config) ->
|
||||||
rabbit_ct_helpers:testcase_started(Config, Testcase).
|
rabbit_ct_helpers:testcase_started(Config, Testcase).
|
||||||
|
@ -95,23 +81,13 @@ proxy_protocol(Config) ->
|
||||||
PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config),
|
PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config),
|
||||||
|
|
||||||
Protocol = ?config(protocol, Config),
|
Protocol = ?config(protocol, Config),
|
||||||
WS = rfc6455_client:new(
|
WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self(),
|
||||||
Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws",
|
undefined, ["mqtt"], "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
|
||||||
self(),
|
|
||||||
undefined,
|
|
||||||
["mqtt"],
|
|
||||||
"PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"
|
|
||||||
),
|
|
||||||
{ok, _} = rfc6455_client:open(WS),
|
{ok, _} = rfc6455_client:open(WS),
|
||||||
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
|
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
|
||||||
{binary, _P} = rfc6455_client:recv(WS),
|
{binary, _P} = rfc6455_client:recv(WS),
|
||||||
ConnectionName = rabbit_ct_broker_helpers:rpc(
|
ConnectionName = rabbit_ct_broker_helpers:rpc(Config, 0,
|
||||||
Config,
|
?MODULE, connection_name, []),
|
||||||
0,
|
|
||||||
?MODULE,
|
|
||||||
connection_name,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
match = re:run(ConnectionName, <<"^192.168.1.1:80 -> 192.168.1.2:81$">>, [{capture, none}]),
|
match = re:run(ConnectionName, <<"^192.168.1.1:80 -> 192.168.1.2:81$">>, [{capture, none}]),
|
||||||
{close, _} = rfc6455_client:close(WS),
|
{close, _} = rfc6455_client:close(WS),
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -18,13 +18,13 @@ all() ->
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[
|
[
|
||||||
{tests, [], [
|
{tests, [],
|
||||||
no_websocket_subprotocol,
|
[no_websocket_subprotocol
|
||||||
unsupported_websocket_subprotocol,
|
,unsupported_websocket_subprotocol
|
||||||
unacceptable_data_type,
|
,unacceptable_data_type
|
||||||
handle_invalid_packets,
|
,handle_invalid_packets
|
||||||
duplicate_connect
|
,duplicate_connect
|
||||||
]}
|
]}
|
||||||
].
|
].
|
||||||
|
|
||||||
suite() ->
|
suite() ->
|
||||||
|
@ -35,19 +35,15 @@ init_per_suite(Config) ->
|
||||||
Config1 = rabbit_ct_helpers:set_config(Config, [
|
Config1 = rabbit_ct_helpers:set_config(Config, [
|
||||||
{rmq_nodename_suffix, ?MODULE},
|
{rmq_nodename_suffix, ?MODULE},
|
||||||
{protocol, "ws"}
|
{protocol, "ws"}
|
||||||
]),
|
]),
|
||||||
rabbit_ct_helpers:run_setup_steps(
|
rabbit_ct_helpers:run_setup_steps(Config1,
|
||||||
Config1,
|
rabbit_ct_broker_helpers:setup_steps() ++
|
||||||
rabbit_ct_broker_helpers:setup_steps() ++
|
rabbit_ct_client_helpers:setup_steps()).
|
||||||
rabbit_ct_client_helpers:setup_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
end_per_suite(Config) ->
|
end_per_suite(Config) ->
|
||||||
rabbit_ct_helpers:run_teardown_steps(
|
rabbit_ct_helpers:run_teardown_steps(Config,
|
||||||
Config,
|
rabbit_ct_client_helpers:teardown_steps() ++
|
||||||
rabbit_ct_client_helpers:teardown_steps() ++
|
rabbit_ct_broker_helpers:teardown_steps()).
|
||||||
rabbit_ct_broker_helpers:teardown_steps()
|
|
||||||
).
|
|
||||||
|
|
||||||
init_per_group(_, Config) ->
|
init_per_group(_, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
@ -76,9 +72,7 @@ websocket_subprotocol(Config, SubProtocol) ->
|
||||||
PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config),
|
PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config),
|
||||||
WS = rfc6455_client:new("ws://localhost:" ++ PortStr ++ "/ws", self(), undefined, SubProtocol),
|
WS = rfc6455_client:new("ws://localhost:" ++ PortStr ++ "/ws", self(), undefined, SubProtocol),
|
||||||
{_, [{http_response, Res}]} = rfc6455_client:open(WS),
|
{_, [{http_response, Res}]} = rfc6455_client:open(WS),
|
||||||
{'HTTP/1.1', 400, <<"Bad Request">>, _} = cow_http:parse_status_line(
|
{'HTTP/1.1', 400, <<"Bad Request">>, _} = cow_http:parse_status_line(rabbit_data_coercion:to_binary(Res)),
|
||||||
rabbit_data_coercion:to_binary(Res)
|
|
||||||
),
|
|
||||||
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
|
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
|
||||||
{close, _} = rfc6455_client:recv(WS, timer:seconds(1)).
|
{close, _} = rfc6455_client:recv(WS, timer:seconds(1)).
|
||||||
|
|
||||||
|
@ -116,8 +110,7 @@ duplicate_connect(Config) ->
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
|
rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
|
||||||
eventually(?_assertEqual(0, num_mqtt_connections(Config, 0))),
|
eventually(?_assertEqual(0, num_mqtt_connections(Config, 0))),
|
||||||
receive
|
receive {'EXIT', WS, _} -> ok
|
||||||
{'EXIT', WS, _} -> ok
|
|
||||||
after 500 -> ct:fail("expected web socket to exit")
|
after 500 -> ct:fail("expected web socket to exit")
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,3 @@
|
||||||
inline_items => {when_under, 4}
|
inline_items => {when_under, 4}
|
||||||
}}
|
}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{project_plugins, [erlfmt]}.
|
|
||||||
{erlfmt, [
|
|
||||||
write,
|
|
||||||
{print_width, 100},
|
|
||||||
{files, "deps/{rabbitmq_mqtt,rabbitmq_web_mqtt}/{test,src}/*.erl"}
|
|
||||||
]}.
|
|
||||||
|
|
Loading…
Reference in New Issue