Revert "Format MQTT code with `erlfmt`"

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

View File

@ -1 +0,0 @@
1de9fcf582def91d1cee6bea457dd24e8a53a431

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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