diff options
46 files changed, 5384 insertions, 3500 deletions
diff --git a/deps/rabbitmq_mqtt/src/Elixir.RabbitMQ.CLI.Ctl.Commands.DecommissionMqttNodeCommand.erl b/deps/rabbitmq_mqtt/src/Elixir.RabbitMQ.CLI.Ctl.Commands.DecommissionMqttNodeCommand.erl index fa8e09341c..a89dad8d16 100644 --- a/deps/rabbitmq_mqtt/src/Elixir.RabbitMQ.CLI.Ctl.Commands.DecommissionMqttNodeCommand.erl +++ b/deps/rabbitmq_mqtt/src/Elixir.RabbitMQ.CLI.Ctl.Commands.DecommissionMqttNodeCommand.erl @@ -10,18 +10,20 @@ -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() -> []. @@ -48,20 +50,29 @@ 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). diff --git a/deps/rabbitmq_mqtt/src/Elixir.RabbitMQ.CLI.Ctl.Commands.ListMqttConnectionsCommand.erl b/deps/rabbitmq_mqtt/src/Elixir.RabbitMQ.CLI.Ctl.Commands.ListMqttConnectionsCommand.erl index 07265b56e1..f40868cd83 100644 --- a/deps/rabbitmq_mqtt/src/Elixir.RabbitMQ.CLI.Ctl.Commands.ListMqttConnectionsCommand.erl +++ b/deps/rabbitmq_mqtt/src/Elixir.RabbitMQ.CLI.Ctl.Commands.ListMqttConnectionsCommand.erl @@ -10,20 +10,22 @@ -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]. @@ -37,10 +39,14 @@ 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) -> @@ -55,19 +61,22 @@ 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), @@ -78,7 +87,8 @@ run(Args, #{node := NodeName, [Nodes, InfoKeys], Timeout, InfoKeys, - length(Nodes)). + length(Nodes) + ). banner(_, _) -> <<"Listing MQTT connections ...">>. diff --git a/deps/rabbitmq_mqtt/src/mqtt_machine.erl b/deps/rabbitmq_mqtt/src/mqtt_machine.erl index 20e69a7a2a..36c0c7e70f 100644 --- a/deps/rabbitmq_mqtt/src/mqtt_machine.erl +++ b/deps/rabbitmq_mqtt/src/mqtt_machine.erl @@ -9,13 +9,15 @@ -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{}. @@ -24,9 +26,10 @@ -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; @@ -38,93 +41,130 @@ 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, -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, + 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. @@ -133,41 +173,69 @@ 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) -> +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)}, + 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}}, []}. @@ -182,17 +250,21 @@ 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) -> diff --git a/deps/rabbitmq_mqtt/src/mqtt_machine_v0.erl b/deps/rabbitmq_mqtt/src/mqtt_machine_v0.erl index 4b32ac88dd..702053b7b5 100644 --- a/deps/rabbitmq_mqtt/src/mqtt_machine_v0.erl +++ b/deps/rabbitmq_mqtt/src/mqtt_machine_v0.erl @@ -9,10 +9,12 @@ -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{}. @@ -21,9 +23,10 @@ -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) -> @@ -35,53 +38,60 @@ 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. @@ -90,25 +100,29 @@ 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}}, []}. @@ -127,7 +141,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) -> diff --git a/deps/rabbitmq_mqtt/src/mqtt_node.erl b/deps/rabbitmq_mqtt/src/mqtt_node.erl index a6442fa85b..8a3c850b78 100644 --- a/deps/rabbitmq_mqtt/src/mqtt_node.erl +++ b/deps/rabbitmq_mqtt/src/mqtt_node.erl @@ -6,8 +6,15 @@ %% -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). @@ -25,8 +32,11 @@ 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 @@ -40,34 +50,41 @@ 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() -> @@ -78,14 +95,15 @@ 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). @@ -103,11 +121,13 @@ 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 -> @@ -128,12 +148,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), @@ -151,12 +171,13 @@ 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), diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt.erl index 5fb1861255..6e681ea3d2 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt.erl @@ -13,11 +13,13 @@ -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(), @@ -31,10 +33,11 @@ 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. @@ -52,9 +55,15 @@ 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. @@ -65,17 +74,23 @@ 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()]. @@ -86,9 +101,12 @@ 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() -> diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_collector.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_collector.erl index 5b5050d64e..438b6cf9f1 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_collector.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_collector.erl @@ -9,8 +9,13 @@ -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()}. @@ -21,7 +26,7 @@ register(ClientId, Pid) -> case ra:members(NodeId) of {ok, _, Leader} -> register(Leader, ClientId, Pid); - _ = Error -> + _ = Error -> Error end; Leader -> @@ -60,25 +65,31 @@ 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(), diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_confirms.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_confirms.erl index a31c94bd16..df56b14861 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_confirms.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_confirms.erl @@ -10,13 +10,15 @@ -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. @@ -39,26 +41,32 @@ 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}; @@ -71,24 +79,31 @@ reject(PktId, State0) {[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), diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_ff.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_ff.erl index 2432cc2ac5..e132128527 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_ff.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_ff.erl @@ -12,17 +12,19 @@ -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() -> diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_internal_event_handler.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_internal_event_handler.erl index 711c065acc..c01f4cc5b3 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_internal_event_handler.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_internal_event_handler.erl @@ -28,7 +28,9 @@ 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) -> diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_keepalive.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_keepalive.erl index 6b7b94b54c..cadfcffdad 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_keepalive.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_keepalive.erl @@ -1,23 +1,26 @@ -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() -> @@ -26,8 +29,9 @@ 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. @@ -36,20 +40,28 @@ start(Seconds, Sock) 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}]} -> @@ -57,8 +69,11 @@ handle(check, State = #state{socket = Sock, {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. @@ -74,10 +89,13 @@ 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. diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_packet.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_packet.erl index ededed8c5b..bcd31d3529 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_packet.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_packet.erl @@ -24,119 +24,148 @@ 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}; @@ -158,72 +187,109 @@ 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), @@ -236,9 +302,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) -> diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl index 58bccaa2f5..31c149cf5f 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl @@ -8,20 +8,31 @@ %% This module contains code that is common to MQTT and Web MQTT connections. -module(rabbit_mqtt_processor). --export([info/2, initial_state/2, initial_state/4, - process_packet/2, serialise/2, - terminate/4, handle_pre_hibernate/0, - handle_ra_event/2, handle_down/2, handle_queue_event/2, - proto_version_tuple/1, throttle/3, format_status/1, - update_trace/2]). +-export([ + info/2, + initial_state/2, initial_state/4, + process_packet/2, + serialise/2, + terminate/4, + handle_pre_hibernate/0, + handle_ra_event/2, + handle_down/2, + handle_queue_event/2, + proto_version_tuple/1, + throttle/3, + format_status/1, + update_trace/2 +]). %% for testing purposes -export([get_vhost_username/1, get_vhost/3, get_vhost_from_user_mapping/2]). -export_type([state/0]). --import(rabbit_mqtt_util, [mqtt_to_amqp/1, - amqp_to_mqtt/1]). +-import(rabbit_mqtt_util, [ + mqtt_to_amqp/1, + amqp_to_mqtt/1 +]). -include_lib("kernel/include/logger.hrl"). -include_lib("rabbit_common/include/rabbit.hrl"). @@ -33,57 +44,57 @@ -define(MAX_PERMISSION_CACHE_SIZE, 12). -define(CONSUMER_TAG, <<"mqtt">>). --record(auth_state, - {username :: rabbit_types:username(), - user :: #user{}, - vhost :: rabbit_types:vhost(), - authz_ctx :: #{binary() := binary()} - }). - --record(cfg, - {socket :: rabbit_net:socket(), - proto_ver :: option(mqtt310 | mqtt311), - clean_sess :: option(boolean()), - will_msg :: option(mqtt_msg()), - exchange :: option(rabbit_exchange:name()), - %% Set if client has at least one subscription with QoS 1. - queue_qos1 :: option(rabbit_amqqueue:name()), - %% Did the client ever sent us a PUBLISH packet? - published = false :: boolean(), - ssl_login_name :: none | binary(), - retainer_pid :: option(pid()), - delivery_flow :: flow | noflow, - trace_state :: option(rabbit_trace:state()), - prefetch :: non_neg_integer(), - client_id :: option(binary()), - conn_name :: option(binary()), - peer_addr :: inet:ip_address(), - host :: inet:ip_address(), - port :: inet:port_number(), - peer_host :: inet:ip_address(), - peer_port :: inet:port_number(), - connected_at :: pos_integer(), - send_fun :: fun((Packet :: tuple(), state()) -> term()) - }). - --record(state, - {cfg :: #cfg{}, - queue_states = rabbit_queue_type:init() :: rabbit_queue_type:state(), - %% Packet IDs published to queues but not yet confirmed. - unacked_client_pubs = rabbit_mqtt_confirms:init() :: rabbit_mqtt_confirms:state(), - %% Packet IDs published to MQTT subscribers but not yet acknowledged. - unacked_server_pubs = #{} :: #{packet_id() => QueueMsgId :: non_neg_integer()}, - %% Packet ID of next PUBLISH packet (with QoS > 0) sent from server to client. - %% (Not to be confused with packet IDs sent from client to server which can be the - %% same IDs because client and server assign IDs independently of each other.) - packet_id = 1 :: packet_id(), - subscriptions = #{} :: #{Topic :: binary() => QoS :: ?QOS_0..?QOS_1}, - auth_state :: option(#auth_state{}), - register_state :: option(registered | {pending, reference()}), - %% quorum queues and streams whose soft limit has been exceeded - queues_soft_limit_exceeded = sets:new([{version, 2}]) :: sets:set(), - qos0_messages_dropped = 0 :: non_neg_integer() - }). +-record(auth_state, { + username :: rabbit_types:username(), + user :: #user{}, + vhost :: rabbit_types:vhost(), + authz_ctx :: #{binary() := binary()} +}). + +-record(cfg, { + socket :: rabbit_net:socket(), + proto_ver :: option(mqtt310 | mqtt311), + clean_sess :: option(boolean()), + will_msg :: option(mqtt_msg()), + exchange :: option(rabbit_exchange:name()), + %% Set if client has at least one subscription with QoS 1. + queue_qos1 :: option(rabbit_amqqueue:name()), + %% Did the client ever sent us a PUBLISH packet? + published = false :: boolean(), + ssl_login_name :: none | binary(), + retainer_pid :: option(pid()), + delivery_flow :: flow | noflow, + trace_state :: option(rabbit_trace:state()), + prefetch :: non_neg_integer(), + client_id :: option(binary()), + conn_name :: option(binary()), + peer_addr :: inet:ip_address(), + host :: inet:ip_address(), + port :: inet:port_number(), + peer_host :: inet:ip_address(), + peer_port :: inet:port_number(), + connected_at :: pos_integer(), + send_fun :: fun((Packet :: tuple(), state()) -> term()) +}). + +-record(state, { + cfg :: #cfg{}, + queue_states = rabbit_queue_type:init() :: rabbit_queue_type:state(), + %% Packet IDs published to queues but not yet confirmed. + unacked_client_pubs = rabbit_mqtt_confirms:init() :: rabbit_mqtt_confirms:state(), + %% Packet IDs published to MQTT subscribers but not yet acknowledged. + unacked_server_pubs = #{} :: #{packet_id() => QueueMsgId :: non_neg_integer()}, + %% Packet ID of next PUBLISH packet (with QoS > 0) sent from server to client. + %% (Not to be confused with packet IDs sent from client to server which can be the + %% same IDs because client and server assign IDs independently of each other.) + packet_id = 1 :: packet_id(), + subscriptions = #{} :: #{Topic :: binary() => QoS :: ?QOS_0..?QOS_1}, + auth_state :: option(#auth_state{}), + register_state :: option(registered | {pending, reference()}), + %% quorum queues and streams whose soft limit has been exceeded + queues_soft_limit_exceeded = sets:new([{version, 2}]) :: sets:set(), + qos0_messages_dropped = 0 :: non_neg_integer() +}). -opaque state() :: #state{}. @@ -91,50 +102,62 @@ state(). initial_state(Socket, ConnectionName) -> {ok, {PeerAddr, _PeerPort}} = rabbit_net:peername(Socket), - initial_state(Socket, - ConnectionName, - fun serialise_and_send_to_client/2, - PeerAddr). - --spec initial_state(Socket :: any(), - ConnectionName :: binary(), - SendFun :: fun((mqtt_packet(), state()) -> any()), - PeerAddr :: inet:ip_address()) -> + initial_state( + Socket, + ConnectionName, + fun serialise_and_send_to_client/2, + PeerAddr + ). + +-spec initial_state( + Socket :: any(), + ConnectionName :: binary(), + SendFun :: fun((mqtt_packet(), state()) -> any()), + PeerAddr :: inet:ip_address() +) -> state(). initial_state(Socket, ConnectionName, SendFun, PeerAddr) -> - Flow = case rabbit_misc:get_env(rabbit, mirroring_flow_control, true) of - true -> flow; - false -> noflow - end, + Flow = + case rabbit_misc:get_env(rabbit, mirroring_flow_control, true) of + true -> flow; + false -> noflow + end, {ok, {PeerHost, PeerPort, Host, Port}} = rabbit_net:socket_ends(Socket, inbound), - #state{cfg = #cfg{socket = Socket, - conn_name = ConnectionName, - ssl_login_name = ssl_login_name(Socket), - send_fun = SendFun, - prefetch = rabbit_mqtt_util:env(prefetch), - delivery_flow = Flow, - connected_at = os:system_time(milli_seconds), - peer_addr = PeerAddr, - peer_host = PeerHost, - peer_port = PeerPort, - host = Host, - port = Port}}. + #state{ + cfg = #cfg{ + socket = Socket, + conn_name = ConnectionName, + ssl_login_name = ssl_login_name(Socket), + send_fun = SendFun, + prefetch = rabbit_mqtt_util:env(prefetch), + delivery_flow = Flow, + connected_at = os:system_time(milli_seconds), + peer_addr = PeerAddr, + peer_host = PeerHost, + peer_port = PeerPort, + host = Host, + port = Port + } + }. -spec process_packet(mqtt_packet(), state()) -> - {ok, state()} | - {stop, disconnect, state()} | - {error, Reason :: term(), state()}. -process_packet(#mqtt_packet{fixed = #mqtt_packet_fixed{type = Type}}, - State = #state{auth_state = undefined}) - when Type =/= ?CONNECT -> + {ok, state()} + | {stop, disconnect, state()} + | {error, Reason :: term(), state()}. +process_packet( + #mqtt_packet{fixed = #mqtt_packet_fixed{type = Type}}, + State = #state{auth_state = undefined} +) when + Type =/= ?CONNECT +-> {error, connect_expected, State}; process_packet(Packet = #mqtt_packet{fixed = #mqtt_packet_fixed{type = Type}}, State) -> process_request(Type, Packet, State). -spec process_request(packet_type(), mqtt_packet(), state()) -> - {ok, state()} | - {stop, disconnect, state()} | - {error, Reason :: term(), state()}. + {ok, state()} + | {stop, disconnect, state()} + | {error, Reason :: term(), state()}. process_request(?CONNECT, Packet, State = #state{cfg = #cfg{socket = Socket}}) -> %% Check whether peer closed the connection. %% For example, this can happen when connection was blocked because of resource @@ -145,68 +168,92 @@ process_request(?CONNECT, Packet, State = #state{cfg = #cfg{socket = Socket}}) - _ -> process_connect(Packet, State) end; - -process_request(?PUBACK, - #mqtt_packet{variable = #mqtt_packet_publish{packet_id = PacketId}}, - #state{unacked_server_pubs = U0, - queue_states = QStates0, - cfg = #cfg{queue_qos1 = QName}} = State) -> +process_request( + ?PUBACK, + #mqtt_packet{variable = #mqtt_packet_publish{packet_id = PacketId}}, + #state{ + unacked_server_pubs = U0, + queue_states = QStates0, + cfg = #cfg{queue_qos1 = QName} + } = State +) -> case maps:take(PacketId, U0) of {QMsgId, U} -> case rabbit_queue_type:settle(QName, complete, ?CONSUMER_TAG, [QMsgId], QStates0) of {ok, QStates, Actions} -> message_acknowledged(QName, State), - {ok, handle_queue_actions(Actions, State#state{unacked_server_pubs = U, - queue_states = QStates})}; + {ok, + handle_queue_actions(Actions, State#state{ + unacked_server_pubs = U, + queue_states = QStates + })}; {protocol_error, _ErrorType, _Reason, _ReasonArgs} = Err -> {error, Err, State} end; error -> {ok, State} end; - -process_request(?PUBLISH, - Packet = #mqtt_packet{ - fixed = Fixed = #mqtt_packet_fixed{qos = ?QOS_2}}, - State) -> +process_request( + ?PUBLISH, + Packet = #mqtt_packet{ + fixed = Fixed = #mqtt_packet_fixed{qos = ?QOS_2} + }, + State +) -> % Downgrade QOS_2 to QOS_1 - process_request(?PUBLISH, - Packet#mqtt_packet{ - fixed = Fixed#mqtt_packet_fixed{qos = ?QOS_1}}, - State); -process_request(?PUBLISH, - #mqtt_packet{ - fixed = #mqtt_packet_fixed{qos = Qos, - retain = Retain, - dup = Dup }, - variable = #mqtt_packet_publish{topic_name = Topic, - packet_id = PacketId }, - payload = Payload}, - State0 = #state{unacked_client_pubs = U, - cfg = #cfg{retainer_pid = RPid, - proto_ver = ProtoVer}}) -> + process_request( + ?PUBLISH, + Packet#mqtt_packet{ + fixed = Fixed#mqtt_packet_fixed{qos = ?QOS_1} + }, + State + ); +process_request( + ?PUBLISH, + #mqtt_packet{ + fixed = #mqtt_packet_fixed{ + qos = Qos, + retain = Retain, + dup = Dup + }, + variable = #mqtt_packet_publish{ + topic_name = Topic, + packet_id = PacketId + }, + payload = Payload + }, + State0 = #state{ + unacked_client_pubs = U, + cfg = #cfg{ + retainer_pid = RPid, + proto_ver = ProtoVer + } + } +) -> rabbit_global_counters:messages_received(ProtoVer, 1), State = maybe_increment_publisher(State0), Publish = fun() -> - Msg = #mqtt_msg{retain = Retain, - qos = Qos, - topic = Topic, - dup = Dup, - packet_id = PacketId, - payload = Payload}, - case publish_to_queues(Msg, State) of - {ok, _} = Ok -> - case Retain of - false -> - ok; - true -> - hand_off_to_retainer(RPid, Topic, Msg) - end, - Ok; - Error -> - Error - end - end, + Msg = #mqtt_msg{ + retain = Retain, + qos = Qos, + topic = Topic, + dup = Dup, + packet_id = PacketId, + payload = Payload + }, + case publish_to_queues(Msg, State) of + {ok, _} = Ok -> + case Retain of + false -> + ok; + true -> + hand_off_to_retainer(RPid, Topic, Msg) + end, + Ok; + Error -> + Error + end + end, case Qos of N when N > ?QOS_0 -> rabbit_global_counters:messages_received_confirm(ProtoVer, 1), @@ -222,157 +269,219 @@ process_request(?PUBLISH, _ -> publish_to_queues_with_checks(Topic, Publish, State) end; - -process_request(?SUBSCRIBE, - #mqtt_packet{ - variable = #mqtt_packet_subscribe{ - packet_id = SubscribePktId, - topic_table = Topics}, - payload = undefined}, - #state{cfg = #cfg{send_fun = SendFun, - retainer_pid = RPid}} = State0) -> +process_request( + ?SUBSCRIBE, + #mqtt_packet{ + variable = #mqtt_packet_subscribe{ + packet_id = SubscribePktId, + topic_table = Topics + }, + payload = undefined + }, + #state{ + cfg = #cfg{ + send_fun = SendFun, + retainer_pid = RPid + } + } = State0 +) -> ?LOG_DEBUG("Received a SUBSCRIBE for topic(s) ~p", [Topics]), {QosResponse, State1} = - lists:foldl( - fun(_Topic, {[?SUBACK_FAILURE | _] = L, S}) -> - %% Once a subscription failed, mark all following subscriptions - %% as failed instead of creating bindings because we are going - %% to close the client connection anyway. - {[?SUBACK_FAILURE | L], S}; - (#mqtt_topic{name = TopicName, - qos = TopicQos}, - {L, S0}) -> - QoS = supported_sub_qos(TopicQos), - case maybe_replace_old_sub(TopicName, QoS, S0) of - {ok, S1} -> - case ensure_queue(QoS, S1) of - {ok, Q} -> - QName = amqqueue:get_name(Q), - case bind(QName, TopicName, S1) of - {ok, _Output, S2 = #state{subscriptions = Subs}} -> - S3 = S2#state{subscriptions = maps:put(TopicName, QoS, Subs)}, - maybe_increment_consumer(S2, S3), - case self_consumes(Q) of - false -> - case consume(Q, QoS, S3) of - {ok, S4} -> - {[QoS | L], S4}; - {error, _Reason} -> - {[?SUBACK_FAILURE | L], S3} - end; - true -> - {[QoS | L], S3} - end; - {error, Reason, S2} -> - ?LOG_ERROR("Failed to bind ~s with topic ~s: ~p", - [rabbit_misc:rs(QName), TopicName, Reason]), - {[?SUBACK_FAILURE | L], S2} - end; - {error, _} -> - {[?SUBACK_FAILURE | L], S1} - end; - {error, _Reason, S1} -> - {[?SUBACK_FAILURE | L], S1} - end - end, {[], State0}, Topics), + lists:foldl( + fun + (_Topic, {[?SUBACK_FAILURE | _] = L, S}) -> + %% Once a subscription failed, mark all following subscriptions + %% as failed instead of creating bindings because we are going + %% to close the client connection anyway. + {[?SUBACK_FAILURE | L], S}; + ( + #mqtt_topic{ + name = TopicName, + qos = TopicQos + }, + {L, S0} + ) -> + QoS = supported_sub_qos(TopicQos), + case maybe_replace_old_sub(TopicName, QoS, S0) of + {ok, S1} -> + case ensure_queue(QoS, S1) of + {ok, Q} -> + QName = amqqueue:get_name(Q), + case bind(QName, TopicName, S1) of + {ok, _Output, S2 = #state{subscriptions = Subs}} -> + S3 = S2#state{ + subscriptions = maps:put(TopicName, QoS, Subs) + }, + maybe_increment_consumer(S2, S3), + case self_consumes(Q) of + false -> + case consume(Q, QoS, S3) of + {ok, S4} -> + {[QoS | L], S4}; + {error, _Reason} -> + {[?SUBACK_FAILURE | L], S3} + end; + true -> + {[QoS | L], S3} + end; + {error, Reason, S2} -> + ?LOG_ERROR( + "Failed to bind ~s with topic ~s: ~p", + [rabbit_misc:rs(QName), TopicName, Reason] + ), + {[?SUBACK_FAILURE | L], S2} + end; + {error, _} -> + {[?SUBACK_FAILURE | L], S1} + end; + {error, _Reason, S1} -> + {[?SUBACK_FAILURE | L], S1} + end + end, + {[], State0}, + Topics + ), SendFun( - #mqtt_packet{fixed = #mqtt_packet_fixed{type = ?SUBACK}, - variable = #mqtt_packet_suback{ - packet_id = SubscribePktId, - qos_table = QosResponse}}, - State1), + #mqtt_packet{ + fixed = #mqtt_packet_fixed{type = ?SUBACK}, + variable = #mqtt_packet_suback{ + packet_id = SubscribePktId, + qos_table = QosResponse + } + }, + State1 + ), case QosResponse of [?SUBACK_FAILURE | _] -> {error, subscribe_error, State1}; _ -> - State = lists:foldl(fun(Topic, S) -> - maybe_send_retained_message(RPid, Topic, S) - end, State1, Topics), + State = lists:foldl( + fun(Topic, S) -> + maybe_send_retained_message(RPid, Topic, S) + end, + State1, + Topics + ), {ok, State} end; - -process_request(?UNSUBSCRIBE, - #mqtt_packet{variable = #mqtt_packet_subscribe{packet_id = PacketId, - topic_table = Topics}, - payload = undefined}, - State0 = #state{cfg = #cfg{send_fun = SendFun}}) -> +process_request( + ?UNSUBSCRIBE, + #mqtt_packet{ + variable = #mqtt_packet_subscribe{ + packet_id = PacketId, + topic_table = Topics + }, + payload = undefined + }, + State0 = #state{cfg = #cfg{send_fun = SendFun}} +) -> ?LOG_DEBUG("Received an UNSUBSCRIBE for topic(s) ~p", [Topics]), State = lists:foldl( - fun(#mqtt_topic{name = TopicName}, #state{subscriptions = Subs0} = S0) -> - case maps:take(TopicName, Subs0) of - {QoS, Subs} -> - QName = queue_name(QoS, S0), - case unbind(QName, TopicName, S0) of - {ok, _, S1} -> - S = S1#state{subscriptions = Subs}, - maybe_decrement_consumer(S1, S), - S; - {error, Reason, S} -> - ?LOG_ERROR("Failed to unbind ~s with topic ~s: ~p", - [rabbit_misc:rs(QName), TopicName, Reason]), - S - end; - error -> - S0 - end - end, State0, Topics), + fun(#mqtt_topic{name = TopicName}, #state{subscriptions = Subs0} = S0) -> + case maps:take(TopicName, Subs0) of + {QoS, Subs} -> + QName = queue_name(QoS, S0), + case unbind(QName, TopicName, S0) of + {ok, _, S1} -> + S = S1#state{subscriptions = Subs}, + maybe_decrement_consumer(S1, S), + S; + {error, Reason, S} -> + ?LOG_ERROR( + "Failed to unbind ~s with topic ~s: ~p", + [rabbit_misc:rs(QName), TopicName, Reason] + ), + S + end; + error -> + S0 + end + end, + State0, + Topics + ), SendFun( - #mqtt_packet{fixed = #mqtt_packet_fixed {type = ?UNSUBACK}, - variable = #mqtt_packet_suback{packet_id = PacketId}}, - State), + #mqtt_packet{ + fixed = #mqtt_packet_fixed{type = ?UNSUBACK}, + variable = #mqtt_packet_suback{packet_id = PacketId} + }, + State + ), {ok, State}; - -process_request(?PINGREQ, #mqtt_packet{}, - State = #state{cfg = #cfg{send_fun = SendFun, - client_id = ClientId}}) -> +process_request( + ?PINGREQ, + #mqtt_packet{}, + State = #state{ + cfg = #cfg{ + send_fun = SendFun, + client_id = ClientId + } + } +) -> ?LOG_DEBUG("Received a PINGREQ, client ID: ~s", [ClientId]), SendFun( - #mqtt_packet{fixed = #mqtt_packet_fixed{type = ?PINGRESP}}, - State), + #mqtt_packet{fixed = #mqtt_packet_fixed{type = ?PINGRESP}}, + State + ), ?LOG_DEBUG("Sent a PINGRESP, client ID: ~s", [ClientId]), {ok, State}; - process_request(?DISCONNECT, #mqtt_packet{}, State) -> ?LOG_DEBUG("Received a DISCONNECT"), {stop, disconnect, State}. -process_connect(#mqtt_packet{ - variable = #mqtt_packet_connect{ - username = Username, - proto_ver = ProtoVersion, - clean_sess = CleanSess, - client_id = ClientId, - keep_alive = Keepalive} = PacketConnect}, - State0 = #state{cfg = #cfg{send_fun = SendFun}}) -> - ?LOG_DEBUG("Received a CONNECT, client ID: ~s, username: ~s, " - "clean session: ~s, protocol version: ~p, keepalive: ~p", - [ClientId, Username, CleanSess, ProtoVersion, Keepalive]), +process_connect( + #mqtt_packet{ + variable = + #mqtt_packet_connect{ + username = Username, + proto_ver = ProtoVersion, + clean_sess = CleanSess, + client_id = ClientId, + keep_alive = Keepalive + } = PacketConnect + }, + State0 = #state{cfg = #cfg{send_fun = SendFun}} +) -> + ?LOG_DEBUG( + "Received a CONNECT, client ID: ~s, username: ~s, " + "clean session: ~s, protocol version: ~p, keepalive: ~p", + [ClientId, Username, CleanSess, ProtoVersion, Keepalive] + ), {ReturnCode, SessPresent, State} = - case rabbit_misc:pipeline([fun check_protocol_version/1, - fun check_client_id/1, - fun check_credentials/2, - fun login/2, - fun register_client/2, - fun start_keepalive/2, - fun notify_connection_created/1, - fun init_trace/2, - fun handle_clean_sess_qos0/2, - fun handle_clean_sess_qos1/2, - fun cache_subscriptions/2 - ], - PacketConnect, State0) of - {ok, SessPresent0, State1} -> - {?CONNACK_ACCEPT, SessPresent0, State1}; - {error, ConnectionRefusedReturnCode, State1} -> - {ConnectionRefusedReturnCode, false, State1} - end, - Response = #mqtt_packet{fixed = #mqtt_packet_fixed{type = ?CONNACK}, - variable = #mqtt_packet_connack{ - session_present = SessPresent, - return_code = ReturnCode}}, + case + rabbit_misc:pipeline( + [ + fun check_protocol_version/1, + fun check_client_id/1, + fun check_credentials/2, + fun login/2, + fun register_client/2, + fun start_keepalive/2, + fun notify_connection_created/1, + fun init_trace/2, + fun handle_clean_sess_qos0/2, + fun handle_clean_sess_qos1/2, + fun cache_subscriptions/2 + ], + PacketConnect, + State0 + ) + of + {ok, SessPresent0, State1} -> + {?CONNACK_ACCEPT, SessPresent0, State1}; + {error, ConnectionRefusedReturnCode, State1} -> + {ConnectionRefusedReturnCode, false, State1} + end, + Response = #mqtt_packet{ + fixed = #mqtt_packet_fixed{type = ?CONNACK}, + variable = #mqtt_packet_connack{ + session_present = SessPresent, + return_code = ReturnCode + } + }, SendFun(Response, State), return_connack(ReturnCode, State). @@ -384,16 +493,26 @@ check_protocol_version(#mqtt_packet_connect{proto_ver = ProtoVersion}) -> {error, ?CONNACK_UNACCEPTABLE_PROTO_VER} end. -check_client_id(#mqtt_packet_connect{clean_sess = false, - client_id = <<>>}) -> +check_client_id(#mqtt_packet_connect{ + clean_sess = false, + client_id = <<>> +}) -> {error, ?CONNACK_ID_REJECTED}; check_client_id(_) -> ok. -check_credentials(Packet = #mqtt_packet_connect{username = Username, - password = Password}, - State = #state{cfg = #cfg{ssl_login_name = SslLoginName, - peer_addr = PeerAddr}}) -> +check_credentials( + Packet = #mqtt_packet_connect{ + username = Username, + password = Password + }, + State = #state{ + cfg = #cfg{ + ssl_login_name = SslLoginName, + peer_addr = PeerAddr + } + } +) -> Ip = list_to_binary(inet:ntoa(PeerAddr)), case creds(Username, Password, SslLoginName) of nocreds -> @@ -412,15 +531,21 @@ check_credentials(Packet = #mqtt_packet_connect{username = Username, {ok, {UserBin, PassBin, Packet}, State} end. -login({UserBin, PassBin, - Packet = #mqtt_packet_connect{client_id = ClientId0, - clean_sess = CleanSess}}, - State0 = #state{cfg = Cfg0}) -> +login( + {UserBin, PassBin, + Packet = #mqtt_packet_connect{ + client_id = ClientId0, + clean_sess = CleanSess + }}, + State0 = #state{cfg = Cfg0} +) -> ClientId = ensure_client_id(ClientId0), case process_login(UserBin, PassBin, ClientId, State0) of {ok, State} -> - Cfg = Cfg0#cfg{client_id = ClientId, - clean_sess = CleanSess}, + Cfg = Cfg0#cfg{ + client_id = ClientId, + clean_sess = CleanSess + }, {ok, Packet, State#state{cfg = Cfg}}; {error, _ConnectionRefusedReturnCode, _State} = Err -> Err @@ -429,28 +554,39 @@ login({UserBin, PassBin, -spec ensure_client_id(binary()) -> binary(). ensure_client_id(<<>>) -> rabbit_data_coercion:to_binary( - rabbit_misc:base64url( - rabbit_guid:gen_secure())); -ensure_client_id(ClientId) - when is_binary(ClientId) -> + rabbit_misc:base64url( + rabbit_guid:gen_secure() + ) + ); +ensure_client_id(ClientId) when + is_binary(ClientId) +-> ClientId. -register_client(Packet = #mqtt_packet_connect{proto_ver = ProtoVersion}, - State = #state{auth_state = #auth_state{vhost = VHost}, - cfg = Cfg0 = #cfg{client_id = ClientId}}) -> +register_client( + Packet = #mqtt_packet_connect{proto_ver = ProtoVersion}, + State = #state{ + auth_state = #auth_state{vhost = VHost}, + cfg = Cfg0 = #cfg{client_id = ClientId} + } +) -> NewProcState = - fun(RegisterState) -> + fun(RegisterState) -> rabbit_mqtt_util:register_clientid(VHost, ClientId), RetainerPid = rabbit_mqtt_retainer_sup:child_for_vhost(VHost), ExchangeBin = rabbit_mqtt_util:env(exchange), ExchangeName = rabbit_misc:r(VHost, exchange, ExchangeBin), - Cfg = Cfg0#cfg{exchange = ExchangeName, - will_msg = make_will_msg(Packet), - retainer_pid = RetainerPid, - proto_ver = proto_integer_to_atom(ProtoVersion)}, - State#state{cfg = Cfg, - register_state = RegisterState} - end, + Cfg = Cfg0#cfg{ + exchange = ExchangeName, + will_msg = make_will_msg(Packet), + retainer_pid = RetainerPid, + proto_ver = proto_integer_to_atom(ProtoVersion) + }, + State#state{ + cfg = Cfg, + register_state = RegisterState + } + end, case rabbit_mqtt_ff:track_client_id_in_ra() of true -> case rabbit_mqtt_collector:register(ClientId, self()) of @@ -458,8 +594,10 @@ register_client(Packet = #mqtt_packet_connect{proto_ver = ProtoVersion}, {ok, NewProcState({pending, Corr})}; {error, _} = Err -> %% e.g. this node was removed from the MQTT cluster members - ?LOG_ERROR("MQTT cannot accept a connection: client ID tracker is unavailable: ~p", - [Err]), + ?LOG_ERROR( + "MQTT cannot accept a connection: client ID tracker is unavailable: ~p", + [Err] + ), {error, ?CONNACK_SERVER_UNAVAILABLE} end; false -> @@ -475,17 +613,25 @@ init_trace(#mqtt_packet_connect{}, State = #state{cfg = #cfg{conn_name = ConnNam {ok, update_trace(ConnName, State)}. -spec update_trace(binary(), state()) -> state(). -update_trace(ConnName0, State = #state{cfg = Cfg0, - auth_state = #auth_state{vhost = VHost}}) -> - ConnName = case rabbit_trace:enabled(VHost) of - true -> - ConnName0; - false -> - %% We won't need conn_name. Use less memmory by setting to undefined. - undefined - end, - Cfg = Cfg0#cfg{conn_name = ConnName, - trace_state = rabbit_trace:init(VHost)}, +update_trace( + ConnName0, + State = #state{ + cfg = Cfg0, + auth_state = #auth_state{vhost = VHost} + } +) -> + ConnName = + case rabbit_trace:enabled(VHost) of + true -> + ConnName0; + false -> + %% We won't need conn_name. Use less memmory by setting to undefined. + undefined + end, + Cfg = Cfg0#cfg{ + conn_name = ConnName, + trace_state = rabbit_trace:init(VHost) + }, State#state{cfg = Cfg}. return_connack(?CONNACK_ACCEPT, S) -> @@ -507,13 +653,18 @@ self_consumes(Queue) -> ?QUEUE_TYPE_QOS_0 -> false; _ -> - lists:any(fun(Consumer) -> - element(1, Consumer) =:= self() - end, rabbit_amqqueue:consumers(Queue)) + lists:any( + fun(Consumer) -> + element(1, Consumer) =:= self() + end, + rabbit_amqqueue:consumers(Queue) + ) end. -start_keepalive(#mqtt_packet_connect{keep_alive = Seconds}, - #state{cfg = #cfg{socket = Socket}}) -> +start_keepalive( + #mqtt_packet_connect{keep_alive = Seconds}, + #state{cfg = #cfg{socket = Socket}} +) -> ok = rabbit_mqtt_keepalive:start(Seconds, Socket). handle_clean_sess_qos0(#mqtt_packet_connect{}, State) -> @@ -522,11 +673,18 @@ handle_clean_sess_qos0(#mqtt_packet_connect{}, State) -> handle_clean_sess_qos1(QoS0SessPresent, State) -> handle_clean_sess(QoS0SessPresent, ?QOS_1, State). -handle_clean_sess(_, QoS, - State = #state{cfg = #cfg{clean_sess = true}, - auth_state = #auth_state{user = User, - username = Username, - authz_ctx = AuthzCtx}}) -> +handle_clean_sess( + _, + QoS, + State = #state{ + cfg = #cfg{clean_sess = true}, + auth_state = #auth_state{ + user = User, + username = Username, + authz_ctx = AuthzCtx + } + } +) -> %% "If the Server accepts a connection with CleanSession set to 1, the Server %% MUST set Session Present to 0 in the CONNACK packet [MQTT-3.2.2-1]. SessPresent = false, @@ -544,9 +702,16 @@ handle_clean_sess(_, QoS, {error, ?CONNACK_NOT_AUTHORIZED} end end; -handle_clean_sess(SessPresent, QoS, - State0 = #state{cfg = #cfg{clean_sess = false, - proto_ver = ProtoVer}}) -> +handle_clean_sess( + SessPresent, + QoS, + State0 = #state{ + cfg = #cfg{ + clean_sess = false, + proto_ver = ProtoVer + } + } +) -> case get_queue(QoS, State0) of {error, _} -> %% Queue will be created later when client subscribes. @@ -571,8 +736,8 @@ handle_clean_sess(SessPresent, QoS, end. -spec get_queue(qos(), state()) -> - {ok, amqqueue:amqqueue()} | - {error, not_found | {resource_locked, amqqueue:amqqueue()}}. + {ok, amqqueue:amqqueue()} + | {error, not_found | {resource_locked, amqqueue:amqqueue()}}. get_queue(QoS, State) -> QName = queue_name(QoS, State), case rabbit_amqqueue:lookup(QName) of @@ -592,8 +757,10 @@ get_queue(QoS, State) -> queue_name(?QOS_1, #state{cfg = #cfg{queue_qos1 = #resource{kind = queue} = Name}}) -> Name; -queue_name(QoS, #state{auth_state = #auth_state{vhost = VHost}, - cfg = #cfg{client_id = ClientId}}) -> +queue_name(QoS, #state{ + auth_state = #auth_state{vhost = VHost}, + cfg = #cfg{client_id = ClientId} +}) -> QNameBin = rabbit_mqtt_util:queue_name_bin(ClientId, QoS), rabbit_misc:r(VHost, queue, QNameBin). @@ -602,23 +769,26 @@ queue_name(QoS, #state{auth_state = #auth_state{vhost = VHost}, cache_subscriptions(_SessionPresent = _SubscriptionsPresent = true, State) -> SubsQos0 = topic_names(?QOS_0, State), SubsQos1 = topic_names(?QOS_1, State), - Subs = maps:merge(maps:from_keys(SubsQos0, ?QOS_0), - maps:from_keys(SubsQos1, ?QOS_1)), + Subs = maps:merge( + maps:from_keys(SubsQos0, ?QOS_0), + maps:from_keys(SubsQos1, ?QOS_1) + ), {ok, State#state{subscriptions = Subs}}; cache_subscriptions(_, _) -> ok. topic_names(QoS, State = #state{cfg = #cfg{exchange = Exchange}}) -> Bindings = - rabbit_binding:list_for_source_and_destination( - Exchange, - queue_name(QoS, State), - %% Querying table rabbit_route is catastrophic for CPU usage. - %% Querying table rabbit_reverse_route is acceptable because - %% the source exchange is always the same in the MQTT plugin whereas - %% the destination queue is different for each MQTT client and - %% rabbit_reverse_route is sorted by destination queue. - _Reverse = true), + rabbit_binding:list_for_source_and_destination( + Exchange, + queue_name(QoS, State), + %% Querying table rabbit_route is catastrophic for CPU usage. + %% Querying table rabbit_reverse_route is acceptable because + %% the source exchange is always the same in the MQTT plugin whereas + %% the destination queue is different for each MQTT client and + %% rabbit_reverse_route is sorted by destination queue. + _Reverse = true + ), lists:map(fun(B) -> amqp_to_mqtt(B#binding.key) end, Bindings). %% "If a Server receives a SUBSCRIBE Packet containing a Topic Filter that is identical @@ -628,21 +798,24 @@ topic_names(QoS, State = #state{cfg = #cfg{exchange = Exchange}}) -> %% could be different." [MQTT-3.8.4-3]. maybe_replace_old_sub(TopicName, QoS, State = #state{subscriptions = Subs}) -> case Subs of - #{TopicName := OldQoS} - when OldQoS =/= QoS -> + #{TopicName := OldQoS} when + OldQoS =/= QoS + -> replace_old_sub(OldQoS, TopicName, State); _ -> {ok, State} end. -replace_old_sub(QoS, TopicName, State0)-> +replace_old_sub(QoS, TopicName, State0) -> QName = queue_name(QoS, State0), case unbind(QName, TopicName, State0) of {ok, _Output, State} -> {ok, State}; {error, Reason, _State} = Err -> - ?LOG_ERROR("Failed to unbind ~s with topic '~s': ~p", - [rabbit_misc:rs(QName), TopicName, Reason]), + ?LOG_ERROR( + "Failed to unbind ~s with topic '~s': ~p", + [rabbit_misc:rs(QName), TopicName, Reason] + ), Err end. @@ -656,85 +829,124 @@ hand_off_to_retainer(RetainerPid, Topic0, Msg) -> rabbit_mqtt_retainer:retain(RetainerPid, Topic1, Msg), ok. -maybe_send_retained_message(RPid, #mqtt_topic{name = Topic0, qos = SubscribeQos}, - State0 = #state{packet_id = PacketId0, - cfg = #cfg{send_fun = SendFun}}) -> +maybe_send_retained_message( + RPid, + #mqtt_topic{name = Topic0, qos = SubscribeQos}, + State0 = #state{ + packet_id = PacketId0, + cfg = #cfg{send_fun = SendFun} + } +) -> Topic1 = amqp_to_mqtt(Topic0), case rabbit_mqtt_retainer:fetch(RPid, Topic1) of undefined -> State0; Msg -> Qos = effective_qos(Msg#mqtt_msg.qos, SubscribeQos), - {PacketId, State} = case Qos of - ?QOS_0 -> - {undefined, State0}; - ?QOS_1 -> - {PacketId0, State0#state{packet_id = increment_packet_id(PacketId0)}} - end, + {PacketId, State} = + case Qos of + ?QOS_0 -> + {undefined, State0}; + ?QOS_1 -> + {PacketId0, State0#state{packet_id = increment_packet_id(PacketId0)}} + end, SendFun( - #mqtt_packet{fixed = #mqtt_packet_fixed{ - type = ?PUBLISH, - qos = Qos, - dup = false, - retain = Msg#mqtt_msg.retain - }, variable = #mqtt_packet_publish{ - packet_id = PacketId, - topic_name = Topic1 - }, - payload = Msg#mqtt_msg.payload}, - State), + #mqtt_packet{ + fixed = #mqtt_packet_fixed{ + type = ?PUBLISH, + qos = Qos, + dup = false, + retain = Msg#mqtt_msg.retain + }, + variable = #mqtt_packet_publish{ + packet_id = PacketId, + topic_name = Topic1 + }, + payload = Msg#mqtt_msg.payload + }, + State + ), State end. -make_will_msg(#mqtt_packet_connect{will_flag = false}) -> +make_will_msg(#mqtt_packet_connect{will_flag = false}) -> undefined; -make_will_msg(#mqtt_packet_connect{will_retain = Retain, - will_qos = Qos, - will_topic = Topic, - will_msg = Msg}) -> - #mqtt_msg{retain = Retain, - qos = Qos, - topic = Topic, - dup = false, - payload = Msg}. - -process_login(_UserBin, _PassBin, ClientId, - #state{cfg = #cfg{peer_addr = Addr}, - auth_state = #auth_state{username = Username, - user = User, - vhost = VHost - }} = State) - when Username =/= undefined, User =/= undefined, VHost =/= underfined -> +make_will_msg(#mqtt_packet_connect{ + will_retain = Retain, + will_qos = Qos, + will_topic = Topic, + will_msg = Msg +}) -> + #mqtt_msg{ + retain = Retain, + qos = Qos, + topic = Topic, + dup = false, + payload = Msg + }. + +process_login( + _UserBin, + _PassBin, + ClientId, + #state{ + cfg = #cfg{peer_addr = Addr}, + auth_state = #auth_state{ + username = Username, + user = User, + vhost = VHost + } + } = State +) when + Username =/= undefined, User =/= undefined, VHost =/= underfined +-> rabbit_core_metrics:auth_attempt_failed(list_to_binary(inet:ntoa(Addr)), Username, mqtt), ?LOG_ERROR( - "MQTT detected duplicate connect attempt for client ID '~ts', user '~ts', vhost '~ts'", - [ClientId, Username, VHost]), + "MQTT detected duplicate connect attempt for client ID '~ts', user '~ts', vhost '~ts'", + [ClientId, Username, VHost] + ), {error, ?CONNACK_ID_REJECTED, State}; -process_login(UserBin, PassBin, ClientId, - #state{auth_state = undefined, - cfg = #cfg{socket = Sock, - ssl_login_name = SslLoginName, - peer_addr = Addr - }} = State0) -> +process_login( + UserBin, + PassBin, + ClientId, + #state{ + auth_state = undefined, + cfg = #cfg{ + socket = Sock, + ssl_login_name = SslLoginName, + peer_addr = Addr + } + } = State0 +) -> {ok, {_PeerHost, _PeerPort, _Host, Port}} = rabbit_net:socket_ends(Sock, inbound), {VHostPickedUsing, {VHost, UsernameBin}} = get_vhost(UserBin, SslLoginName, Port), - ?LOG_DEBUG("MQTT vhost picked using ~s", - [human_readable_vhost_lookup_strategy(VHostPickedUsing)]), + ?LOG_DEBUG( + "MQTT vhost picked using ~s", + [human_readable_vhost_lookup_strategy(VHostPickedUsing)] + ), RemoteIpAddressBin = list_to_binary(inet:ntoa(Addr)), - Input = #{vhost => VHost, - username_bin => UsernameBin, - pass_bin => PassBin, - client_id => ClientId}, - case rabbit_misc:pipeline( - [fun check_vhost_exists/1, - fun check_vhost_connection_limit/1, - fun check_vhost_alive/1, - fun check_user_login/2, - fun check_user_connection_limit/1, - fun check_vhost_access/2, - fun check_user_loopback/2 - ], - Input, State0) of + Input = #{ + vhost => VHost, + username_bin => UsernameBin, + pass_bin => PassBin, + client_id => ClientId + }, + case + rabbit_misc:pipeline( + [ + fun check_vhost_exists/1, + fun check_vhost_connection_limit/1, + fun check_vhost_alive/1, + fun check_user_login/2, + fun check_user_connection_limit/1, + fun check_vhost_access/2, + fun check_user_loopback/2 + ], + Input, + State0 + ) + of {ok, _Output, State} -> rabbit_core_metrics:auth_attempt_succeeded(RemoteIpAddressBin, UsernameBin, mqtt), {ok, State}; @@ -743,134 +955,174 @@ process_login(UserBin, PassBin, ClientId, Err end. -check_vhost_exists(#{vhost := VHost, - username_bin := UsernameBin}) -> +check_vhost_exists(#{ + vhost := VHost, + username_bin := UsernameBin +}) -> case rabbit_vhost:exists(VHost) of - true -> + true -> ok; false -> - ?LOG_ERROR("MQTT login failed for user '~s': virtual host '~s' does not exist", - [UsernameBin, VHost]), + ?LOG_ERROR( + "MQTT login failed for user '~s': virtual host '~s' does not exist", + [UsernameBin, VHost] + ), {error, ?CONNACK_BAD_CREDENTIALS} end. -check_vhost_connection_limit(#{vhost := VHost, - client_id := ClientId, - username_bin := Username}) -> +check_vhost_connection_limit(#{ + vhost := VHost, + client_id := ClientId, + username_bin := Username +}) -> case rabbit_vhost_limit:is_over_connection_limit(VHost) of false -> ok; {true, Limit} -> ?LOG_ERROR( - "Failed to create MQTT connection because vhost connection limit is reached; " - "vhost: '~s'; connection limit: ~p; user: '~s'; client ID '~s'", - [VHost, Limit, Username, ClientId]), + "Failed to create MQTT connection because vhost connection limit is reached; " + "vhost: '~s'; connection limit: ~p; user: '~s'; client ID '~s'", + [VHost, Limit, Username, ClientId] + ), {error, ?CONNACK_NOT_AUTHORIZED} end. -check_vhost_alive(#{vhost := VHost, - client_id := ClientId, - username_bin := UsernameBin}) -> +check_vhost_alive(#{ + vhost := VHost, + client_id := ClientId, + username_bin := UsernameBin +}) -> case rabbit_vhost_sup_sup:is_vhost_alive(VHost) of - true -> + true -> ok; false -> ?LOG_ERROR( - "Failed to create MQTT connection because vhost is down; " - "vhost: ~s; user: ~s; client ID: ~s", - [VHost, UsernameBin, ClientId]), + "Failed to create MQTT connection because vhost is down; " + "vhost: ~s; user: ~s; client ID: ~s", + [VHost, UsernameBin, ClientId] + ), {error, ?CONNACK_NOT_AUTHORIZED} end. -check_user_login(#{vhost := VHost, - username_bin := UsernameBin, - pass_bin := PassBin, - client_id := ClientId - } = In, State) -> - AuthProps = case PassBin of - none -> - %% SSL user name provided. - %% Authenticating using username only. - []; - _ -> - [{password, PassBin}, - {vhost, VHost}, - {client_id, ClientId}] - end, - case rabbit_access_control:check_user_login( - UsernameBin, AuthProps) of +check_user_login( + #{ + vhost := VHost, + username_bin := UsernameBin, + pass_bin := PassBin, + client_id := ClientId + } = In, + State +) -> + AuthProps = + case PassBin of + none -> + %% SSL user name provided. + %% Authenticating using username only. + []; + _ -> + [ + {password, PassBin}, + {vhost, VHost}, + {client_id, ClientId} + ] + end, + case + rabbit_access_control:check_user_login( + UsernameBin, AuthProps + ) + of {ok, User = #user{username = Username}} -> notify_auth_result(user_authentication_success, Username, State), {ok, maps:put(user, User, In), State}; {refused, Username, Msg, Args} -> ?LOG_ERROR( - "Error on MQTT connection ~p~n" - "access refused for user '~s' in vhost '~s' " - ++ Msg, - [self(), Username, VHost] ++ Args), + "Error on MQTT connection ~p~n" + "access refused for user '~s' in vhost '~s' " ++ + Msg, + [self(), Username, VHost] ++ Args + ), notify_auth_result(user_authentication_failure, Username, State), {error, ?CONNACK_BAD_CREDENTIALS} end. notify_auth_result(AuthResult, Username, #state{cfg = #cfg{conn_name = ConnName}}) -> rabbit_event:notify( - AuthResult, - [{name, Username}, - {connection_name, ConnName}, - {connection_type, network}]). - -check_user_connection_limit(#{user := #user{username = Username}, - client_id := ClientId}) -> + AuthResult, + [ + {name, Username}, + {connection_name, ConnName}, + {connection_type, network} + ] + ). + +check_user_connection_limit(#{ + user := #user{username = Username}, + client_id := ClientId +}) -> case rabbit_auth_backend_internal:is_over_connection_limit(Username) of false -> ok; {true, Limit} -> ?LOG_ERROR( - "Failed to create MQTT connection because user connection limit is reached; " - "user: '~s'; connection limit: ~p; client ID '~s'", - [Username, Limit, ClientId]), + "Failed to create MQTT connection because user connection limit is reached; " + "user: '~s'; connection limit: ~p; client ID '~s'", + [Username, Limit, ClientId] + ), {error, ?CONNACK_NOT_AUTHORIZED} end. - -check_vhost_access(#{vhost := VHost, - client_id := ClientId, - user := User = #user{username = Username} - } = In, - #state{cfg = #cfg{peer_addr = PeerAddr}} = State) -> +check_vhost_access( + #{ + vhost := VHost, + client_id := ClientId, + user := User = #user{username = Username} + } = In, + #state{cfg = #cfg{peer_addr = PeerAddr}} = State +) -> AuthzCtx = #{<<"client_id">> => ClientId}, - try rabbit_access_control:check_vhost_access( - User, - VHost, - {ip, PeerAddr}, - AuthzCtx) of + try + rabbit_access_control:check_vhost_access( + User, + VHost, + {ip, PeerAddr}, + AuthzCtx + ) + of ok -> {ok, maps:put(authz_ctx, AuthzCtx, In), State} - catch exit:#amqp_error{name = not_allowed} -> - ?LOG_ERROR( + catch + exit:#amqp_error{name = not_allowed} -> + ?LOG_ERROR( "Error on MQTT connection ~p~n" "access refused for user '~s'", - [self(), Username]), - {error, ?CONNACK_NOT_AUTHORIZED} + [self(), Username] + ), + {error, ?CONNACK_NOT_AUTHORIZED} end. -check_user_loopback(#{vhost := VHost, - username_bin := UsernameBin, - user := User, - authz_ctx := AuthzCtx - }, - #state{cfg = #cfg{peer_addr = PeerAddr}} = State) -> +check_user_loopback( + #{ + vhost := VHost, + username_bin := UsernameBin, + user := User, + authz_ctx := AuthzCtx + }, + #state{cfg = #cfg{peer_addr = PeerAddr}} = State +) -> case rabbit_access_control:check_user_loopback(UsernameBin, PeerAddr) of ok -> - AuthState = #auth_state{user = User, - username = UsernameBin, - vhost = VHost, - authz_ctx = AuthzCtx}, + AuthState = #auth_state{ + user = User, + username = UsernameBin, + vhost = VHost, + authz_ctx = AuthzCtx + }, {ok, State#state{auth_state = AuthState}}; not_allowed -> ?LOG_WARNING( - "MQTT login failed: user '~s' can only connect via localhost", - [UsernameBin]), + "MQTT login failed: user '~s' can only connect via localhost", + [UsernameBin] + ), {error, ?CONNACK_NOT_AUTHORIZED} end. @@ -883,12 +1135,12 @@ get_vhost(UserBin, SslLogin, Port) -> get_vhost_no_ssl(UserBin, Port) -> case vhost_in_username(UserBin) of - true -> + true -> {vhost_in_username_or_default, get_vhost_username(UserBin)}; false -> PortVirtualHostMapping = rabbit_runtime_parameters:value_global( - mqtt_port_to_vhost_mapping - ), + mqtt_port_to_vhost_mapping + ), case get_vhost_from_port_mapping(Port, PortVirtualHostMapping) of undefined -> {default_vhost, {rabbit_mqtt_util:env(vhost), UserBin}}; @@ -899,13 +1151,13 @@ get_vhost_no_ssl(UserBin, Port) -> get_vhost_ssl(UserBin, SslLoginName, Port) -> UserVirtualHostMapping = rabbit_runtime_parameters:value_global( - mqtt_default_vhosts - ), + mqtt_default_vhosts + ), case get_vhost_from_user_mapping(SslLoginName, UserVirtualHostMapping) of undefined -> PortVirtualHostMapping = rabbit_runtime_parameters:value_global( - mqtt_port_to_vhost_mapping - ), + mqtt_port_to_vhost_mapping + ), case get_vhost_from_port_mapping(Port, PortVirtualHostMapping) of undefined -> {vhost_in_username_or_default, get_vhost_username(UserBin)}; @@ -918,24 +1170,26 @@ get_vhost_ssl(UserBin, SslLoginName, Port) -> vhost_in_username(UserBin) -> case application:get_env(?APP_NAME, ignore_colons_in_username) of - {ok, true} -> false; + {ok, true} -> + false; _ -> %% split at the last colon, disallowing colons in username case re:split(UserBin, ":(?!.*?:)") of - [_, _] -> true; - [UserBin] -> false + [_, _] -> true; + [UserBin] -> false end end. get_vhost_username(UserBin) -> Default = {rabbit_mqtt_util:env(vhost), UserBin}, case application:get_env(?APP_NAME, ignore_colons_in_username) of - {ok, true} -> Default; + {ok, true} -> + Default; _ -> %% split at the last colon, disallowing colons in username case re:split(UserBin, ":(?!.*?:)") of - [Vhost, UserName] -> {Vhost, UserName}; - [UserBin] -> Default + [Vhost, UserName] -> {Vhost, UserName}; + [UserBin] -> Default end end. @@ -954,12 +1208,13 @@ get_vhost_from_port_mapping(_Port, not_found) -> undefined; get_vhost_from_port_mapping(Port, Mapping) -> M = rabbit_data_coercion:to_proplist(Mapping), - Res = case rabbit_misc:pget(rabbit_data_coercion:to_binary(Port), M) of - undefined -> - undefined; - VHost -> - VHost - end, + Res = + case rabbit_misc:pget(rabbit_data_coercion:to_binary(Port), M) of + undefined -> + undefined; + VHost -> + VHost + end, Res. human_readable_vhost_lookup_strategy(vhost_in_username_or_default) -> @@ -972,13 +1227,14 @@ human_readable_vhost_lookup_strategy(default_vhost) -> "plugin configuration or default". creds(User, Pass, SSLLoginName) -> - DefaultUser = rabbit_mqtt_util:env(default_user), - DefaultPass = rabbit_mqtt_util:env(default_pass), - {ok, Anon} = application:get_env(?APP_NAME, allow_anonymous), + DefaultUser = rabbit_mqtt_util:env(default_user), + DefaultPass = rabbit_mqtt_util:env(default_pass), + {ok, Anon} = application:get_env(?APP_NAME, allow_anonymous), {ok, TLSAuth} = application:get_env(?APP_NAME, ssl_cert_login), - HaveDefaultCreds = Anon =:= true andalso - is_binary(DefaultUser) andalso - is_binary(DefaultPass), + HaveDefaultCreds = + Anon =:= true andalso + is_binary(DefaultUser) andalso + is_binary(DefaultPass), CredentialsProvided = User =/= undefined orelse Pass =/= undefined, CorrectCredentials = is_binary(User) andalso is_binary(Pass), @@ -986,15 +1242,15 @@ creds(User, Pass, SSLLoginName) -> case {CredentialsProvided, CorrectCredentials, SSLLoginProvided, HaveDefaultCreds} of %% Username and password take priority - {true, true, _, _} -> {User, Pass}; + {true, true, _, _} -> {User, Pass}; %% Either username or password is provided - {true, false, _, _} -> {invalid_creds, {User, Pass}}; + {true, false, _, _} -> {invalid_creds, {User, Pass}}; %% rabbitmq_mqtt.ssl_cert_login is true. SSL user name provided. %% Authenticating using username only. - {false, false, true, _} -> {SSLLoginName, none}; + {false, false, true, _} -> {SSLLoginName, none}; %% Anonymous connection uses default credentials {false, false, false, true} -> {DefaultUser, DefaultPass}; - _ -> nocreds + _ -> nocreds end. supported_sub_qos(?QOS_0) -> ?QOS_0; @@ -1011,8 +1267,10 @@ ensure_queue(QoS, State = #state{auth_state = #auth_state{user = #user{username {ok, Q}; {error, {resource_locked, Q}} -> QName = amqqueue:get_name(Q), - ?LOG_DEBUG("MQTT deleting exclusive ~s owned by ~p", - [rabbit_misc:rs(QName), ?amqqueue_v2_field_exclusive_owner(Q)]), + ?LOG_DEBUG( + "MQTT deleting exclusive ~s owned by ~p", + [rabbit_misc:rs(QName), ?amqqueue_v2_field_exclusive_owner(Q)] + ), delete_queue(QName, Username), create_queue(QoS, State); {error, not_found} -> @@ -1020,14 +1278,18 @@ ensure_queue(QoS, State = #state{auth_state = #auth_state{user = #user{username end. create_queue( - QoS, #state{cfg = #cfg{ - client_id = ClientId, - clean_sess = CleanSess}, - auth_state = #auth_state{ - vhost = VHost, - user = User = #user{username = Username}, - authz_ctx = AuthzCtx} - }) -> + QoS, #state{ + cfg = #cfg{ + client_id = ClientId, + clean_sess = CleanSess + }, + auth_state = #auth_state{ + vhost = VHost, + user = User = #user{username = Username}, + authz_ctx = AuthzCtx + } + } +) -> QNameBin = rabbit_mqtt_util:queue_name_bin(ClientId, QoS), QName = rabbit_misc:r(VHost, queue, QNameBin), %% configure access to queue required for queue.declare @@ -1037,29 +1299,34 @@ create_queue( false -> rabbit_core_metrics:queue_declared(QName), QArgs = queue_args(QoS, CleanSess), - Q0 = amqqueue:new(QName, - self(), - _Durable = true, - _AutoDelete = false, - queue_owner(CleanSess), - QArgs, - VHost, - #{user => Username}, - queue_type(QoS, CleanSess, QArgs) - ), + Q0 = amqqueue:new( + QName, + self(), + _Durable = true, + _AutoDelete = false, + queue_owner(CleanSess), + QArgs, + VHost, + #{user => Username}, + queue_type(QoS, CleanSess, QArgs) + ), case rabbit_queue_type:declare(Q0, node()) of {new, Q} when ?is_amqqueue(Q) -> rabbit_core_metrics:queue_created(QName), {ok, Q}; Other -> - ?LOG_ERROR("Failed to declare ~s: ~p", - [rabbit_misc:rs(QName), Other]), + ?LOG_ERROR( + "Failed to declare ~s: ~p", + [rabbit_misc:rs(QName), Other] + ), {error, queue_declare} end; {true, Limit} -> - ?LOG_ERROR("cannot declare ~s because " - "queue limit ~p in vhost '~s' is reached", - [rabbit_misc:rs(QName), Limit, VHost]), + ?LOG_ERROR( + "cannot declare ~s because " + "queue limit ~p in vhost '~s' is reached", + [rabbit_misc:rs(QName), Limit, VHost] + ), {error, access_refused} end; {error, access_refused} = E -> @@ -1076,12 +1343,13 @@ queue_owner(false) -> none. queue_args(QoS, false) -> - Args = case rabbit_mqtt_util:env(subscription_ttl) of - Ms when is_integer(Ms) -> - [{<<"x-expires">>, long, Ms}]; - _ -> - [] - end, + Args = + case rabbit_mqtt_util:env(subscription_ttl) of + Ms when is_integer(Ms) -> + [{<<"x-expires">>, long, Ms}]; + _ -> + [] + end, case {QoS, rabbit_mqtt_util:env(durable_queue_type)} of {?QOS_1, quorum} -> [{<<"x-queue-type">>, longstr, <<"quorum">>} | Args]; @@ -1101,13 +1369,18 @@ queue_type(?QOS_0, true, QArgs) -> queue_type(_, _, QArgs) -> rabbit_amqqueue:get_queue_type(QArgs). -consume(Q, QoS, #state{ - queue_states = QStates0, - cfg = #cfg{prefetch = Prefetch}, - auth_state = #auth_state{ - authz_ctx = AuthzCtx, - user = User = #user{username = Username}} - } = State0) -> +consume( + Q, + QoS, + #state{ + queue_states = QStates0, + cfg = #cfg{prefetch = Prefetch}, + auth_state = #auth_state{ + authz_ctx = AuthzCtx, + user = User = #user{username = Username} + } + } = State0 +) -> QName = amqqueue:get_name(Q), %% read access to queue required for basic.consume case check_resource_access(User, QName, read, AuthzCtx) of @@ -1118,30 +1391,35 @@ consume(Q, QoS, #state{ %% explicitly calling rabbit_queue_type:consume/3. {ok, State0}; _ -> - Spec = #{no_ack => QoS =:= ?QOS_0, - channel_pid => self(), - limiter_pid => none, - limiter_active => false, - prefetch_count => Prefetch, - consumer_tag => ?CONSUMER_TAG, - exclusive_consume => false, - args => [], - ok_msg => undefined, - acting_user => Username}, + Spec = #{ + no_ack => QoS =:= ?QOS_0, + channel_pid => self(), + limiter_pid => none, + limiter_active => false, + prefetch_count => Prefetch, + consumer_tag => ?CONSUMER_TAG, + exclusive_consume => false, + args => [], + ok_msg => undefined, + acting_user => Username + }, rabbit_amqqueue:with( - QName, - fun(Q1) -> - case rabbit_queue_type:consume(Q1, Spec, QStates0) of - {ok, QStates} -> - State1 = State0#state{queue_states = QStates}, - State = maybe_set_queue_qos1(QoS, State1), - {ok, State}; - {error, Reason} = Err -> - ?LOG_ERROR("Failed to consume from ~s: ~p", - [rabbit_misc:rs(QName), Reason]), - Err - end - end) + QName, + fun(Q1) -> + case rabbit_queue_type:consume(Q1, Spec, QStates0) of + {ok, QStates} -> + State1 = State0#state{queue_states = QStates}, + State = maybe_set_queue_qos1(QoS, State1), + {ok, State}; + {error, Reason} = Err -> + ?LOG_ERROR( + "Failed to consume from ~s: ~p", + [rabbit_misc:rs(QName), Reason] + ), + Err + end + end + ) end; {error, access_refused} = Err -> Err @@ -1163,25 +1441,37 @@ binding_action_with_checks(Input, State) -> %% Same permission checks required for both binding and unbinding %% queue to / from topic exchange. rabbit_misc:pipeline( - [fun check_queue_write_access/2, - fun check_exchange_read_access/2, - fun check_topic_access/2, - fun binding_action/2], - Input, State). + [ + fun check_queue_write_access/2, + fun check_exchange_read_access/2, + fun check_topic_access/2, + fun binding_action/2 + ], + Input, + State + ). check_queue_write_access( - {QueueName, _, _}, - #state{auth_state = #auth_state{ - user = User, - authz_ctx = AuthzCtx}}) -> + {QueueName, _, _}, + #state{ + auth_state = #auth_state{ + user = User, + authz_ctx = AuthzCtx + } + } +) -> %% write access to queue required for queue.(un)bind check_resource_access(User, QueueName, write, AuthzCtx). check_exchange_read_access( - _, #state{cfg = #cfg{exchange = ExchangeName}, - auth_state = #auth_state{ - user = User, - authz_ctx = AuthzCtx}}) -> + _, #state{ + cfg = #cfg{exchange = ExchangeName}, + auth_state = #auth_state{ + user = User, + authz_ctx = AuthzCtx + } + } +) -> %% read access to exchange required for queue.(un)bind check_resource_access(User, ExchangeName, read, AuthzCtx). @@ -1189,57 +1479,72 @@ check_topic_access({_, TopicName, _}, State) -> check_topic_access(TopicName, read, State). binding_action( - {QueueName, TopicName, BindingFun}, - #state{cfg = #cfg{exchange = ExchangeName}, - auth_state = #auth_state{user = #user{username = Username}}}) -> + {QueueName, TopicName, BindingFun}, + #state{ + cfg = #cfg{exchange = ExchangeName}, + auth_state = #auth_state{user = #user{username = Username}} + } +) -> RoutingKey = mqtt_to_amqp(TopicName), - Binding = #binding{source = ExchangeName, - destination = QueueName, - key = RoutingKey}, + Binding = #binding{ + source = ExchangeName, + destination = QueueName, + key = RoutingKey + }, BindingFun(Binding, Username). publish_to_queues( - #mqtt_msg{qos = Qos, - topic = Topic, - dup = Dup, - packet_id = PacketId, - payload = Payload}, - #state{cfg = #cfg{exchange = ExchangeName, - delivery_flow = Flow, - conn_name = ConnName, - trace_state = TraceState}, - auth_state = #auth_state{username = Username} - } = State) -> + #mqtt_msg{ + qos = Qos, + topic = Topic, + dup = Dup, + packet_id = PacketId, + payload = Payload + }, + #state{ + cfg = #cfg{ + exchange = ExchangeName, + delivery_flow = Flow, + conn_name = ConnName, + trace_state = TraceState + }, + auth_state = #auth_state{username = Username} + } = State +) -> RoutingKey = mqtt_to_amqp(Topic), Confirm = Qos > ?QOS_0, - Headers = [{<<"x-mqtt-publish-qos">>, byte, Qos}, - {<<"x-mqtt-dup">>, bool, Dup}], + Headers = [ + {<<"x-mqtt-publish-qos">>, byte, Qos}, + {<<"x-mqtt-dup">>, bool, Dup} + ], Props = #'P_basic'{ - headers = Headers, - delivery_mode = delivery_mode(Qos)}, + headers = Headers, + delivery_mode = delivery_mode(Qos) + }, {ClassId, _MethodId} = rabbit_framing_amqp_0_9_1:method_id('basic.publish'), Content = #content{ - class_id = ClassId, - properties = Props, - properties_bin = none, - protocol = none, - payload_fragments_rev = [Payload] - }, + class_id = ClassId, + properties = Props, + properties_bin = none, + protocol = none, + payload_fragments_rev = [Payload] + }, BasicMessage = #basic_message{ - exchange_name = ExchangeName, - routing_keys = [RoutingKey], - content = Content, - id = <<>>, %% GUID set in rabbit_classic_queue - is_persistent = Confirm - }, + exchange_name = ExchangeName, + routing_keys = [RoutingKey], + content = Content, + %% GUID set in rabbit_classic_queue + id = <<>>, + is_persistent = Confirm + }, Delivery = #delivery{ - mandatory = false, - confirm = Confirm, - sender = self(), - message = BasicMessage, - msg_seq_no = PacketId, - flow = Flow - }, + mandatory = false, + confirm = Confirm, + sender = self(), + message = BasicMessage, + msg_seq_no = PacketId, + flow = Flow + }, case rabbit_exchange:lookup(ExchangeName) of {ok, Exchange} -> QNames = rabbit_exchange:route(Exchange, Delivery), @@ -1250,39 +1555,61 @@ publish_to_queues( {error, exchange_not_found, State} end. -deliver_to_queues(Delivery, - RoutedToQNames, - State0 = #state{queue_states = QStates0, - cfg = #cfg{proto_ver = ProtoVer}}) -> +deliver_to_queues( + Delivery, + RoutedToQNames, + State0 = #state{ + queue_states = QStates0, + cfg = #cfg{proto_ver = ProtoVer} + } +) -> Qs0 = rabbit_amqqueue:lookup(RoutedToQNames), Qs = rabbit_amqqueue:prepend_extra_bcc(Qs0), case rabbit_queue_type:deliver(Qs, Delivery, QStates0) of {ok, QStates, Actions} -> rabbit_global_counters:messages_routed(ProtoVer, length(Qs)), - State = process_routing_confirm(Delivery, Qs, - State0#state{queue_states = QStates}), + State = process_routing_confirm( + Delivery, + Qs, + State0#state{queue_states = QStates} + ), %% Actions must be processed after registering confirms as actions may %% contain rejections of publishes. {ok, handle_queue_actions(Actions, State)}; {error, Reason} -> - ?LOG_ERROR("Failed to deliver message with packet_id=~p to queues: ~p", - [Delivery#delivery.msg_seq_no, Reason]), + ?LOG_ERROR( + "Failed to deliver message with packet_id=~p to queues: ~p", + [Delivery#delivery.msg_seq_no, Reason] + ), {error, Reason, State0} end. -process_routing_confirm(#delivery{confirm = false}, - [], State = #state{cfg = #cfg{proto_ver = ProtoVer}}) -> +process_routing_confirm( + #delivery{confirm = false}, + [], + State = #state{cfg = #cfg{proto_ver = ProtoVer}} +) -> rabbit_global_counters:messages_unroutable_dropped(ProtoVer, 1), State; -process_routing_confirm(#delivery{confirm = true, - msg_seq_no = undefined}, - [], State = #state{cfg = #cfg{proto_ver = ProtoVer}}) -> +process_routing_confirm( + #delivery{ + confirm = true, + msg_seq_no = undefined + }, + [], + State = #state{cfg = #cfg{proto_ver = ProtoVer}} +) -> %% unroutable will message with QoS > 0 rabbit_global_counters:messages_unroutable_dropped(ProtoVer, 1), State; -process_routing_confirm(#delivery{confirm = true, - msg_seq_no = PktId}, - [], State = #state{cfg = #cfg{proto_ver = ProtoVer}}) -> +process_routing_confirm( + #delivery{ + confirm = true, + msg_seq_no = PktId + }, + [], + State = #state{cfg = #cfg{proto_ver = ProtoVer}} +) -> rabbit_global_counters:messages_unroutable_returned(ProtoVer, 1), %% MQTT 5 spec: %% If the Server knows that there are no matching subscribers, it MAY use @@ -1291,42 +1618,75 @@ process_routing_confirm(#delivery{confirm = true, State; process_routing_confirm(#delivery{confirm = false}, _, State) -> State; -process_routing_confirm(#delivery{confirm = true, - msg_seq_no = undefined}, [_|_], State) -> +process_routing_confirm( + #delivery{ + confirm = true, + msg_seq_no = undefined + }, + [_ | _], + State +) -> %% routable will message with QoS > 0 State; -process_routing_confirm(#delivery{confirm = true, - msg_seq_no = PktId}, - Qs, State = #state{unacked_client_pubs = U0}) -> +process_routing_confirm( + #delivery{ + confirm = true, + msg_seq_no = PktId + }, + Qs, + State = #state{unacked_client_pubs = U0} +) -> QNames = lists:map(fun amqqueue:get_name/1, Qs), U = rabbit_mqtt_confirms:insert(PktId, QNames, U0), State#state{unacked_client_pubs = U}. -send_puback(PktIds0, State) - when is_list(PktIds0) -> +send_puback(PktIds0, State) when + is_list(PktIds0) +-> %% Classic queues confirm messages unordered. %% Let's sort them here assuming most MQTT clients send with an increasing packet identifier. PktIds = lists:usort(PktIds0), - lists:foreach(fun(Id) -> - send_puback(Id, State) - end, PktIds); -send_puback(PktId, State = #state{cfg = #cfg{send_fun = SendFun, - proto_ver = ProtoVer}}) -> + lists:foreach( + fun(Id) -> + send_puback(Id, State) + end, + PktIds + ); +send_puback( + PktId, + State = #state{ + cfg = #cfg{ + send_fun = SendFun, + proto_ver = ProtoVer + } + } +) -> rabbit_global_counters:messages_confirmed(ProtoVer, 1), SendFun( - #mqtt_packet{fixed = #mqtt_packet_fixed{type = ?PUBACK}, - variable = #mqtt_packet_publish{packet_id = PktId}}, - State). - -serialise_and_send_to_client(Packet, #state{cfg = #cfg{proto_ver = ProtoVer, - socket = Sock}}) -> + #mqtt_packet{ + fixed = #mqtt_packet_fixed{type = ?PUBACK}, + variable = #mqtt_packet_publish{packet_id = PktId} + }, + State + ). + +serialise_and_send_to_client(Packet, #state{ + cfg = #cfg{ + proto_ver = ProtoVer, + socket = Sock + } +}) -> Data = rabbit_mqtt_packet:serialise(Packet, ProtoVer), - try rabbit_net:port_command(Sock, Data) - catch error:Error -> - ?LOG_ERROR("MQTT: a socket write failed: ~p", [Error]), - ?LOG_DEBUG("MQTT failed to write to socket ~p, error: ~p, " - "fixed packet header: ~p, variable packet header: ~p", - [Sock, Error, Packet#mqtt_packet.fixed, Packet#mqtt_packet.variable]) + try + rabbit_net:port_command(Sock, Data) + catch + error:Error -> + ?LOG_ERROR("MQTT: a socket write failed: ~p", [Error]), + ?LOG_DEBUG( + "MQTT failed to write to socket ~p, error: ~p, " + "fixed packet header: ~p, variable packet header: ~p", + [Sock, Error, Packet#mqtt_packet.fixed, Packet#mqtt_packet.variable] + ) end. -spec serialise(#mqtt_packet{}, state()) -> @@ -1338,11 +1698,13 @@ serialise(Packet, #state{cfg = #cfg{proto_ver = ProtoVer}}) -> ok. terminate(SendWill, ConnName, ProtoFamily, State) -> maybe_send_will(SendWill, ConnName, State), - Infos = [{name, ConnName}, - {node, node()}, - {pid, self()}, - {disconnected_at, os:system_time(milli_seconds)} - ] ++ additional_connection_closed_info(ProtoFamily, State), + Infos = + [ + {name, ConnName}, + {node, node()}, + {pid, self()}, + {disconnected_at, os:system_time(milli_seconds)} + ] ++ additional_connection_closed_info(ProtoFamily, State), rabbit_core_metrics:connection_closed(self()), rabbit_event:notify(connection_closed, Infos), rabbit_networking:unregister_non_amqp_connection(self()), @@ -1352,13 +1714,23 @@ terminate(SendWill, ConnName, ProtoFamily, State) -> maybe_delete_mqtt_qos0_queue(State). maybe_send_will( - true, ConnStr, - #state{cfg = #cfg{retainer_pid = RPid, - will_msg = WillMsg = #mqtt_msg{retain = Retain, - topic = Topic}} - } = State) -> - ?LOG_DEBUG("sending MQTT will message to topic ~s on connection ~s", - [Topic, ConnStr]), + true, + ConnStr, + #state{ + cfg = #cfg{ + retainer_pid = RPid, + will_msg = + WillMsg = #mqtt_msg{ + retain = Retain, + topic = Topic + } + } + } = State +) -> + ?LOG_DEBUG( + "sending MQTT will message to topic ~s on connection ~s", + [Topic, ConnStr] + ), case check_topic_access(Topic, write, State) of ok -> _ = publish_to_queues(WillMsg, State), @@ -1368,24 +1740,32 @@ maybe_send_will( true -> hand_off_to_retainer(RPid, Topic, WillMsg) end; - {error, access_refused = Reason} -> + {error, access_refused = Reason} -> ?LOG_ERROR("failed to send will message: ~p", [Reason]) end; maybe_send_will(_, _, _) -> ok. additional_connection_closed_info( - ProtoFamily, - State = #state{auth_state = #auth_state{vhost = VHost, - username = Username}}) -> - [{protocol, {ProtoFamily, proto_version_tuple(State)}}, - {vhost, VHost}, - {user, Username}]; + ProtoFamily, + State = #state{ + auth_state = #auth_state{ + vhost = VHost, + username = Username + } + } +) -> + [ + {protocol, {ProtoFamily, proto_version_tuple(State)}}, + {vhost, VHost}, + {user, Username} + ]; additional_connection_closed_info(_, _) -> []. -maybe_unregister_client(#state{cfg = #cfg{client_id = ClientId}}) - when ClientId =/= undefined -> +maybe_unregister_client(#state{cfg = #cfg{client_id = ClientId}}) when + ClientId =/= undefined +-> case rabbit_mqtt_ff:track_client_id_in_ra() of true -> %% ignore any errors as we are shutting down @@ -1397,14 +1777,18 @@ maybe_unregister_client(_) -> ok. maybe_delete_mqtt_qos0_queue( - State = #state{cfg = #cfg{clean_sess = true}, - auth_state = #auth_state{username = Username}}) -> + State = #state{ + cfg = #cfg{clean_sess = true}, + auth_state = #auth_state{username = Username} + } +) -> case get_queue(?QOS_0, State) of {ok, Q} -> %% double check we delete the right queue case {amqqueue:get_type(Q), amqqueue:get_pid(Q)} of - {?QUEUE_TYPE_QOS_0, Pid} - when Pid =:= self() -> + {?QUEUE_TYPE_QOS_0, Pid} when + Pid =:= self() + -> rabbit_queue_type:delete(Q, false, false, Username); _ -> ok @@ -1417,19 +1801,21 @@ maybe_delete_mqtt_qos0_queue(_) -> delete_queue(QName, Username) -> rabbit_amqqueue:with( - QName, - fun (Q) -> - rabbit_queue_type:delete(Q, false, false, Username) - end, - fun (not_found) -> - ok; - ({absent, Q, crashed}) -> - rabbit_classic_queue:delete_crashed(Q, Username); - ({absent, Q, stopped}) -> - rabbit_classic_queue:delete_crashed(Q, Username); - ({absent, _Q, _Reason}) -> - ok - end). + QName, + fun(Q) -> + rabbit_queue_type:delete(Q, false, false, Username) + end, + fun + (not_found) -> + ok; + ({absent, Q, crashed}) -> + rabbit_classic_queue:delete_crashed(Q, Username); + ({absent, Q, stopped}) -> + rabbit_classic_queue:delete_crashed(Q, Username); + ({absent, _Q, _Reason}) -> + ok + end + ). -spec handle_pre_hibernate() -> ok. handle_pre_hibernate() -> @@ -1437,16 +1823,25 @@ handle_pre_hibernate() -> erase(topic_permission_cache), ok. --spec handle_ra_event(register_timeout -| {applied, [{reference(), ok}]} -| {not_leader, term(), reference()}, state()) -> state(). -handle_ra_event({applied, [{Corr, ok}]}, - State = #state{register_state = {pending, Corr}}) -> +-spec handle_ra_event( + register_timeout + | {applied, [{reference(), ok}]} + | {not_leader, term(), reference()}, + state() +) -> state(). +handle_ra_event( + {applied, [{Corr, ok}]}, + State = #state{register_state = {pending, Corr}} +) -> %% success case - command was applied transition into registered state State#state{register_state = registered}; -handle_ra_event({not_leader, Leader, Corr}, - State = #state{register_state = {pending, Corr}, - cfg = #cfg{client_id = ClientId}}) -> +handle_ra_event( + {not_leader, Leader, Corr}, + State = #state{ + register_state = {pending, Corr}, + cfg = #cfg{client_id = ClientId} + } +) -> case rabbit_mqtt_ff:track_client_id_in_ra() of true -> %% retry command against actual leader @@ -1455,9 +1850,13 @@ handle_ra_event({not_leader, Leader, Corr}, false -> State end; -handle_ra_event(register_timeout, - State = #state{register_state = {pending, _Corr}, - cfg = #cfg{client_id = ClientId}}) -> +handle_ra_event( + register_timeout, + State = #state{ + register_state = {pending, _Corr}, + cfg = #cfg{client_id = ClientId} + } +) -> case rabbit_mqtt_ff:track_client_id_in_ra() of true -> {ok, NewCorr} = rabbit_mqtt_collector:register(ClientId, self()), @@ -1473,9 +1872,13 @@ handle_ra_event(Evt, State) -> -spec handle_down(term(), state()) -> {ok, state()} | {error, Reason :: any()}. -handle_down({{'DOWN', QName}, _MRef, process, QPid, Reason}, - State0 = #state{queue_states = QStates0, - unacked_client_pubs = U0}) -> +handle_down( + {{'DOWN', QName}, _MRef, process, QPid, Reason}, + State0 = #state{ + queue_states = QStates0, + unacked_client_pubs = U0 + } +) -> credit_flow:peer_down(QPid), case rabbit_queue_type:handle_down(QPid, QName, Reason, QStates0) of {ok, QStates1, Actions} -> @@ -1483,33 +1886,44 @@ handle_down({{'DOWN', QName}, _MRef, process, QPid, Reason}, try handle_queue_actions(Actions, State1) of State -> {ok, State} - catch throw:consuming_queue_down -> - {error, consuming_queue_down} + catch + throw:consuming_queue_down -> + {error, consuming_queue_down} end; {eol, QStates1, QRef} -> {ConfirmPktIds, U} = rabbit_mqtt_confirms:remove_queue(QRef, U0), QStates = rabbit_queue_type:remove(QRef, QStates1), - State = State0#state{queue_states = QStates, - unacked_client_pubs = U}, + State = State0#state{ + queue_states = QStates, + unacked_client_pubs = U + }, send_puback(ConfirmPktIds, State), {ok, State} end. -spec handle_queue_event( - {queue_event, rabbit_amqqueue:name() | ?QUEUE_TYPE_QOS_0, term()}, state()) -> + {queue_event, rabbit_amqqueue:name() | ?QUEUE_TYPE_QOS_0, term()}, state() +) -> {ok, state()} | {error, Reason :: any(), state()}. -handle_queue_event({queue_event, ?QUEUE_TYPE_QOS_0, Msg}, - State0 = #state{qos0_messages_dropped = N}) -> - State = case drop_qos0_message(State0) of - false -> - deliver_one_to_client(Msg, false, State0); - true -> - State0#state{qos0_messages_dropped = N + 1} - end, +handle_queue_event( + {queue_event, ?QUEUE_TYPE_QOS_0, Msg}, + State0 = #state{qos0_messages_dropped = N} +) -> + State = + case drop_qos0_message(State0) of + false -> + deliver_one_to_client(Msg, false, State0); + true -> + State0#state{qos0_messages_dropped = N + 1} + end, {ok, State}; -handle_queue_event({queue_event, QName, Evt}, - State0 = #state{queue_states = QStates0, - unacked_client_pubs = U0}) -> +handle_queue_event( + {queue_event, QName, Evt}, + State0 = #state{ + queue_states = QStates0, + unacked_client_pubs = U0 + } +) -> case rabbit_queue_type:handle_event(QName, Evt, QStates0) of {ok, QStates, Actions} -> State1 = State0#state{queue_states = QStates}, @@ -1519,8 +1933,10 @@ handle_queue_event({queue_event, QName, Evt}, State1 = handle_queue_actions(Actions, State0), {ConfirmPktIds, U} = rabbit_mqtt_confirms:remove_queue(QName, U0), QStates = rabbit_queue_type:remove(QName, QStates0), - State = State1#state{queue_states = QStates, - unacked_client_pubs = U}, + State = State1#state{ + queue_states = QStates, + unacked_client_pubs = U + }, send_puback(ConfirmPktIds, State), {ok, State}; {protocol_error, _Type, _Reason, _ReasonArgs} = Error -> @@ -1529,30 +1945,37 @@ handle_queue_event({queue_event, QName, Evt}, handle_queue_actions(Actions, #state{} = State0) -> lists:foldl( - fun ({deliver, ?CONSUMER_TAG, Ack, Msgs}, S) -> - deliver_to_client(Msgs, Ack, S); - ({settled, QName, PktIds}, S = #state{unacked_client_pubs = U0}) -> - {ConfirmPktIds, U} = rabbit_mqtt_confirms:confirm(PktIds, QName, U0), - send_puback(ConfirmPktIds, S), - S#state{unacked_client_pubs = U}; - ({rejected, _QName, PktIds}, S = #state{unacked_client_pubs = U0}) -> - %% Negative acks are supported in MQTT v5 only. - %% Therefore, in MQTT v3 and v4 we ignore rejected messages. - U = lists:foldl( + fun + ({deliver, ?CONSUMER_TAG, Ack, Msgs}, S) -> + deliver_to_client(Msgs, Ack, S); + ({settled, QName, PktIds}, S = #state{unacked_client_pubs = U0}) -> + {ConfirmPktIds, U} = rabbit_mqtt_confirms:confirm(PktIds, QName, U0), + send_puback(ConfirmPktIds, S), + S#state{unacked_client_pubs = U}; + ({rejected, _QName, PktIds}, S = #state{unacked_client_pubs = U0}) -> + %% Negative acks are supported in MQTT v5 only. + %% Therefore, in MQTT v3 and v4 we ignore rejected messages. + U = lists:foldl( fun(PktId, Acc0) -> - case rabbit_mqtt_confirms:reject(PktId, Acc0) of - {ok, Acc} -> Acc; - {error, not_found} -> Acc0 - end - end, U0, PktIds), - S#state{unacked_client_pubs = U}; - ({block, QName}, S = #state{queues_soft_limit_exceeded = QSLE}) -> - S#state{queues_soft_limit_exceeded = sets:add_element(QName, QSLE)}; - ({unblock, QName}, S = #state{queues_soft_limit_exceeded = QSLE}) -> - S#state{queues_soft_limit_exceeded = sets:del_element(QName, QSLE)}; - ({queue_down, QName}, S) -> - handle_queue_down(QName, S) - end, State0, Actions). + case rabbit_mqtt_confirms:reject(PktId, Acc0) of + {ok, Acc} -> Acc; + {error, not_found} -> Acc0 + end + end, + U0, + PktIds + ), + S#state{unacked_client_pubs = U}; + ({block, QName}, S = #state{queues_soft_limit_exceeded = QSLE}) -> + S#state{queues_soft_limit_exceeded = sets:add_element(QName, QSLE)}; + ({unblock, QName}, S = #state{queues_soft_limit_exceeded = QSLE}) -> + S#state{queues_soft_limit_exceeded = sets:del_element(QName, QSLE)}; + ({queue_down, QName}, S) -> + handle_queue_down(QName, S) + end, + State0, + Actions + ). handle_queue_down(QName, State0 = #state{cfg = #cfg{client_id = ClientId}}) -> %% Classic queue is down. @@ -1568,8 +1991,10 @@ handle_queue_down(QName, State0 = #state{cfg = #cfg{client_id = ClientId}}) -> {ok, State} -> State; {error, _Reason} -> - ?LOG_INFO("Terminating MQTT connection because consuming ~s is down.", - [rabbit_misc:rs(QName)]), + ?LOG_INFO( + "Terminating MQTT connection because consuming ~s is down.", + [rabbit_misc:rs(QName)] + ), throw(consuming_queue_down) end end; @@ -1578,26 +2003,37 @@ handle_queue_down(QName, State0 = #state{cfg = #cfg{client_id = ClientId}}) -> end. deliver_to_client(Msgs, Ack, State) -> - lists:foldl(fun(Msg, S) -> - deliver_one_to_client(Msg, Ack, S) - end, State, Msgs). - -deliver_one_to_client(Msg = {QNameOrType, QPid, QMsgId, _Redelivered, - #basic_message{content = #content{properties = #'P_basic'{headers = Headers}}}}, - AckRequired, State0) -> - PublisherQoS = case rabbit_mqtt_util:table_lookup(Headers, <<"x-mqtt-publish-qos">>) of - {byte, QoS0} -> - QoS0; - undefined -> - %% non-MQTT publishes are assumed to be QoS 1 regardless of delivery_mode - ?QOS_1 - end, - SubscriberQoS = case AckRequired of - true -> - ?QOS_1; - false -> - ?QOS_0 - end, + lists:foldl( + fun(Msg, S) -> + deliver_one_to_client(Msg, Ack, S) + end, + State, + Msgs + ). + +deliver_one_to_client( + Msg = + {QNameOrType, QPid, QMsgId, _Redelivered, #basic_message{ + content = #content{properties = #'P_basic'{headers = Headers}} + }}, + AckRequired, + State0 +) -> + PublisherQoS = + case rabbit_mqtt_util:table_lookup(Headers, <<"x-mqtt-publish-qos">>) of + {byte, QoS0} -> + QoS0; + undefined -> + %% non-MQTT publishes are assumed to be QoS 1 regardless of delivery_mode + ?QOS_1 + end, + SubscriberQoS = + case AckRequired of + true -> + ?QOS_1; + false -> + ?QOS_0 + end, QoS = effective_qos(PublisherQoS, SubscriberQoS), State1 = maybe_publish_to_client(Msg, QoS, State0), State = maybe_auto_ack(AckRequired, QoS, QNameOrType, QMsgId, State1), @@ -1615,28 +2051,33 @@ maybe_publish_to_client({_, _, _, _Redelivered = true, _}, ?QOS_0, State) -> %% Do not redeliver to MQTT subscriber who gets message at most once. State; maybe_publish_to_client( - {QNameOrType, _QPid, QMsgId, Redelivered, - #basic_message{ - routing_keys = [RoutingKey | _CcRoutes], - content = #content{payload_fragments_rev = FragmentsRev}}} = Msg, - QoS, State0 = #state{cfg = #cfg{send_fun = SendFun}}) -> + {QNameOrType, _QPid, QMsgId, Redelivered, #basic_message{ + routing_keys = [RoutingKey | _CcRoutes], + content = #content{payload_fragments_rev = FragmentsRev} + }} = Msg, + QoS, + State0 = #state{cfg = #cfg{send_fun = SendFun}} +) -> {PacketId, State} = msg_id_to_packet_id(QMsgId, QoS, State0), Packet = - #mqtt_packet{ - fixed = #mqtt_packet_fixed{ - type = ?PUBLISH, - qos = QoS, - %% "The value of the DUP flag from an incoming PUBLISH packet is not - %% propagated when the PUBLISH Packet is sent to subscribers by the Server. - %% The DUP flag in the outgoing PUBLISH packet is set independently to the - %% incoming PUBLISH packet, its value MUST be determined solely by whether - %% the outgoing PUBLISH packet is a retransmission [MQTT-3.3.1-3]." - %% Therefore, we do not consider header value <<"x-mqtt-dup">> here. - dup = Redelivered}, - variable = #mqtt_packet_publish{ - packet_id = PacketId, - topic_name = amqp_to_mqtt(RoutingKey)}, - payload = lists:reverse(FragmentsRev)}, + #mqtt_packet{ + fixed = #mqtt_packet_fixed{ + type = ?PUBLISH, + qos = QoS, + %% "The value of the DUP flag from an incoming PUBLISH packet is not + %% propagated when the PUBLISH Packet is sent to subscribers by the Server. + %% The DUP flag in the outgoing PUBLISH packet is set independently to the + %% incoming PUBLISH packet, its value MUST be determined solely by whether + %% the outgoing PUBLISH packet is a retransmission [MQTT-3.3.1-3]." + %% Therefore, we do not consider header value <<"x-mqtt-dup">> here. + dup = Redelivered + }, + variable = #mqtt_packet_publish{ + packet_id = PacketId, + topic_name = amqp_to_mqtt(RoutingKey) + }, + payload = lists:reverse(FragmentsRev) + }, SendFun(Packet, State), trace_tap_out(Msg, State), message_delivered(QNameOrType, Redelivered, QoS, State), @@ -1645,21 +2086,37 @@ maybe_publish_to_client( msg_id_to_packet_id(_, ?QOS_0, State) -> %% "A PUBLISH packet MUST NOT contain a Packet Identifier if its QoS value is set to 0 [MQTT-2.2.1-2]." {undefined, State}; -msg_id_to_packet_id(QMsgId, ?QOS_1, #state{packet_id = PktId, - unacked_server_pubs = U} = State) -> - {PktId, State#state{packet_id = increment_packet_id(PktId), - unacked_server_pubs = maps:put(PktId, QMsgId, U)}}. +msg_id_to_packet_id( + QMsgId, + ?QOS_1, + #state{ + packet_id = PktId, + unacked_server_pubs = U + } = State +) -> + {PktId, State#state{ + packet_id = increment_packet_id(PktId), + unacked_server_pubs = maps:put(PktId, QMsgId, U) + }}. -spec increment_packet_id(packet_id()) -> packet_id(). -increment_packet_id(Id) - when Id >= 16#ffff -> +increment_packet_id(Id) when + Id >= 16#ffff +-> 1; increment_packet_id(Id) -> Id + 1. -maybe_auto_ack(_AckRequired = true, ?QOS_0, QName, QMsgId, - State = #state{queue_states = QStates0}) -> - {ok, QStates, Actions} = rabbit_queue_type:settle(QName, complete, ?CONSUMER_TAG, [QMsgId], QStates0), +maybe_auto_ack( + _AckRequired = true, + ?QOS_0, + QName, + QMsgId, + State = #state{queue_states = QStates0} +) -> + {ok, QStates, Actions} = rabbit_queue_type:settle( + QName, complete, ?CONSUMER_TAG, [QMsgId], QStates0 + ), handle_queue_actions(Actions, State#state{queue_states = QStates}); maybe_auto_ack(_, _, _, _, State) -> State. @@ -1674,13 +2131,21 @@ maybe_notify_sent(QName, QPid, #state{queue_states = QStates}) -> ok end. -trace_tap_out(Msg = {#resource{}, _, _, _, _}, - #state{auth_state = #auth_state{username = Username}, - cfg = #cfg{conn_name = ConnName, - trace_state = TraceState}}) -> +trace_tap_out( + Msg = {#resource{}, _, _, _, _}, + #state{ + auth_state = #auth_state{username = Username}, + cfg = #cfg{ + conn_name = ConnName, + trace_state = TraceState + } + } +) -> rabbit_trace:tap_out(Msg, ConnName, Username, TraceState); -trace_tap_out(Msg0 = {?QUEUE_TYPE_QOS_0, _, _, _, _}, - State = #state{cfg = #cfg{trace_state = TraceState}}) -> +trace_tap_out( + Msg0 = {?QUEUE_TYPE_QOS_0, _, _, _, _}, + State = #state{cfg = #cfg{trace_state = TraceState}} +) -> case rabbit_trace:enabled(TraceState) of false -> ok; @@ -1692,11 +2157,16 @@ trace_tap_out(Msg0 = {?QUEUE_TYPE_QOS_0, _, _, _, _}, end. publish_to_queues_with_checks( - TopicName, PublishFun, - #state{cfg = #cfg{exchange = Exchange}, - auth_state = #auth_state{user = User, - authz_ctx = AuthzCtx} - } = State) -> + TopicName, + PublishFun, + #state{ + cfg = #cfg{exchange = Exchange}, + auth_state = #auth_state{ + user = User, + authz_ctx = AuthzCtx + } + } = State +) -> case check_resource_access(User, Exchange, write, AuthzCtx) of ok -> case check_topic_access(TopicName, write, State) of @@ -1711,57 +2181,75 @@ publish_to_queues_with_checks( check_resource_access(User, Resource, Perm, Context) -> V = {Resource, Context, Perm}, - Cache = case get(permission_cache) of - undefined -> []; - Other -> Other - end, + Cache = + case get(permission_cache) of + undefined -> []; + Other -> Other + end, case lists:member(V, Cache) of true -> ok; false -> try rabbit_access_control:check_resource_access(User, Resource, Perm, Context) of ok -> - CacheTail = lists:sublist(Cache, ?MAX_PERMISSION_CACHE_SIZE-1), + CacheTail = lists:sublist(Cache, ?MAX_PERMISSION_CACHE_SIZE - 1), put(permission_cache, [V | CacheTail]), ok catch - exit:#amqp_error{name = access_refused, - explanation = Msg} -> + exit:#amqp_error{ + name = access_refused, + explanation = Msg + } -> ?LOG_ERROR("MQTT resource access refused: ~s", [Msg]), {error, access_refused} end end. check_topic_access( - TopicName, Access, - #state{auth_state = #auth_state{user = User = #user{username = Username}, - vhost = VHost, - authz_ctx = AuthzCtx}, - cfg = #cfg{client_id = ClientId, - exchange = #resource{name = ExchangeBin}}}) -> - Cache = case get(topic_permission_cache) of - undefined -> []; - Other -> Other - end, + TopicName, + Access, + #state{ + auth_state = #auth_state{ + user = User = #user{username = Username}, + vhost = VHost, + authz_ctx = AuthzCtx + }, + cfg = #cfg{ + client_id = ClientId, + exchange = #resource{name = ExchangeBin} + } + } +) -> + Cache = + case get(topic_permission_cache) of + undefined -> []; + Other -> Other + end, Key = {TopicName, Username, ClientId, VHost, ExchangeBin, Access}, case lists:member(Key, Cache) of true -> ok; false -> - Resource = #resource{virtual_host = VHost, - kind = topic, - name = ExchangeBin}, + Resource = #resource{ + virtual_host = VHost, + kind = topic, + name = ExchangeBin + }, RoutingKey = mqtt_to_amqp(TopicName), - Context = #{routing_key => RoutingKey, - variable_map => AuthzCtx}, + Context = #{ + routing_key => RoutingKey, + variable_map => AuthzCtx + }, try rabbit_access_control:check_topic_access(User, Resource, Access, Context) of ok -> CacheTail = lists:sublist(Cache, ?MAX_PERMISSION_CACHE_SIZE - 1), put(topic_permission_cache, [Key | CacheTail]), ok catch - exit:#amqp_error{name = access_refused, - explanation = Msg} -> + exit:#amqp_error{ + name = access_refused, + explanation = Msg + } -> ?LOG_ERROR("MQTT topic access refused: ~s", [Msg]), {error, access_refused} end @@ -1771,7 +2259,7 @@ check_topic_access( boolean(). drop_qos0_message(State) -> mailbox_soft_limit_exceeded() andalso - is_socket_busy(State#state.cfg#cfg.socket). + is_socket_busy(State#state.cfg#cfg.socket). -spec mailbox_soft_limit_exceeded() -> boolean(). @@ -1790,19 +2278,22 @@ mailbox_soft_limit_exceeded() -> is_socket_busy(Socket) -> case rabbit_net:getstat(Socket, [send_pend]) of - {ok, [{send_pend, NumBytes}]} - when is_number(NumBytes) andalso NumBytes > 0 -> + {ok, [{send_pend, NumBytes}]} when + is_number(NumBytes) andalso NumBytes > 0 + -> true; _ -> false end. -spec throttle(boolean(), boolean(), state()) -> boolean(). -throttle(Conserve, Connected, #state{queues_soft_limit_exceeded = QSLE, - cfg = #cfg{published = Published}}) -> +throttle(Conserve, Connected, #state{ + queues_soft_limit_exceeded = QSLE, + cfg = #cfg{published = Published} +}) -> Conserve andalso (Published orelse not Connected) orelse - not sets:is_empty(QSLE) orelse - credit_flow:blocked(). + not sets:is_empty(QSLE) orelse + credit_flow:blocked(). -spec info(rabbit_types:info_key(), state()) -> any(). info(host, #state{cfg = #cfg{host = Val}}) -> Val; @@ -1812,23 +2303,19 @@ info(peer_port, #state{cfg = #cfg{peer_port = Val}}) -> Val; info(connected_at, #state{cfg = #cfg{connected_at = Val}}) -> Val; info(ssl_login_name, #state{cfg = #cfg{ssl_login_name = Val}}) -> Val; info(vhost, #state{auth_state = #auth_state{vhost = Val}}) -> Val; -info(user_who_performed_action, S) -> - info(user, S); +info(user_who_performed_action, S) -> info(user, S); info(user, #state{auth_state = #auth_state{username = Val}}) -> Val; info(clean_sess, #state{cfg = #cfg{clean_sess = Val}}) -> Val; info(will_msg, #state{cfg = #cfg{will_msg = Val}}) -> Val; info(retainer_pid, #state{cfg = #cfg{retainer_pid = Val}}) -> Val; info(exchange, #state{cfg = #cfg{exchange = #resource{name = Val}}}) -> Val; info(prefetch, #state{cfg = #cfg{prefetch = Val}}) -> Val; -info(messages_unconfirmed, #state{unacked_client_pubs = Val}) -> - rabbit_mqtt_confirms:size(Val); -info(messages_unacknowledged, #state{unacked_server_pubs = Val}) -> - maps:size(Val); +info(messages_unconfirmed, #state{unacked_client_pubs = Val}) -> rabbit_mqtt_confirms:size(Val); +info(messages_unacknowledged, #state{unacked_server_pubs = Val}) -> maps:size(Val); info(node, _) -> node(); info(client_id, #state{cfg = #cfg{client_id = Val}}) -> Val; %% for rabbitmq_management/priv/www/js/tmpl/connection.ejs -info(client_properties, #state{cfg = #cfg{client_id = Val}}) -> - [{client_id, longstr, Val}]; +info(client_properties, #state{cfg = #cfg{client_id = Val}}) -> [{client_id, longstr, Val}]; info(channel_max, _) -> 0; %% Maximum packet size supported only in MQTT 5.0. info(frame_max, _) -> 0; @@ -1840,13 +2327,16 @@ info(Other, _) -> throw({bad_argument, Other}). none | binary(). ssl_login_name(Sock) -> case rabbit_net:peercert(Sock) of - {ok, C} -> case rabbit_ssl:peer_cert_auth_name(C) of - unsafe -> none; - not_found -> none; - Name -> Name - end; - {error, no_peercert} -> none; - nossl -> none + {ok, C} -> + case rabbit_ssl:peer_cert_auth_name(C) of + unsafe -> none; + not_found -> none; + Name -> Name + end; + {error, no_peercert} -> + none; + nossl -> + none end. proto_integer_to_atom(3) -> @@ -1860,47 +2350,73 @@ proto_version_tuple(#state{cfg = #cfg{proto_ver = ?MQTT_PROTO_V3}}) -> proto_version_tuple(#state{cfg = #cfg{proto_ver = ?MQTT_PROTO_V4}}) -> {3, 1, 1}. -maybe_increment_publisher(State = #state{cfg = Cfg = #cfg{published = false, - proto_ver = ProtoVer}}) -> +maybe_increment_publisher( + State = #state{ + cfg = + Cfg = #cfg{ + published = false, + proto_ver = ProtoVer + } + } +) -> rabbit_global_counters:publisher_created(ProtoVer), State#state{cfg = Cfg#cfg{published = true}}; maybe_increment_publisher(State) -> State. -maybe_decrement_publisher(#state{cfg = #cfg{published = true, - proto_ver = ProtoVer}}) -> +maybe_decrement_publisher(#state{ + cfg = #cfg{ + published = true, + proto_ver = ProtoVer + } +}) -> rabbit_global_counters:publisher_deleted(ProtoVer); maybe_decrement_publisher(_) -> ok. %% Multiple subscriptions from the same connection count as one consumer. -maybe_increment_consumer(#state{subscriptions = OldSubs}, - #state{subscriptions = NewSubs, - cfg = #cfg{proto_ver = ProtoVer}}) - when map_size(OldSubs) =:= 0 andalso - map_size(NewSubs) > 0 -> +maybe_increment_consumer( + #state{subscriptions = OldSubs}, + #state{ + subscriptions = NewSubs, + cfg = #cfg{proto_ver = ProtoVer} + } +) when + map_size(OldSubs) =:= 0 andalso + map_size(NewSubs) > 0 +-> rabbit_global_counters:consumer_created(ProtoVer); maybe_increment_consumer(_, _) -> ok. -maybe_decrement_consumer(#state{subscriptions = Subs, - cfg = #cfg{proto_ver = ProtoVer}}) - when map_size(Subs) > 0 -> +maybe_decrement_consumer(#state{ + subscriptions = Subs, + cfg = #cfg{proto_ver = ProtoVer} +}) when + map_size(Subs) > 0 +-> rabbit_global_counters:consumer_deleted(ProtoVer); maybe_decrement_consumer(_) -> ok. -maybe_decrement_consumer(#state{subscriptions = OldSubs}, - #state{subscriptions = NewSubs, - cfg = #cfg{proto_ver = ProtoVer}}) - when map_size(OldSubs) > 0 andalso - map_size(NewSubs) =:= 0 -> +maybe_decrement_consumer( + #state{subscriptions = OldSubs}, + #state{ + subscriptions = NewSubs, + cfg = #cfg{proto_ver = ProtoVer} + } +) when + map_size(OldSubs) > 0 andalso + map_size(NewSubs) =:= 0 +-> rabbit_global_counters:consumer_deleted(ProtoVer); maybe_decrement_consumer(_, _) -> ok. -message_acknowledged(QName, #state{queue_states = QStates, - cfg = #cfg{proto_ver = ProtoVer}}) -> +message_acknowledged(QName, #state{ + queue_states = QStates, + cfg = #cfg{proto_ver = ProtoVer} +}) -> case rabbit_queue_type:module(QName, QStates) of {ok, QType} -> rabbit_global_counters:messages_acknowledged(ProtoVer, QType, 1); @@ -1908,17 +2424,27 @@ message_acknowledged(QName, #state{queue_states = QStates, ok end. -message_delivered(?QUEUE_TYPE_QOS_0, false, ?QOS_0, - #state{cfg = #cfg{proto_ver = ProtoVer}}) -> +message_delivered( + ?QUEUE_TYPE_QOS_0, + false, + ?QOS_0, + #state{cfg = #cfg{proto_ver = ProtoVer}} +) -> rabbit_global_counters:messages_delivered(ProtoVer, ?QUEUE_TYPE_QOS_0, 1), %% Technically, the message is not acked to a queue at all. %% However, from a user perspective it is still auto acked because: %% "In automatic acknowledgement mode, a message is considered to be successfully %% delivered immediately after it is sent." rabbit_global_counters:messages_delivered_consume_auto_ack(ProtoVer, ?QUEUE_TYPE_QOS_0, 1); -message_delivered(QName, Redelivered, QoS, - #state{queue_states = QStates, - cfg = #cfg{proto_ver = ProtoVer}}) -> +message_delivered( + QName, + Redelivered, + QoS, + #state{ + queue_states = QStates, + cfg = #cfg{proto_ver = ProtoVer} + } +) -> case rabbit_queue_type:module(QName, QStates) of {ok, QType} -> rabbit_global_counters:messages_delivered(ProtoVer, QType, 1), @@ -1940,65 +2466,72 @@ message_redelivered(_, _, _) -> -spec format_status(state()) -> map(). format_status( - #state{queue_states = QState, - unacked_client_pubs = UnackClientPubs, - unacked_server_pubs = UnackSerPubs, - packet_id = PackID, - subscriptions = Subscriptions, - auth_state = AuthState, - register_state = RegisterState, - queues_soft_limit_exceeded = QSLE, - qos0_messages_dropped = Qos0MsgsDropped, - cfg = #cfg{ - socket = Socket, - proto_ver = ProtoVersion, - clean_sess = CleanSess, - will_msg = WillMsg, - exchange = Exchange, - queue_qos1 = _, - published = Published, - ssl_login_name = SSLLoginName, - retainer_pid = RetainerPid, - delivery_flow = DeliveryFlow, - trace_state = TraceState, - prefetch = Prefetch, - client_id = ClientID, - conn_name = ConnName, - peer_addr = PeerAddr, - host = Host, - port = Port, - peer_host = PeerHost, - peer_port = PeerPort, - connected_at = ConnectedAt, - send_fun = _ - }}) -> - Cfg = #{socket => Socket, - proto_ver => ProtoVersion, - clean_sess => CleanSess, - will_msg_defined => WillMsg =/= undefined, - exchange => Exchange, - published => Published, - ssl_login_name => SSLLoginName, - retainer_pid => RetainerPid, - - delivery_flow => DeliveryFlow, - trace_state => TraceState, - prefetch => Prefetch, - client_id => ClientID, - conn_name => ConnName, - peer_addr => PeerAddr, - host => Host, - port => Port, - peer_host => PeerHost, - peer_port => PeerPort, - connected_at => ConnectedAt}, - #{cfg => Cfg, - queue_states => rabbit_queue_type:format_status(QState), - unacked_client_pubs => UnackClientPubs, - unacked_server_pubs => UnackSerPubs, - packet_id => PackID, - subscriptions => Subscriptions, - auth_state => AuthState, - register_state => RegisterState, - queues_soft_limit_exceeded => QSLE, - qos0_messages_dropped => Qos0MsgsDropped}. + #state{ + queue_states = QState, + unacked_client_pubs = UnackClientPubs, + unacked_server_pubs = UnackSerPubs, + packet_id = PackID, + subscriptions = Subscriptions, + auth_state = AuthState, + register_state = RegisterState, + queues_soft_limit_exceeded = QSLE, + qos0_messages_dropped = Qos0MsgsDropped, + cfg = #cfg{ + socket = Socket, + proto_ver = ProtoVersion, + clean_sess = CleanSess, + will_msg = WillMsg, + exchange = Exchange, + queue_qos1 = _, + published = Published, + ssl_login_name = SSLLoginName, + retainer_pid = RetainerPid, + delivery_flow = DeliveryFlow, + trace_state = TraceState, + prefetch = Prefetch, + client_id = ClientID, + conn_name = ConnName, + peer_addr = PeerAddr, + host = Host, + port = Port, + peer_host = PeerHost, + peer_port = PeerPort, + connected_at = ConnectedAt, + send_fun = _ + } + } +) -> + Cfg = #{ + socket => Socket, + proto_ver => ProtoVersion, + clean_sess => CleanSess, + will_msg_defined => WillMsg =/= undefined, + exchange => Exchange, + published => Published, + ssl_login_name => SSLLoginName, + retainer_pid => RetainerPid, + + delivery_flow => DeliveryFlow, + trace_state => TraceState, + prefetch => Prefetch, + client_id => ClientID, + conn_name => ConnName, + peer_addr => PeerAddr, + host => Host, + port => Port, + peer_host => PeerHost, + peer_port => PeerPort, + connected_at => ConnectedAt + }, + #{ + cfg => Cfg, + queue_states => rabbit_queue_type:format_status(QState), + unacked_client_pubs => UnackClientPubs, + unacked_server_pubs => UnackSerPubs, + packet_id => PackID, + subscriptions => Subscriptions, + auth_state => AuthState, + register_state => RegisterState, + queues_soft_limit_exceeded => QSLE, + qos0_messages_dropped => Qos0MsgsDropped + }. diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_qos0_queue.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_qos0_queue.erl index deb0eff622..1244c96483 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_qos0_queue.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_qos0_queue.erl @@ -24,42 +24,50 @@ %% 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(). @@ -67,8 +75,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. @@ -76,23 +84,29 @@ 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), @@ -102,35 +116,41 @@ 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}. @@ -152,8 +172,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. @@ -161,20 +181,24 @@ 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()}. @@ -203,11 +227,13 @@ 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, _) -> @@ -249,26 +275,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]). diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl index 3949989230..6f32816492 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl @@ -14,11 +14,20 @@ -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([conserve_resources/3, - close_connection/2]). +-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([info/2]). @@ -29,22 +38,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() - }). - --type(state() :: #state{}). +-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{}. %%---------------------------------------------------------------------------- @@ -52,9 +61,11 @@ 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. @@ -73,8 +84,10 @@ 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} -> @@ -84,15 +97,17 @@ 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); @@ -108,51 +123,67 @@ 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}. @@ -161,49 +192,53 @@ 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}; @@ -213,7 +248,6 @@ handle_info({keepalive, Req}, State = #state{keepalive = KState0, {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}) -> @@ -224,43 +258,47 @@ 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), @@ -268,36 +306,25 @@ terminate(Reason, {SendWill, State = #state{conn_name = ConnName, 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. @@ -309,54 +336,80 @@ 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]), @@ -365,9 +418,11 @@ process_received_bytes(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]), @@ -375,8 +430,8 @@ process_received_bytes(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) -> @@ -387,9 +442,13 @@ 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 @@ -397,62 +456,77 @@ network_error(closed, 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 }. - -control_throttle(State = #state{connection_state = ConnState, - conserve = Conserve, - received_connect_packet = Connected, - proc_state = PState, - keepalive = KState - }) -> + State#state{await_recv = true}. + +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), @@ -465,12 +539,13 @@ 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; @@ -494,17 +569,19 @@ 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); @@ -514,40 +591,48 @@ 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 + }. diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_dets.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_dets.erl index 8dbd06a440..bfaa0cd146 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_dets.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_dets.erl @@ -13,53 +13,57 @@ -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)} ]. diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_ets.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_ets.erl index 3a0a7384db..bbc7a77390 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_ets.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_ets.erl @@ -13,49 +13,51 @@ -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]}]). diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_noop.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_noop.erl index 1e62ed5abe..5f6f0ef56b 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_noop.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_retained_msg_store_noop.erl @@ -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. diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_retainer.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_retainer.erl index 1c632eb597..8df5d80ff7 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_retainer.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_retainer.erl @@ -12,15 +12,23 @@ -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 +}). %%---------------------------------------------------------------------------- @@ -46,12 +54,19 @@ 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(). @@ -63,26 +78,33 @@ 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}. diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_retainer_sup.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_retainer_sup.erl index eadaab7ca2..f2b2a38867 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_retainer_sup.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_retainer_sup.erl @@ -8,8 +8,13 @@ -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(). @@ -19,13 +24,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( @@ -41,18 +46,20 @@ 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 diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_sup.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_sup.erl index a04ef73cb9..5aef65bc2d 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_sup.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_sup.erl @@ -23,52 +23,61 @@ 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() -> @@ -89,33 +98,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; diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_util.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_util.erl index d098d3ff93..89428c5965 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_util.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_util.erl @@ -11,20 +11,21 @@ -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). @@ -72,7 +73,8 @@ 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 -> @@ -102,12 +104,13 @@ 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; @@ -142,11 +145,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; @@ -161,11 +164,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(). @@ -174,8 +177,9 @@ 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 @@ -183,10 +187,12 @@ register_clientid(Vhost, ClientId) %% 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. @@ -194,18 +200,24 @@ 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). diff --git a/deps/rabbitmq_mqtt/test/auth_SUITE.erl b/deps/rabbitmq_mqtt/test/auth_SUITE.erl index 63ac362842..f2ad72f857 100644 --- a/deps/rabbitmq_mqtt/test/auth_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/auth_SUITE.erl @@ -5,8 +5,10 @@ %% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved. %% -module(auth_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"). @@ -14,79 +16,78 @@ %% defined in MQTT v4 and v5 (not in v3) -define(SUBACK_FAILURE, 16#80). --define(FAIL_IF_CRASH_LOG, {["Generic server.*terminating"], - fun () -> ct:fail(crash_detected) end}). +-define(FAIL_IF_CRASH_LOG, {["Generic server.*terminating"], fun() -> ct:fail(crash_detected) end}). -import(rabbit_ct_broker_helpers, [rpc/5]). all() -> - [{group, anonymous_no_ssl_user}, - {group, anonymous_ssl_user}, - {group, no_ssl_user}, - {group, ssl_user}, - {group, client_id_propagation}, - {group, authz}, - {group, limit}]. + [ + {group, anonymous_no_ssl_user}, + {group, anonymous_ssl_user}, + {group, no_ssl_user}, + {group, ssl_user}, + {group, client_id_propagation}, + {group, authz}, + {group, limit} + ]. groups() -> - [{anonymous_ssl_user, [], - [anonymous_auth_success, - user_credentials_auth, - ssl_user_auth_success, - ssl_user_vhost_not_allowed, - ssl_user_vhost_parameter_mapping_success, - ssl_user_vhost_parameter_mapping_not_allowed, - ssl_user_vhost_parameter_mapping_vhost_does_not_exist, - ssl_user_port_vhost_mapping_takes_precedence_over_cert_vhost_mapping - ]}, - {anonymous_no_ssl_user, [], - [anonymous_auth_success, - user_credentials_auth, - port_vhost_mapping_success, - port_vhost_mapping_success_no_mapping, - port_vhost_mapping_not_allowed, - port_vhost_mapping_vhost_does_not_exist - %% SSL auth will succeed, because we cannot ignore anonymous - ]}, - {ssl_user, [], - [anonymous_auth_failure, - user_credentials_auth, - ssl_user_auth_success, - ssl_user_vhost_not_allowed, - ssl_user_vhost_parameter_mapping_success, - ssl_user_vhost_parameter_mapping_not_allowed, - ssl_user_vhost_parameter_mapping_vhost_does_not_exist, - ssl_user_port_vhost_mapping_takes_precedence_over_cert_vhost_mapping - ]}, - {no_ssl_user, [], - [anonymous_auth_failure, - user_credentials_auth, - ssl_user_auth_failure, - port_vhost_mapping_success, - port_vhost_mapping_success_no_mapping, - port_vhost_mapping_not_allowed, - port_vhost_mapping_vhost_does_not_exist - ]}, - {client_id_propagation, [], - [client_id_propagation] - }, - {authz, [], - [no_queue_bind_permission, - no_queue_unbind_permission, - no_queue_consume_permission, - no_queue_consume_permission_on_connect, - no_queue_delete_permission, - no_queue_declare_permission, - no_publish_permission, - no_topic_read_permission, - no_topic_write_permission, - loopback_user_connects_from_remote_host - ] - }, - {limit, [], - [vhost_connection_limit, - vhost_queue_limit, - user_connection_limit - ]} + [ + {anonymous_ssl_user, [], [ + anonymous_auth_success, + user_credentials_auth, + ssl_user_auth_success, + ssl_user_vhost_not_allowed, + ssl_user_vhost_parameter_mapping_success, + ssl_user_vhost_parameter_mapping_not_allowed, + ssl_user_vhost_parameter_mapping_vhost_does_not_exist, + ssl_user_port_vhost_mapping_takes_precedence_over_cert_vhost_mapping + ]}, + {anonymous_no_ssl_user, [], [ + anonymous_auth_success, + user_credentials_auth, + port_vhost_mapping_success, + port_vhost_mapping_success_no_mapping, + port_vhost_mapping_not_allowed, + port_vhost_mapping_vhost_does_not_exist + %% SSL auth will succeed, because we cannot ignore anonymous + ]}, + {ssl_user, [], [ + anonymous_auth_failure, + user_credentials_auth, + ssl_user_auth_success, + ssl_user_vhost_not_allowed, + ssl_user_vhost_parameter_mapping_success, + ssl_user_vhost_parameter_mapping_not_allowed, + ssl_user_vhost_parameter_mapping_vhost_does_not_exist, + ssl_user_port_vhost_mapping_takes_precedence_over_cert_vhost_mapping + ]}, + {no_ssl_user, [], [ + anonymous_auth_failure, + user_credentials_auth, + ssl_user_auth_failure, + port_vhost_mapping_success, + port_vhost_mapping_success_no_mapping, + port_vhost_mapping_not_allowed, + port_vhost_mapping_vhost_does_not_exist + ]}, + {client_id_propagation, [], [client_id_propagation]}, + {authz, [], [ + no_queue_bind_permission, + no_queue_unbind_permission, + no_queue_consume_permission, + no_queue_consume_permission_on_connect, + no_queue_delete_permission, + no_queue_declare_permission, + no_publish_permission, + no_topic_read_permission, + no_topic_write_permission, + loopback_user_connects_from_remote_host + ]}, + {limit, [], [ + vhost_connection_limit, + vhost_queue_limit, + user_connection_limit + ]} ]. init_per_suite(Config) -> @@ -100,19 +101,29 @@ init_per_group(authz, Config0) -> User = <<"mqtt-user">>, Password = <<"mqtt-password">>, VHost = <<"mqtt-vhost">>, - MqttConfig = {rabbitmq_mqtt, [{default_user, User} - ,{default_pass, Password} - ,{allow_anonymous, true} - ,{vhost, VHost} - ,{exchange, <<"amq.topic">>} - ]}, - Config1 = rabbit_ct_helpers:run_setup_steps(rabbit_ct_helpers:merge_app_env(Config0, MqttConfig), - rabbit_ct_broker_helpers:setup_steps() ++ - rabbit_ct_client_helpers:setup_steps()), + MqttConfig = + {rabbitmq_mqtt, [ + {default_user, User}, + {default_pass, Password}, + {allow_anonymous, true}, + {vhost, VHost}, + {exchange, <<"amq.topic">>} + ]}, + Config1 = rabbit_ct_helpers:run_setup_steps( + rabbit_ct_helpers:merge_app_env(Config0, MqttConfig), + rabbit_ct_broker_helpers:setup_steps() ++ + rabbit_ct_client_helpers:setup_steps() + ), rabbit_ct_broker_helpers:add_user(Config1, User, Password), rabbit_ct_broker_helpers:add_vhost(Config1, VHost), - [Log|_] = rpc(Config1, 0, rabbit, log_locations, []), - [{mqtt_user, User}, {mqtt_vhost, VHost}, {mqtt_password, Password}, {log_location, Log}|Config1]; + [Log | _] = rpc(Config1, 0, rabbit, log_locations, []), + [ + {mqtt_user, User}, + {mqtt_vhost, VHost}, + {mqtt_password, Password}, + {log_location, Log} + | Config1 + ]; init_per_group(Group, Config) -> Suffix = rabbit_ct_helpers:testcase_absname(Config, "", "-"), Config1 = rabbit_ct_helpers:set_config(Config, [ @@ -121,56 +132,77 @@ init_per_group(Group, Config) -> ]), MqttConfig = mqtt_config(Group), AuthConfig = auth_config(Group), - rabbit_ct_helpers:run_setup_steps(Config1, - [ fun(Conf) -> case MqttConfig of - undefined -> Conf; - _ -> merge_app_env(MqttConfig, Conf) - end - end] ++ - [ fun(Conf) -> case AuthConfig of - undefined -> Conf; - _ -> merge_app_env(AuthConfig, Conf) - end - end ] ++ - rabbit_ct_broker_helpers:setup_steps() ++ - rabbit_ct_client_helpers:setup_steps()). + rabbit_ct_helpers:run_setup_steps( + Config1, + [ + fun(Conf) -> + case MqttConfig of + undefined -> Conf; + _ -> merge_app_env(MqttConfig, Conf) + end + end + ] ++ + [ + fun(Conf) -> + case AuthConfig of + undefined -> Conf; + _ -> merge_app_env(AuthConfig, Conf) + end + 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()). + rabbit_ct_helpers:run_teardown_steps( + Config, + rabbit_ct_client_helpers:teardown_steps() ++ + rabbit_ct_broker_helpers:teardown_steps() + ). merge_app_env(MqttConfig, Config) -> rabbit_ct_helpers:merge_app_env(Config, MqttConfig). mqtt_config(anonymous_ssl_user) -> - {rabbitmq_mqtt, [{ssl_cert_login, true}, - {allow_anonymous, true}]}; + {rabbitmq_mqtt, [ + {ssl_cert_login, true}, + {allow_anonymous, true} + ]}; mqtt_config(anonymous_no_ssl_user) -> - {rabbitmq_mqtt, [{ssl_cert_login, false}, - {allow_anonymous, true}]}; + {rabbitmq_mqtt, [ + {ssl_cert_login, false}, + {allow_anonymous, true} + ]}; mqtt_config(ssl_user) -> - {rabbitmq_mqtt, [{ssl_cert_login, true}, - {allow_anonymous, false}]}; + {rabbitmq_mqtt, [ + {ssl_cert_login, true}, + {allow_anonymous, false} + ]}; mqtt_config(no_ssl_user) -> - {rabbitmq_mqtt, [{ssl_cert_login, false}, - {allow_anonymous, false}]}; + {rabbitmq_mqtt, [ + {ssl_cert_login, false}, + {allow_anonymous, false} + ]}; mqtt_config(client_id_propagation) -> - {rabbitmq_mqtt, [{ssl_cert_login, true}, - {allow_anonymous, true}]}; + {rabbitmq_mqtt, [ + {ssl_cert_login, true}, + {allow_anonymous, true} + ]}; mqtt_config(_) -> undefined. auth_config(client_id_propagation) -> {rabbit, [ - {auth_backends, [rabbit_auth_backend_mqtt_mock]} - ] - }; + {auth_backends, [rabbit_auth_backend_mqtt_mock]} + ]}; auth_config(_) -> undefined. -init_per_testcase(Testcase, Config) when Testcase == ssl_user_auth_success; - Testcase == ssl_user_auth_failure -> +init_per_testcase(Testcase, Config) when + Testcase == ssl_user_auth_success; + Testcase == ssl_user_auth_failure +-> Config1 = set_cert_user_on_default_vhost(Config), rabbit_ct_helpers:testcase_started(Config1, Testcase); init_per_testcase(ssl_user_vhost_parameter_mapping_success, Config) -> @@ -191,8 +223,10 @@ init_per_testcase(user_credentials_auth, Config) -> Pass = <<"new-user-pass">>, ok = rabbit_ct_broker_helpers:add_user(Config, 0, User, Pass), ok = rabbit_ct_broker_helpers:set_full_permissions(Config, User, <<"/">>), - Config1 = rabbit_ct_helpers:set_config(Config, [{new_user, User}, - {new_user_pass, Pass}]), + Config1 = rabbit_ct_helpers:set_config(Config, [ + {new_user, User}, + {new_user_pass, Pass} + ]), rabbit_ct_helpers:testcase_started(Config1, user_credentials_auth); init_per_testcase(ssl_user_vhost_not_allowed, Config) -> Config1 = set_cert_user_on_default_vhost(Config), @@ -205,7 +239,9 @@ init_per_testcase(ssl_user_vhost_parameter_mapping_vhost_does_not_exist, Config) Config2 = set_vhost_for_cert_user(Config1, User), VhostForCertUser = ?config(temp_vhost_for_ssl_user, Config2), ok = rabbit_ct_broker_helpers:delete_vhost(Config, VhostForCertUser), - rabbit_ct_helpers:testcase_started(Config1, ssl_user_vhost_parameter_mapping_vhost_does_not_exist); + rabbit_ct_helpers:testcase_started( + Config1, ssl_user_vhost_parameter_mapping_vhost_does_not_exist + ); init_per_testcase(port_vhost_mapping_success, Config) -> User = <<"guest">>, Config1 = set_vhost_for_port_vhost_mapping_user(Config, User), @@ -215,9 +251,12 @@ init_per_testcase(port_vhost_mapping_success_no_mapping, Config) -> User = <<"guest">>, Config1 = set_vhost_for_port_vhost_mapping_user(Config, User), PortToVHostMappingParameter = [ - {<<"1">>, <<"unlikely to exist">>}, - {<<"2">>, <<"unlikely to exist">>}], - ok = rabbit_ct_broker_helpers:set_global_parameter(Config, mqtt_port_to_vhost_mapping, PortToVHostMappingParameter), + {<<"1">>, <<"unlikely to exist">>}, + {<<"2">>, <<"unlikely to exist">>} + ], + ok = rabbit_ct_broker_helpers:set_global_parameter( + Config, mqtt_port_to_vhost_mapping, PortToVHostMappingParameter + ), VHost = ?config(temp_vhost_for_port_mapping, Config1), rabbit_ct_broker_helpers:clear_permissions(Config1, User, VHost), rabbit_ct_helpers:testcase_started(Config1, port_vhost_mapping_success_no_mapping); @@ -245,7 +284,9 @@ init_per_testcase(ssl_user_port_vhost_mapping_takes_precedence_over_cert_vhost_m rabbit_ct_broker_helpers:clear_permissions(Config3, User, VhostForPortMapping), rabbit_ct_broker_helpers:clear_permissions(Config3, User, <<"/">>), - rabbit_ct_helpers:testcase_started(Config3, ssl_user_port_vhost_mapping_takes_precedence_over_cert_vhost_mapping); + rabbit_ct_helpers:testcase_started( + Config3, ssl_user_port_vhost_mapping_takes_precedence_over_cert_vhost_mapping + ); init_per_testcase(Testcase, Config) -> rabbit_ct_helpers:testcase_started(Config, Testcase). @@ -268,7 +309,9 @@ set_vhost_for_cert_user(Config, User) -> ], ok = rabbit_ct_broker_helpers:add_vhost(Config, VhostForCertUser), ok = rabbit_ct_broker_helpers:set_full_permissions(Config, User, VhostForCertUser), - ok = rabbit_ct_broker_helpers:set_global_parameter(Config, mqtt_default_vhosts, UserToVHostMappingParameter), + ok = rabbit_ct_broker_helpers:set_global_parameter( + Config, mqtt_default_vhosts, UserToVHostMappingParameter + ), rabbit_ct_helpers:set_config(Config, [{temp_vhost_for_ssl_user, VhostForCertUser}]). set_vhost_for_port_vhost_mapping_user(Config, User) -> @@ -276,24 +319,29 @@ set_vhost_for_port_vhost_mapping_user(Config, User) -> Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt), TlsPort = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt_tls), PortToVHostMappingParameter = [ - {integer_to_binary(Port), VhostForPortMapping}, - {<<"1884">>, <<"vhost2">>}, + {integer_to_binary(Port), VhostForPortMapping}, + {<<"1884">>, <<"vhost2">>}, {integer_to_binary(TlsPort), VhostForPortMapping}, - {<<"8884">>, <<"vhost2">>} - + {<<"8884">>, <<"vhost2">>} ], ok = rabbit_ct_broker_helpers:add_vhost(Config, VhostForPortMapping), ok = rabbit_ct_broker_helpers:set_full_permissions(Config, User, VhostForPortMapping), - ok = rabbit_ct_broker_helpers:set_global_parameter(Config, mqtt_port_to_vhost_mapping, PortToVHostMappingParameter), + ok = rabbit_ct_broker_helpers:set_global_parameter( + Config, mqtt_port_to_vhost_mapping, PortToVHostMappingParameter + ), rabbit_ct_helpers:set_config(Config, [{temp_vhost_for_port_mapping, VhostForPortMapping}]). -end_per_testcase(Testcase, Config) when Testcase == ssl_user_auth_success; - Testcase == ssl_user_auth_failure; - Testcase == ssl_user_vhost_not_allowed -> +end_per_testcase(Testcase, Config) when + Testcase == ssl_user_auth_success; + Testcase == ssl_user_auth_failure; + Testcase == ssl_user_vhost_not_allowed +-> delete_cert_user(Config), rabbit_ct_helpers:testcase_finished(Config, Testcase); -end_per_testcase(TestCase, Config) when TestCase == ssl_user_vhost_parameter_mapping_success; - TestCase == ssl_user_vhost_parameter_mapping_not_allowed -> +end_per_testcase(TestCase, Config) when + TestCase == ssl_user_vhost_parameter_mapping_success; + TestCase == ssl_user_vhost_parameter_mapping_not_allowed +-> delete_cert_user(Config), VhostForCertUser = ?config(temp_vhost_for_ssl_user, Config), ok = rabbit_ct_broker_helpers:delete_vhost(Config, VhostForCertUser), @@ -301,15 +349,19 @@ end_per_testcase(TestCase, Config) when TestCase == ssl_user_vhost_parameter_map rabbit_ct_helpers:testcase_finished(Config, TestCase); end_per_testcase(user_credentials_auth, Config) -> User = ?config(new_user, Config), - {ok,_} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["delete_user", User]), + {ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["delete_user", User]), rabbit_ct_helpers:testcase_finished(Config, user_credentials_auth); end_per_testcase(ssl_user_vhost_parameter_mapping_vhost_does_not_exist, Config) -> delete_cert_user(Config), ok = rabbit_ct_broker_helpers:clear_global_parameter(Config, mqtt_default_vhosts), - rabbit_ct_helpers:testcase_finished(Config, ssl_user_vhost_parameter_mapping_vhost_does_not_exist); -end_per_testcase(Testcase, Config) when Testcase == port_vhost_mapping_success; - Testcase == port_vhost_mapping_not_allowed; - Testcase == port_vhost_mapping_success_no_mapping -> + rabbit_ct_helpers:testcase_finished( + Config, ssl_user_vhost_parameter_mapping_vhost_does_not_exist + ); +end_per_testcase(Testcase, Config) when + Testcase == port_vhost_mapping_success; + Testcase == port_vhost_mapping_not_allowed; + Testcase == port_vhost_mapping_success_no_mapping +-> User = <<"guest">>, rabbit_ct_broker_helpers:set_full_permissions(Config, User, <<"/">>), VHost = ?config(temp_vhost_for_port_mapping, Config), @@ -330,24 +382,31 @@ end_per_testcase(ssl_user_port_vhost_mapping_takes_precedence_over_cert_vhost_ma VHostForPortVHostMapping = ?config(temp_vhost_for_port_mapping, Config), ok = rabbit_ct_broker_helpers:delete_vhost(Config, VHostForPortVHostMapping), ok = rabbit_ct_broker_helpers:clear_global_parameter(Config, mqtt_port_to_vhost_mapping), - rabbit_ct_helpers:testcase_finished(Config, ssl_user_port_vhost_mapping_takes_precedence_over_cert_vhost_mapping); -end_per_testcase(Testcase, Config) when Testcase == no_queue_bind_permission; - Testcase == no_queue_unbind_permission; - Testcase == no_queue_consume_permission; - Testcase == no_queue_consume_permission_on_connect; - Testcase == no_queue_delete_permission; - Testcase == no_queue_declare_permission; - Testcase == no_publish_permission; - Testcase == no_topic_read_permission; - Testcase == no_topic_write_permission; - Testcase == loopback_user_connects_from_remote_host -> + rabbit_ct_helpers:testcase_finished( + Config, ssl_user_port_vhost_mapping_takes_precedence_over_cert_vhost_mapping + ); +end_per_testcase(Testcase, Config) when + Testcase == no_queue_bind_permission; + Testcase == no_queue_unbind_permission; + Testcase == no_queue_consume_permission; + Testcase == no_queue_consume_permission_on_connect; + Testcase == no_queue_delete_permission; + Testcase == no_queue_declare_permission; + Testcase == no_publish_permission; + Testcase == no_topic_read_permission; + Testcase == no_topic_write_permission; + Testcase == loopback_user_connects_from_remote_host +-> %% So let's wait before logs are surely flushed Marker = "MQTT_AUTH_SUITE_MARKER", rpc(Config, 0, rabbit_log, error, [Marker]), - wait_log(Config, [{[Marker], fun () -> stop end}]), + wait_log(Config, [{[Marker], fun() -> stop end}]), %% Preserve file contents in case some investigation is needed, before truncating. - file:copy(?config(log_location, Config), iolist_to_binary([?config(log_location, Config), ".", atom_to_binary(Testcase)])), + file:copy( + ?config(log_location, Config), + iolist_to_binary([?config(log_location, Config), ".", atom_to_binary(Testcase)]) + ), %% And provide an empty log file for the next test in this group file:write_file(?config(log_location, Config), <<>>), @@ -358,7 +417,7 @@ end_per_testcase(Testcase, Config) -> delete_cert_user(Config) -> User = ?config(temp_ssl_user, Config), - {ok,_} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["delete_user", User]). + {ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["delete_user", User]). anonymous_auth_success(Config) -> expect_successful_connection(fun connect_anonymous/1, Config). @@ -366,7 +425,6 @@ anonymous_auth_success(Config) -> anonymous_auth_failure(Config) -> expect_authentication_failure(fun connect_anonymous/1, Config). - ssl_user_auth_success(Config) -> expect_successful_connection(fun connect_ssl/1, Config). @@ -379,31 +437,38 @@ user_credentials_auth(Config) -> expect_successful_connection( fun(Conf) -> connect_user(NewUser, NewUserPass, Conf) end, - Config), + Config + ), expect_successful_connection( fun(Conf) -> connect_user(<<"guest">>, <<"guest">>, Conf) end, - Config), + Config + ), expect_successful_connection( fun(Conf) -> connect_user(<<"/:guest">>, <<"guest">>, Conf) end, - Config), + Config + ), expect_authentication_failure( fun(Conf) -> connect_user(NewUser, <<"invalid_pass">>, Conf) end, - Config), + Config + ), expect_authentication_failure( fun(Conf) -> connect_user(undefined, <<"pass">>, Conf) end, - Config), + Config + ), expect_authentication_failure( fun(Conf) -> connect_user(NewUser, undefined, Conf) end, - Config), + Config + ), expect_authentication_failure( fun(Conf) -> connect_user(<<"non-existing-vhost:guest">>, <<"guest">>, Conf) end, - Config). + Config + ). ssl_user_vhost_parameter_mapping_success(Config) -> expect_successful_connection(fun connect_ssl/1, Config). @@ -420,7 +485,8 @@ ssl_user_vhost_parameter_mapping_vhost_does_not_exist(Config) -> port_vhost_mapping_success(Config) -> expect_successful_connection( fun(Conf) -> connect_user(<<"guest">>, <<"guest">>, Conf) end, - Config). + Config + ). port_vhost_mapping_success_no_mapping(Config) -> %% no vhost mapping for the port, falling back to default vhost @@ -450,68 +516,92 @@ connect_anonymous(Config) -> connect_anonymous(Config, ClientId) -> P = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt), - emqtt:start_link([{host, "localhost"}, - {port, P}, - {clientid, ClientId}, - {proto_ver, v4}]). + emqtt:start_link([ + {host, "localhost"}, + {port, P}, + {clientid, ClientId}, + {proto_ver, v4} + ]). connect_ssl(Config) -> CertsDir = ?config(rmq_certsdir, Config), - SSLConfig = [{cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}, - {certfile, filename:join([CertsDir, "client", "cert.pem"])}, - {keyfile, filename:join([CertsDir, "client", "key.pem"])}], + SSLConfig = [ + {cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}, + {certfile, filename:join([CertsDir, "client", "cert.pem"])}, + {keyfile, filename:join([CertsDir, "client", "key.pem"])} + ], P = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt_tls), - emqtt:start_link([{host, "localhost"}, - {port, P}, - {clientid, <<"simpleClient">>}, - {proto_ver, v4}, - {ssl, true}, - {ssl_opts, SSLConfig}]). + emqtt:start_link([ + {host, "localhost"}, + {port, P}, + {clientid, <<"simpleClient">>}, + {proto_ver, v4}, + {ssl, true}, + {ssl_opts, SSLConfig} + ]). client_id_propagation(Config) -> - ok = rabbit_ct_broker_helpers:add_code_path_to_all_nodes(Config, - rabbit_auth_backend_mqtt_mock), + ok = rabbit_ct_broker_helpers:add_code_path_to_all_nodes( + Config, + rabbit_auth_backend_mqtt_mock + ), %% setup creates the ETS table required for the mqtt auth mock %% it blocks indefinitely so we need to spawn Self = self(), _ = spawn( - fun () -> - rpc(Config, 0, rabbit_auth_backend_mqtt_mock, setup, [Self]) - end), + fun() -> + rpc(Config, 0, rabbit_auth_backend_mqtt_mock, setup, [Self]) + end + ), %% the setup process will notify us receive ok -> ok - after - 3000 -> ct:fail("timeout waiting for rabbit_auth_backend_mqtt_mock:setup/1") + after 3000 -> ct:fail("timeout waiting for rabbit_auth_backend_mqtt_mock:setup/1") end, ClientId = <<"client-id-propagation">>, - {ok, C} = connect_user(<<"fake-user">>, <<"fake-password">>, - Config, ClientId), + {ok, C} = connect_user( + <<"fake-user">>, + <<"fake-password">>, + Config, + ClientId + ), {ok, _} = emqtt:connect(C), {ok, _, _} = emqtt:subscribe(C, <<"TopicA">>), - [{authentication, AuthProps}] = rpc(Config, 0, - rabbit_auth_backend_mqtt_mock, - get, - [authentication]), + [{authentication, AuthProps}] = rpc( + Config, + 0, + rabbit_auth_backend_mqtt_mock, + get, + [authentication] + ), ?assertEqual(ClientId, proplists:get_value(client_id, AuthProps)), - [{vhost_access, AuthzData}] = rpc(Config, 0, - rabbit_auth_backend_mqtt_mock, - get, - [vhost_access]), + [{vhost_access, AuthzData}] = rpc( + Config, + 0, + rabbit_auth_backend_mqtt_mock, + get, + [vhost_access] + ), ?assertEqual(ClientId, maps:get(<<"client_id">>, AuthzData)), - [{resource_access, AuthzContext}] = rpc(Config, 0, - rabbit_auth_backend_mqtt_mock, - get, - [resource_access]), + [{resource_access, AuthzContext}] = rpc( + Config, + 0, + rabbit_auth_backend_mqtt_mock, + get, + [resource_access] + ), ?assertEqual(true, maps:size(AuthzContext) > 0), ?assertEqual(ClientId, maps:get(<<"client_id">>, AuthzContext)), - [{topic_access, TopicContext}] = rpc(Config, 0, - rabbit_auth_backend_mqtt_mock, - get, - [topic_access]), + [{topic_access, TopicContext}] = rpc( + Config, + 0, + rabbit_auth_backend_mqtt_mock, + get, + [topic_access] + ), VariableMap = maps:get(variable_map, TopicContext), ?assertEqual(ClientId, maps:get(<<"client_id">>, VariableMap)), @@ -531,12 +621,13 @@ client_id_propagation(Config) -> %% flushed, and won't contaminate following tests from this group. no_queue_bind_permission(Config) -> ExpectedLogs = - ["MQTT resource access refused: write access to queue " - "'mqtt-subscription-mqtt-userqos0' in vhost 'mqtt-vhost' " - "refused for user 'mqtt-user'", - "Failed to bind queue 'mqtt-subscription-mqtt-userqos0' " - "in vhost 'mqtt-vhost' with topic test/topic: access_refused" - ], + [ + "MQTT resource access refused: write access to queue " + "'mqtt-subscription-mqtt-userqos0' in vhost 'mqtt-vhost' " + "refused for user 'mqtt-user'", + "Failed to bind queue 'mqtt-subscription-mqtt-userqos0' " + "in vhost 'mqtt-vhost' with topic test/topic: access_refused" + ], test_subscribe_permissions_combination(<<".*">>, <<"">>, <<".*">>, Config, ExpectedLogs). no_queue_unbind_permission(Config) -> @@ -544,36 +635,45 @@ no_queue_unbind_permission(Config) -> Vhost = ?config(mqtt_vhost, Config), rabbit_ct_broker_helpers:set_permissions(Config, User, Vhost, <<".*">>, <<".*">>, <<".*">>), P = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt), - Opts = [{host, "localhost"}, - {port, P}, - {proto_ver, v4}, - {clientid, User}, - {username, User}, - {password, ?config(mqtt_password, Config)}], + Opts = [ + {host, "localhost"}, + {port, P}, + {proto_ver, v4}, + {clientid, User}, + {username, User}, + {password, ?config(mqtt_password, Config)} + ], {ok, C1} = emqtt:start_link([{clean_start, false} | Opts]), {ok, _} = emqtt:connect(C1), Topic = <<"my/topic">>, - ?assertMatch({ok, _Properties, [1]}, - emqtt:subscribe(C1, Topic, qos1)), + ?assertMatch( + {ok, _Properties, [1]}, + emqtt:subscribe(C1, Topic, qos1) + ), ok = emqtt:disconnect(C1), %% Revoke read access to amq.topic exchange. - rabbit_ct_broker_helpers:set_permissions(Config, User, Vhost, <<".*">>, <<".*">>, <<"^(?!amq\.topic$)">>), + rabbit_ct_broker_helpers:set_permissions( + Config, User, Vhost, <<".*">>, <<".*">>, <<"^(?!amq\.topic$)">> + ), {ok, C2} = emqtt:start_link([{clean_start, false} | Opts]), {ok, _} = emqtt:connect(C2), process_flag(trap_exit, true), %% We subscribe with the same client ID to the same topic again, but this time with QoS 0. %% Therefore we trigger the qos1 queue to be unbound (and the qos0 queue to be bound). %% However, unbinding requires read access to the exchange, which we don't have anymore. - ?assertMatch({ok, _Properties, [?SUBACK_FAILURE]}, - emqtt:subscribe(C2, Topic, qos0)), + ?assertMatch( + {ok, _Properties, [?SUBACK_FAILURE]}, + emqtt:subscribe(C2, Topic, qos0) + ), ok = assert_connection_closed(C2), ExpectedLogs = - ["MQTT resource access refused: read access to exchange 'amq.topic' in vhost 'mqtt-vhost' refused for user 'mqtt-user'", - "Failed to unbind queue 'mqtt-subscription-mqtt-userqos1' in vhost 'mqtt-vhost' with topic 'my/topic': access_refused", - "MQTT protocol error on connection.*: subscribe_error" - ], - wait_log(Config, [?FAIL_IF_CRASH_LOG, {ExpectedLogs, fun () -> stop end}]), + [ + "MQTT resource access refused: read access to exchange 'amq.topic' in vhost 'mqtt-vhost' refused for user 'mqtt-user'", + "Failed to unbind queue 'mqtt-subscription-mqtt-userqos1' in vhost 'mqtt-vhost' with topic 'my/topic': access_refused", + "MQTT protocol error on connection.*: subscribe_error" + ], + wait_log(Config, [?FAIL_IF_CRASH_LOG, {ExpectedLogs, fun() -> stop end}]), %% Clean up the qos1 queue by connecting with clean session. rabbit_ct_broker_helpers:set_permissions(Config, User, Vhost, <<".*">>, <<".*">>, <<".*">>), @@ -583,20 +683,25 @@ no_queue_unbind_permission(Config) -> no_queue_consume_permission(Config) -> ExpectedLogs = - ["MQTT resource access refused: read access to queue " - "'mqtt-subscription-mqtt-userqos0' in vhost 'mqtt-vhost' " - "refused for user 'mqtt-user'"], - test_subscribe_permissions_combination(<<".*">>, <<".*">>, <<"^amq\\.topic">>, Config, ExpectedLogs). + [ + "MQTT resource access refused: read access to queue " + "'mqtt-subscription-mqtt-userqos0' in vhost 'mqtt-vhost' " + "refused for user 'mqtt-user'" + ], + test_subscribe_permissions_combination( + <<".*">>, <<".*">>, <<"^amq\\.topic">>, Config, ExpectedLogs + ). no_queue_delete_permission(Config) -> set_permissions(".*", ".*", ".*", Config), ClientId = <<"no_queue_delete_permission">>, {ok, C1} = connect_user( - ?config(mqtt_user, Config), - ?config(mqtt_password, Config), - Config, - ClientId, - [{clean_start, false}]), + ?config(mqtt_user, Config), + ?config(mqtt_password, Config), + Config, + ClientId, + [{clean_start, false}] + ), {ok, _} = emqtt:connect(C1), {ok, _, _} = emqtt:subscribe(C1, {<<"test/topic">>, qos1}), ok = emqtt:disconnect(C1), @@ -605,82 +710,114 @@ no_queue_delete_permission(Config) -> %% Now we have a durable queue that user doesn't have permission to delete. %% Attempt to establish clean session should fail. {ok, C2} = connect_user( - ?config(mqtt_user, Config), - ?config(mqtt_password, Config), - Config, - ClientId, - [{clean_start, true}]), + ?config(mqtt_user, Config), + ?config(mqtt_password, Config), + Config, + ClientId, + [{clean_start, true}] + ), unlink(C2), - ?assertMatch({error, _}, - emqtt:connect(C2)), + ?assertMatch( + {error, _}, + emqtt:connect(C2) + ), wait_log( - Config, - [?FAIL_IF_CRASH_LOG - ,{[io_lib:format("MQTT resource access refused: configure access to queue " + Config, + [ + ?FAIL_IF_CRASH_LOG, + { + [ + io_lib:format( + "MQTT resource access refused: configure access to queue " "'mqtt-subscription-~sqos1' in vhost 'mqtt-vhost' refused for user 'mqtt-user'", - [ClientId]), - "MQTT connection .* is closing due to an authorization failure"], - fun() -> stop end} - ]), + [ClientId] + ), + "MQTT connection .* is closing due to an authorization failure" + ], + fun() -> stop end + } + ] + ), ok. no_queue_consume_permission_on_connect(Config) -> set_permissions(".*", ".*", ".*", Config), ClientId = <<"no_queue_consume_permission_on_connect">>, {ok, C1} = connect_user( - ?config(mqtt_user, Config), - ?config(mqtt_password, Config), - Config, - ClientId, - [{clean_start, false}]), + ?config(mqtt_user, Config), + ?config(mqtt_password, Config), + Config, + ClientId, + [{clean_start, false}] + ), {ok, _} = emqtt:connect(C1), {ok, _, _} = emqtt:subscribe(C1, {<<"test/topic">>, qos1}), ok = emqtt:disconnect(C1), set_permissions(".*", ".*", "^amq\\.topic", Config), {ok, C2} = connect_user( - ?config(mqtt_user, Config), - ?config(mqtt_password, Config), - Config, - ClientId, - [{clean_start, false}]), + ?config(mqtt_user, Config), + ?config(mqtt_password, Config), + Config, + ClientId, + [{clean_start, false}] + ), unlink(C2), - ?assertMatch({error, _}, - emqtt:connect(C2)), + ?assertMatch( + {error, _}, + emqtt:connect(C2) + ), wait_log( - Config, - [?FAIL_IF_CRASH_LOG - ,{[io_lib:format("MQTT resource access refused: read access to queue " + Config, + [ + ?FAIL_IF_CRASH_LOG, + { + [ + io_lib:format( + "MQTT resource access refused: read access to queue " "'mqtt-subscription-~sqos1' in vhost 'mqtt-vhost' refused for user 'mqtt-user'", - [ClientId]), - "MQTT connection .* is closing due to an authorization failure"], - fun () -> stop end} - ]), + [ClientId] + ), + "MQTT connection .* is closing due to an authorization failure" + ], + fun() -> stop end + } + ] + ), ok. no_queue_declare_permission(Config) -> set_permissions("", ".*", ".*", Config), ClientId = <<"no_queue_declare_permission">>, {ok, C} = connect_user( - ?config(mqtt_user, Config), - ?config(mqtt_password, Config), - Config, - ClientId, - [{clean_start, true}]), + ?config(mqtt_user, Config), + ?config(mqtt_password, Config), + Config, + ClientId, + [{clean_start, true}] + ), {ok, _} = emqtt:connect(C), process_flag(trap_exit, true), {ok, _, [?SUBACK_FAILURE]} = emqtt:subscribe(C, <<"test/topic">>, qos0), ok = assert_connection_closed(C), wait_log( - Config, - [?FAIL_IF_CRASH_LOG - ,{[io_lib:format("MQTT resource access refused: configure access to queue " + Config, + [ + ?FAIL_IF_CRASH_LOG, + { + [ + io_lib:format( + "MQTT resource access refused: configure access to queue " "'mqtt-subscription-~sqos0' in vhost 'mqtt-vhost' refused for user 'mqtt-user'", - [ClientId]), - "MQTT protocol error on connection .*: subscribe_error"], - fun () -> stop end} - ]), + [ClientId] + ), + "MQTT protocol error on connection .*: subscribe_error" + ], + fun() -> stop end + } + ] + ), ok. no_publish_permission(Config) -> @@ -689,13 +826,20 @@ no_publish_permission(Config) -> process_flag(trap_exit, true), ok = emqtt:publish(C, <<"some/topic">>, <<"payload">>), assert_connection_closed(C), - wait_log(Config, - [?FAIL_IF_CRASH_LOG - ,{["MQTT resource access refused: write access to exchange " - "'amq.topic' in vhost 'mqtt-vhost' refused for user 'mqtt-user'", - "MQTT connection .* is closing due to an authorization failure"], - fun () -> stop end} - ]), + wait_log( + Config, + [ + ?FAIL_IF_CRASH_LOG, + { + [ + "MQTT resource access refused: write access to exchange " + "'amq.topic' in vhost 'mqtt-vhost' refused for user 'mqtt-user'", + "MQTT connection .* is closing due to an authorization failure" + ], + fun() -> stop end + } + ] + ), ok. no_topic_read_permission(Config) -> @@ -709,15 +853,21 @@ no_topic_read_permission(Config) -> process_flag(trap_exit, true), {ok, _, [?SUBACK_FAILURE]} = emqtt:subscribe(C, <<"test/topic">>), ok = assert_connection_closed(C), - wait_log(Config, - [?FAIL_IF_CRASH_LOG, - {["MQTT topic access refused: read access to topic 'test.topic' in exchange " - "'amq.topic' in vhost 'mqtt-vhost' refused for user 'mqtt-user'", - "Failed to bind queue 'mqtt-subscription-mqtt-userqos0' " - "in vhost 'mqtt-vhost' with topic test/topic: access_refused" - ], - fun () -> stop end} - ]), + wait_log( + Config, + [ + ?FAIL_IF_CRASH_LOG, + { + [ + "MQTT topic access refused: read access to topic 'test.topic' in exchange " + "'amq.topic' in vhost 'mqtt-vhost' refused for user 'mqtt-user'", + "Failed to bind queue 'mqtt-subscription-mqtt-userqos0' " + "in vhost 'mqtt-vhost' with topic test/topic: access_refused" + ], + fun() -> stop end + } + ] + ), ok. no_topic_write_permission(Config) -> @@ -729,15 +879,24 @@ no_topic_write_permission(Config) -> {ok, _} = emqtt:publish(C, <<"allow-write/some/topic">>, <<"payload">>, qos1), process_flag(trap_exit, true), - ?assertMatch({error, _}, - emqtt:publish(C, <<"some/other/topic">>, <<"payload">>, qos1)), - wait_log(Config, - [?FAIL_IF_CRASH_LOG - ,{["MQTT topic access refused: write access to topic 'some.other.topic' in " - "exchange 'amq.topic' in vhost 'mqtt-vhost' refused for user 'mqtt-user'", - "MQTT connection .* is closing due to an authorization failure"], - fun () -> stop end} - ]), + ?assertMatch( + {error, _}, + emqtt:publish(C, <<"some/other/topic">>, <<"payload">>, qos1) + ), + wait_log( + Config, + [ + ?FAIL_IF_CRASH_LOG, + { + [ + "MQTT topic access refused: write access to topic 'some.other.topic' in " + "exchange 'amq.topic' in vhost 'mqtt-vhost' refused for user 'mqtt-user'", + "MQTT connection .* is closing due to an authorization failure" + ], + fun() -> stop end + } + ] + ), ok. loopback_user_connects_from_remote_host(Config) -> @@ -754,65 +913,107 @@ loopback_user_connects_from_remote_host(Config) -> process_flag(trap_exit, true), ?assertMatch({error, _}, emqtt:connect(C)), - wait_log(Config, - [?FAIL_IF_CRASH_LOG, - {["MQTT login failed: user 'mqtt-user' can only connect via localhost", - "MQTT connection .* is closing due to an authorization failure"], - fun () -> stop end} - ]), + wait_log( + Config, + [ + ?FAIL_IF_CRASH_LOG, + { + [ + "MQTT login failed: user 'mqtt-user' can only connect via localhost", + "MQTT connection .* is closing due to an authorization failure" + ], + fun() -> stop end + } + ] + ), true = rpc(Config, 0, meck, validate, [Mod]), ok = rpc(Config, 0, meck, unload, [Mod]). set_topic_permissions(WritePat, ReadPat, Config) -> - rpc(Config, 0, - rabbit_auth_backend_internal, set_topic_permissions, - [?config(mqtt_user, Config), ?config(mqtt_vhost, Config), - <<"amq.topic">>, WritePat, ReadPat, <<"acting-user">>]). + rpc( + Config, + 0, + rabbit_auth_backend_internal, + set_topic_permissions, + [ + ?config(mqtt_user, Config), + ?config(mqtt_vhost, Config), + <<"amq.topic">>, + WritePat, + ReadPat, + <<"acting-user">> + ] + ). set_permissions(PermConf, PermWrite, PermRead, Config) -> - rabbit_ct_broker_helpers:set_permissions(Config, ?config(mqtt_user, Config), ?config(mqtt_vhost, Config), - iolist_to_binary(PermConf), - iolist_to_binary(PermWrite), - iolist_to_binary(PermRead)). + rabbit_ct_broker_helpers:set_permissions( + Config, + ?config(mqtt_user, Config), + ?config(mqtt_vhost, Config), + iolist_to_binary(PermConf), + iolist_to_binary(PermWrite), + iolist_to_binary(PermRead) + ). open_mqtt_connection(Config) -> open_mqtt_connection(Config, []). open_mqtt_connection(Config, Opts) -> - {ok, C} = connect_user(?config(mqtt_user, Config), ?config(mqtt_password, Config), Config, ?config(mqtt_user, Config), Opts), + {ok, C} = connect_user( + ?config(mqtt_user, Config), + ?config(mqtt_password, Config), + Config, + ?config(mqtt_user, Config), + Opts + ), {ok, _} = emqtt:connect(C), C. test_subscribe_permissions_combination(PermConf, PermWrite, PermRead, Config, ExtraLogChecks) -> - rabbit_ct_broker_helpers:set_permissions(Config, - ?config(mqtt_user, Config), - ?config(mqtt_vhost, Config), - PermConf, PermWrite, PermRead), + rabbit_ct_broker_helpers:set_permissions( + Config, + ?config(mqtt_user, Config), + ?config(mqtt_vhost, Config), + PermConf, + PermWrite, + PermRead + ), P = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt), User = ?config(mqtt_user, Config), - Opts = [{host, "localhost"}, - {port, P}, - {clientid, User}, - {username, User}, - {password, ?config(mqtt_password, Config)}], + Opts = [ + {host, "localhost"}, + {port, P}, + {clientid, User}, + {username, User}, + {password, ?config(mqtt_password, Config)} + ], {ok, C1} = emqtt:start_link([{proto_ver, v4} | Opts]), {ok, _} = emqtt:connect(C1), process_flag(trap_exit, true), %% In v4, we expect to receive a failure return code for our subscription in the SUBACK packet. - ?assertMatch({ok, _Properties, [?SUBACK_FAILURE]}, - emqtt:subscribe(C1, <<"test/topic">>)), + ?assertMatch( + {ok, _Properties, [?SUBACK_FAILURE]}, + emqtt:subscribe(C1, <<"test/topic">>) + ), ok = assert_connection_closed(C1), - wait_log(Config, - [?FAIL_IF_CRASH_LOG - ,{["MQTT protocol error on connection.*: subscribe_error"|ExtraLogChecks], fun () -> stop end} - ]), + wait_log( + Config, + [ + ?FAIL_IF_CRASH_LOG, + {["MQTT protocol error on connection.*: subscribe_error" | ExtraLogChecks], fun() -> + stop + end} + ] + ), {ok, C2} = emqtt:start_link([{proto_ver, v3} | Opts]), {ok, _} = emqtt:connect(C2), %% In v3, there is no failure return code in the SUBACK packet. - ?assertMatch({ok, _Properties, [0]}, - emqtt:subscribe(C2, <<"test/topic">>)), + ?assertMatch( + {ok, _Properties, [0]}, + emqtt:subscribe(C2, <<"test/topic">>) + ), ok = assert_connection_closed(C2). connect_user(User, Pass, Config) -> @@ -820,20 +1021,25 @@ connect_user(User, Pass, Config) -> connect_user(User, Pass, Config, ClientID) -> connect_user(User, Pass, Config, ClientID, []). connect_user(User, Pass, Config, ClientID0, Opts) -> - Creds = case User of - undefined -> []; - _ -> [{username, User}] - end ++ case Pass of - undefined -> []; - _ -> [{password, Pass}] - end, - ClientID = case ClientID0 of - undefined -> []; - ID -> [{clientid, ID}] - end, + Creds = + case User of + undefined -> []; + _ -> [{username, User}] + end ++ + case Pass of + undefined -> []; + _ -> [{password, Pass}] + end, + ClientID = + case ClientID0 of + undefined -> []; + ID -> [{clientid, ID}] + end, P = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt), - emqtt:start_link(Opts ++ Creds ++ ClientID ++ - [{host, "localhost"}, {port, P}, {proto_ver, v4}]). + emqtt:start_link( + Opts ++ Creds ++ ClientID ++ + [{host, "localhost"}, {port, P}, {proto_ver, v4}] + ). expect_successful_connection(ConnectFun, Config) -> rpc(Config, 0, rabbit_core_metrics, reset_auth_attempt_metrics, []), @@ -881,10 +1087,14 @@ vhost_queue_limit(Config) -> process_flag(trap_exit, true), %% qos0 queue can be created, qos1 queue fails to be created. %% (RabbitMQ creates subscriptions in the reverse order of the SUBSCRIBE packet.) - ?assertMatch({ok, _Properties, [?SUBACK_FAILURE, ?SUBACK_FAILURE, 0]}, - emqtt:subscribe(C, [{<<"topic1">>, qos1}, - {<<"topic2">>, qos1}, - {<<"topic3">>, qos0}])), + ?assertMatch( + {ok, _Properties, [?SUBACK_FAILURE, ?SUBACK_FAILURE, 0]}, + emqtt:subscribe(C, [ + {<<"topic1">>, qos1}, + {<<"topic2">>, qos1}, + {<<"topic3">>, qos0} + ]) + ), ok = assert_connection_closed(C). user_connection_limit(Config) -> @@ -906,42 +1116,47 @@ wait_log(Config, Clauses, Deadline) -> case erlang:monotonic_time(millisecond) of T when T =< Deadline -> case wait_log_check_clauses(Content, Clauses) of - stop -> ok; + stop -> + ok; continue -> timer:sleep(50), wait_log(Config, Clauses, Deadline) end; _ -> lists:foreach( - fun - ({REs, _}) -> - Matches = [ io_lib:format("~p - ~s~n", [RE, re:run(Content, RE, [{capture, none}])]) || RE <- REs ], - ct:pal("Wait log clause status: ~s", [Matches]) - end, Clauses), + fun({REs, _}) -> + Matches = [ + io_lib:format("~p - ~s~n", [RE, re:run(Content, RE, [{capture, none}])]) + || RE <- REs + ], + ct:pal("Wait log clause status: ~s", [Matches]) + end, + Clauses + ), ct:fail(expected_logs_not_found) end, ok. wait_log_check_clauses(_, []) -> continue; -wait_log_check_clauses(Content, [{REs, Fun}|Rest]) -> +wait_log_check_clauses(Content, [{REs, Fun} | Rest]) -> case multiple_re_match(Content, REs) of true -> Fun(); - _ -> - wait_log_check_clauses(Content, Rest) + _ -> wait_log_check_clauses(Content, Rest) end. multiple_re_match(Content, REs) -> lists:all( - fun (RE) -> - match == re:run(Content, RE, [{capture, none}]) - end, REs). + fun(RE) -> + match == re:run(Content, RE, [{capture, none}]) + end, + REs + ). assert_connection_closed(ClientPid) -> receive {'EXIT', ClientPid, {shutdown, tcp_closed}} -> ok - after - 2000 -> - ct:fail("timed out waiting for exit message") + after 2000 -> + ct:fail("timed out waiting for exit message") end. diff --git a/deps/rabbitmq_mqtt/test/cluster_SUITE.erl b/deps/rabbitmq_mqtt/test/cluster_SUITE.erl index 0ac2434ea2..15f4e92ddc 100644 --- a/deps/rabbitmq_mqtt/test/cluster_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/cluster_SUITE.erl @@ -9,36 +9,42 @@ -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(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}]). +-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 + ] +). + +-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() -> @@ -50,11 +56,12 @@ 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(), @@ -65,7 +72,8 @@ 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. @@ -75,19 +83,25 @@ 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). %% ------------------------------------------------------------------- @@ -165,14 +179,15 @@ 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. diff --git a/deps/rabbitmq_mqtt/test/command_SUITE.erl b/deps/rabbitmq_mqtt/test/command_SUITE.erl index 76421287d3..1f3831f94e 100644 --- a/deps/rabbitmq_mqtt/test/command_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/command_SUITE.erl @@ -4,7 +4,6 @@ %% %% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved. - -module(command_SUITE). -compile([export_all, nowarn_export_all]). @@ -17,39 +16,45 @@ 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. @@ -73,7 +78,6 @@ 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}, @@ -91,18 +95,27 @@ 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), @@ -110,14 +123,19 @@ 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), diff --git a/deps/rabbitmq_mqtt/test/config_SUITE.erl b/deps/rabbitmq_mqtt/test/config_SUITE.erl index b2b04b52e2..37cb23f43a 100644 --- a/deps/rabbitmq_mqtt/test/config_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/config_SUITE.erl @@ -5,8 +5,10 @@ %% 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"). @@ -14,17 +16,16 @@ all() -> [ - {group, mnesia} + {group, mnesia} ]. groups() -> [ - {mnesia, [shuffle], - [ - rabbitmq_default, - environment_set, - flag_set - ]} + {mnesia, [shuffle], [ + rabbitmq_default, + environment_set, + flag_set + ]} ]. suite() -> @@ -45,8 +46,12 @@ 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], @@ -55,17 +60,19 @@ 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 @@ -74,21 +81,33 @@ 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]) + ). diff --git a/deps/rabbitmq_mqtt/test/config_schema_SUITE.erl b/deps/rabbitmq_mqtt/test/config_schema_SUITE.erl index c3f3d867c4..b62ca26185 100644 --- a/deps/rabbitmq_mqtt/test/config_schema_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/config_schema_SUITE.erl @@ -23,7 +23,6 @@ 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). @@ -31,15 +30,19 @@ 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). %% ------------------------------------------------------------------- @@ -47,9 +50,13 @@ 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). - diff --git a/deps/rabbitmq_mqtt/test/event_recorder.erl b/deps/rabbitmq_mqtt/test/event_recorder.erl index cd495f9427..4c2af35437 100644 --- a/deps/rabbitmq_mqtt/test/event_recorder.erl +++ b/deps/rabbitmq_mqtt/test/event_recorder.erl @@ -15,10 +15,11 @@ 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]}. diff --git a/deps/rabbitmq_mqtt/test/ff_SUITE.erl b/deps/rabbitmq_mqtt/test/ff_SUITE.erl index a7c528c640..5e002f808f 100644 --- a/deps/rabbitmq_mqtt/test/ff_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/ff_SUITE.erl @@ -13,27 +13,31 @@ -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) -> @@ -44,26 +48,36 @@ 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) -> @@ -76,16 +90,27 @@ 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]))), - - ?assertEqual(ok, - rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)), + ?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) + ), %% 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)), @@ -99,20 +124,30 @@ 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), @@ -120,13 +155,22 @@ 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). diff --git a/deps/rabbitmq_mqtt/test/java_SUITE.erl b/deps/rabbitmq_mqtt/test/java_SUITE.erl index 5155f1ba77..4e3fdeed29 100644 --- a/deps/rabbitmq_mqtt/test/java_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/java_SUITE.erl @@ -12,24 +12,25 @@ -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() -> @@ -52,16 +53,20 @@ 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. @@ -74,25 +79,38 @@ 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 %% ------------------------------------------------------------------- @@ -122,5 +140,5 @@ q(P, [K | Rem]) -> undefined -> undefined; V -> q(V, Rem) end; -q(P, []) -> {ok, P}. - +q(P, []) -> + {ok, P}. diff --git a/deps/rabbitmq_mqtt/test/mqtt_machine_SUITE.erl b/deps/rabbitmq_mqtt/test/mqtt_machine_SUITE.erl index 274877cdc8..ebf773aa44 100644 --- a/deps/rabbitmq_mqtt/test/mqtt_machine_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/mqtt_machine_SUITE.erl @@ -12,20 +12,19 @@ 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) -> @@ -53,13 +52,16 @@ 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), @@ -74,41 +76,63 @@ 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) + }. diff --git a/deps/rabbitmq_mqtt/test/processor_SUITE.erl b/deps/rabbitmq_mqtt/test/processor_SUITE.erl index 15ae0dd537..1078462ad8 100644 --- a/deps/rabbitmq_mqtt/test/processor_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/processor_SUITE.erl @@ -4,7 +4,6 @@ %% %% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved. - -module(processor_SUITE). -compile([export_all, nowarn_export_all]). @@ -14,17 +13,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() -> @@ -42,35 +41,50 @@ 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(), @@ -83,27 +97,35 @@ 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 @@ -121,7 +143,9 @@ 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 @@ -138,42 +162,50 @@ 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, [ @@ -185,15 +217,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). diff --git a/deps/rabbitmq_mqtt/test/proxy_protocol_SUITE.erl b/deps/rabbitmq_mqtt/test/proxy_protocol_SUITE.erl index 551b14c865..6499b9aaea 100644 --- a/deps/rabbitmq_mqtt/test/proxy_protocol_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/proxy_protocol_SUITE.erl @@ -34,21 +34,26 @@ 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. @@ -61,8 +66,11 @@ 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), @@ -75,8 +83,11 @@ 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()), @@ -96,29 +107,5 @@ 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>>. diff --git a/deps/rabbitmq_mqtt/test/rabbit_auth_backend_mqtt_mock.erl b/deps/rabbitmq_mqtt/test/rabbit_auth_backend_mqtt_mock.erl index 69fea6c221..ad1bfe1e58 100644 --- a/deps/rabbitmq_mqtt/test/rabbit_auth_backend_mqtt_mock.erl +++ b/deps/rabbitmq_mqtt/test/rabbit_auth_backend_mqtt_mock.erl @@ -13,11 +13,16 @@ -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]), @@ -26,12 +31,13 @@ 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"), diff --git a/deps/rabbitmq_mqtt/test/reader_SUITE.erl b/deps/rabbitmq_mqtt/test/reader_SUITE.erl index 4c65e20e47..5a96612231 100644 --- a/deps/rabbitmq_mqtt/test/reader_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/reader_SUITE.erl @@ -5,42 +5,45 @@ %% 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() -> @@ -51,28 +54,36 @@ 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. @@ -86,7 +97,6 @@ init_per_testcase(Testcase, Config) -> end_per_testcase(Testcase, Config) -> rabbit_ct_helpers:testcase_finished(Config, Testcase). - %% ------------------------------------------------------------------- %% Testsuite cases %% ------------------------------------------------------------------- @@ -100,11 +110,13 @@ 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), @@ -112,7 +124,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, []), @@ -130,7 +142,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)), @@ -170,8 +182,12 @@ 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) -> @@ -216,32 +232,41 @@ 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), @@ -252,9 +277,13 @@ 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, []). @@ -266,8 +295,12 @@ 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), @@ -279,9 +312,12 @@ 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), @@ -318,9 +354,11 @@ 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. diff --git a/deps/rabbitmq_mqtt/test/retainer_SUITE.erl b/deps/rabbitmq_mqtt/test/retainer_SUITE.erl index 3a2585e8fe..eb3f8dedd5 100644 --- a/deps/rabbitmq_mqtt/test/retainer_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/retainer_SUITE.erl @@ -8,29 +8,31 @@ -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() -> @@ -49,31 +51,38 @@ 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). @@ -81,7 +90,6 @@ init_per_testcase(Testcase, Config) -> end_per_testcase(Testcase, Config) -> rabbit_ct_helpers:testcase_finished(Config, Testcase). - %% ------------------------------------------------------------------- %% Testsuite cases %% ------------------------------------------------------------------- @@ -104,7 +112,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). @@ -116,7 +124,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). @@ -128,19 +136,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). diff --git a/deps/rabbitmq_mqtt/test/shared_SUITE.erl b/deps/rabbitmq_mqtt/test/shared_SUITE.erl index 71183d5f9c..32d46f0481 100644 --- a/deps/rabbitmq_mqtt/test/shared_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/shared_SUITE.erl @@ -6,8 +6,10 @@ %% Test suite shared between rabbitmq_mqtt and rabbitmq_web_mqtt. -module(shared_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"). @@ -15,100 +17,113 @@ -include_lib("rabbitmq_ct_helpers/include/rabbit_assert.hrl"). -include_lib("rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl"). --import(rabbit_ct_broker_helpers, - [rabbitmqctl_list/3, - rpc/4, - rpc/5, - rpc_all/4, - get_node_config/3, - drain_node/2, - revive_node/2 - ]). --import(rabbit_ct_helpers, - [eventually/3, - eventually/1]). --import(util, - [all_connection_pids/1, - 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, - publish_qos1_timeout/4]). --import(rabbit_mgmt_test_util, - [http_get/2, - http_delete/3]). +-import( + rabbit_ct_broker_helpers, + [ + rabbitmqctl_list/3, + rpc/4, + rpc/5, + rpc_all/4, + get_node_config/3, + drain_node/2, + revive_node/2 + ] +). +-import( + rabbit_ct_helpers, + [ + eventually/3, + eventually/1 + ] +). +-import( + util, + [ + all_connection_pids/1, + 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, + publish_qos1_timeout/4 + ] +). +-import( + rabbit_mgmt_test_util, + [ + http_get/2, + http_delete/3 + ] +). all() -> [ - {group, mqtt} - ,{group, web_mqtt} + {group, mqtt}, + {group, web_mqtt} ]. groups() -> [ - {mqtt, [], subgroups()} - ,{web_mqtt, [], subgroups()} + {mqtt, [], subgroups()}, + {web_mqtt, [], subgroups()} ]. subgroups() -> [ - {cluster_size_1, [], - [ - {global_counters, [], - [ - global_counters_v3, - global_counters_v4 + {cluster_size_1, [], [ + {global_counters, [], [ + global_counters_v3, + global_counters_v4 + ]}, + {tests, [], [ + block_only_publisher, + many_qos1_messages, + subscription_ttl, + management_plugin_connection, + management_plugin_enable, + disconnect, + pubsub_shared_connection, + pubsub_separate_connections, + will_with_disconnect, + will_without_disconnect, + quorum_queue_rejects, + events, + internal_event_handler, + non_clean_sess_reconnect_qos1, + non_clean_sess_reconnect_qos0, + non_clean_sess_reconnect_qos0_and_qos1, + subscribe_same_topic_same_qos, + subscribe_same_topic_different_qos, + subscribe_multiple, + large_message_mqtt_to_mqtt, + large_message_amqp_to_mqtt, + keepalive, + keepalive_turned_off, + duplicate_client_id, + block, + amqp_to_mqtt_qos0, + clean_session_disconnect_client, + clean_session_kill_node, + rabbit_status_connection_count, + trace + ]} ]}, - {tests, [], - [ - block_only_publisher - ,many_qos1_messages - ,subscription_ttl - ,management_plugin_connection - ,management_plugin_enable - ,disconnect - ,pubsub_shared_connection - ,pubsub_separate_connections - ,will_with_disconnect - ,will_without_disconnect - ,quorum_queue_rejects - ,events - ,internal_event_handler - ,non_clean_sess_reconnect_qos1 - ,non_clean_sess_reconnect_qos0 - ,non_clean_sess_reconnect_qos0_and_qos1 - ,subscribe_same_topic_same_qos - ,subscribe_same_topic_different_qos - ,subscribe_multiple - ,large_message_mqtt_to_mqtt - ,large_message_amqp_to_mqtt - ,keepalive - ,keepalive_turned_off - ,duplicate_client_id - ,block - ,amqp_to_mqtt_qos0 - ,clean_session_disconnect_client - ,clean_session_kill_node - ,rabbit_status_connection_count - ,trace + {cluster_size_3, [], [ + queue_down_qos1, + consuming_classic_mirrored_queue_down, + consuming_classic_queue_down, + flow_classic_mirrored_queue, + flow_quorum_queue, + flow_stream, + rabbit_mqtt_qos0_queue, + cli_list_queues, + maintenance, + delete_create_queue, + publish_to_all_queue_types_qos0, + publish_to_all_queue_types_qos1 ]} - ]}, - {cluster_size_3, [], - [ - queue_down_qos1, - consuming_classic_mirrored_queue_down, - consuming_classic_queue_down, - flow_classic_mirrored_queue, - flow_quorum_queue, - flow_stream, - rabbit_mqtt_qos0_queue, - cli_list_queues, - maintenance, - delete_create_queue, - publish_to_all_queue_types_qos0, - publish_to_all_queue_types_qos1 - ]} ]. suite() -> @@ -129,54 +144,67 @@ init_per_group(mqtt, Config) -> rabbit_ct_helpers:set_config(Config, {websocket, false}); init_per_group(web_mqtt, Config) -> rabbit_ct_helpers:set_config(Config, {websocket, true}); - init_per_group(cluster_size_1, Config) -> rabbit_ct_helpers:set_config(Config, [{rmq_nodes_count, 1}]); init_per_group(cluster_size_3 = Group, Config) -> - init_per_group0(Group, - rabbit_ct_helpers:set_config(Config, [{rmq_nodes_count, 3}])); -init_per_group(Group, Config) - when Group =:= global_counters orelse - Group =:= tests -> + init_per_group0( + Group, + rabbit_ct_helpers:set_config(Config, [{rmq_nodes_count, 3}]) + ); +init_per_group(Group, Config) when + Group =:= global_counters orelse + Group =:= tests +-> init_per_group0(Group, Config). init_per_group0(Group, Config0) -> Suffix = lists:flatten(io_lib:format("~s_websocket_~w", [Group, ?config(websocket, Config0)])), Config1 = rabbit_ct_helpers:set_config( - Config0, - [{rmq_nodename_suffix, Suffix}, - {rmq_extra_tcp_ports, [tcp_port_mqtt_extra, - tcp_port_mqtt_tls_extra]}]), + Config0, + [ + {rmq_nodename_suffix, Suffix}, + {rmq_extra_tcp_ports, [ + tcp_port_mqtt_extra, + tcp_port_mqtt_tls_extra + ]} + ] + ), Config = rabbit_ct_helpers:merge_app_env( - Config1, - {rabbit, [{classic_queue_default_version, 2}]}), + Config1, + {rabbit, [{classic_queue_default_version, 2}]} + ), rabbit_ct_helpers:run_steps( - Config, - rabbit_ct_broker_helpers:setup_steps() ++ - rabbit_ct_client_helpers:setup_steps()). - -end_per_group(G, Config) - when G =:= mqtt; - G =:= web_mqtt; - G =:= cluster_size_1 -> + Config, + rabbit_ct_broker_helpers:setup_steps() ++ + rabbit_ct_client_helpers:setup_steps() + ). + +end_per_group(G, Config) when + G =:= mqtt; + G =:= web_mqtt; + G =:= cluster_size_1 +-> Config; 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 = maintenance, Config) -> case rabbit_ct_helpers:is_mixed_versions() of true -> - {skip, "maintenance mode wrongly closes cluster-wide MQTT connections " - "in RMQ < 3.11.2 and < 3.10.10"}; + {skip, + "maintenance mode wrongly closes cluster-wide MQTT connections " + "in RMQ < 3.11.2 and < 3.10.10"}; false -> init_per_testcase0(Testcase, Config) end; -init_per_testcase(T, Config) - when T =:= management_plugin_connection; - T =:= management_plugin_enable -> +init_per_testcase(T, Config) when + T =:= management_plugin_connection; + T =:= management_plugin_enable +-> ok = inets:start(), init_per_testcase0(T, Config); init_per_testcase(Testcase, Config) -> @@ -187,9 +215,10 @@ init_per_testcase0(Testcase, Config) -> [ok = rabbit_ct_broker_helpers:enable_plugin(Config, N, rabbitmq_web_mqtt) || N <- Nodes], rabbit_ct_helpers:testcase_started(Config, Testcase). -end_per_testcase(T, Config) - when T =:= management_plugin_connection; - T =:= management_plugin_enable -> +end_per_testcase(T, Config) when + T =:= management_plugin_connection; + T =:= management_plugin_enable +-> ok = inets:stop(), end_per_testcase0(T, Config); end_per_testcase(Testcase, Config) -> @@ -219,11 +248,14 @@ pubsub_shared_connection(Config) -> {ok, _, [1]} = emqtt:subscribe(C, Topic, qos1), Payload = <<"a\x00a">>, - ?assertMatch({ok, #{packet_id := _, - reason_code := 0, - reason_code_name := success - }}, - emqtt:publish(C, Topic, Payload, [{qos, 1}])), + ?assertMatch( + {ok, #{ + packet_id := _, + reason_code := 0, + reason_code_name := success + }}, + emqtt:publish(C, Topic, Payload, [{qos, 1}]) + ), ok = expect_publishes(C, Topic, [Payload]), ok = emqtt:disconnect(C). @@ -235,11 +267,14 @@ pubsub_separate_connections(Config) -> {ok, _, [1]} = emqtt:subscribe(Sub, Topic, qos1), Payload = <<"a\x00a">>, - ?assertMatch({ok, #{packet_id := _, - reason_code := 0, - reason_code_name := success - }}, - emqtt:publish(Pub, Topic, Payload, [{qos, 1}])), + ?assertMatch( + {ok, #{ + packet_id := _, + reason_code := 0, + reason_code_name := success + }}, + emqtt:publish(Pub, Topic, Payload, [{qos, 1}]) + ), ok = expect_publishes(Sub, Topic, [Payload]), ok = emqtt:disconnect(Pub), ok = emqtt:disconnect(Sub). @@ -247,26 +282,32 @@ pubsub_separate_connections(Config) -> will_with_disconnect(Config) -> LastWillTopic = <<"/topic/last-will">>, LastWillMsg = <<"last will message">>, - PubOpts = [{will_topic, LastWillTopic}, - {will_payload, LastWillMsg}, - {will_qos, 1}], + PubOpts = [ + {will_topic, LastWillTopic}, + {will_payload, LastWillMsg}, + {will_qos, 1} + ], Pub = connect(<<(atom_to_binary(?FUNCTION_NAME))/binary, "_publisher">>, Config, PubOpts), Sub = connect(<<(atom_to_binary(?FUNCTION_NAME))/binary, "_subscriber">>, Config), {ok, _, [1]} = emqtt:subscribe(Sub, LastWillTopic, qos1), %% Client sends DISCONNECT packet. Therefore, will message should not be sent. ok = emqtt:disconnect(Pub), - ?assertEqual({publish_not_received, LastWillMsg}, - expect_publishes(Sub, LastWillTopic, [LastWillMsg])), + ?assertEqual( + {publish_not_received, LastWillMsg}, + expect_publishes(Sub, LastWillTopic, [LastWillMsg]) + ), ok = emqtt:disconnect(Sub). will_without_disconnect(Config) -> LastWillTopic = <<"/topic/last-will">>, LastWillMsg = <<"last will message">>, - PubOpts = [{will_topic, LastWillTopic}, - {will_payload, LastWillMsg}, - {will_qos, 1}], + PubOpts = [ + {will_topic, LastWillTopic}, + {will_payload, LastWillMsg}, + {will_qos, 1} + ], Pub = connect(<<(atom_to_binary(?FUNCTION_NAME))/binary, "_publisher">>, Config, PubOpts), timer:sleep(100), [ServerPublisherPid] = all_connection_pids(Config), @@ -285,8 +326,11 @@ quorum_queue_rejects(Config) -> Name = atom_to_binary(?FUNCTION_NAME), ok = rabbit_ct_broker_helpers:set_policy( - Config, 0, <<"qq-policy">>, Name, <<"queues">>, [{<<"max-length">>, 1}, - {<<"overflow">>, <<"reject-publish">>}]), + Config, 0, <<"qq-policy">>, Name, <<"queues">>, [ + {<<"max-length">>, 1}, + {<<"overflow">>, <<"reject-publish">>} + ] + ), declare_queue(Ch, Name, [{<<"x-queue-type">>, longstr, <<"quorum">>}]), bind(Ch, Name, Name), @@ -296,14 +340,21 @@ quorum_queue_rejects(Config) -> %% We expect m3 to be rejected and dropped. ?assertEqual(puback_timeout, util:publish_qos1_timeout(C, Name, <<"m3">>, 700)), - ?assertMatch({#'basic.get_ok'{}, #amqp_msg{payload = <<"m1">>}}, - amqp_channel:call(Ch, #'basic.get'{queue = Name, no_ack = true})), - ?assertMatch({#'basic.get_ok'{}, #amqp_msg{payload = <<"m2">>}}, - amqp_channel:call(Ch, #'basic.get'{queue = Name, no_ack = true})), + ?assertMatch( + {#'basic.get_ok'{}, #amqp_msg{payload = <<"m1">>}}, + amqp_channel:call(Ch, #'basic.get'{queue = Name, no_ack = true}) + ), + ?assertMatch( + {#'basic.get_ok'{}, #amqp_msg{payload = <<"m2">>}}, + amqp_channel:call(Ch, #'basic.get'{queue = Name, no_ack = true}) + ), %% m3 is re-sent by emqtt. - ?awaitMatch({#'basic.get_ok'{}, #amqp_msg{payload = <<"m3">>}}, - amqp_channel:call(Ch, #'basic.get'{queue = Name, no_ack = true}), - 2000, 200), + ?awaitMatch( + {#'basic.get_ok'{}, #amqp_msg{payload = <<"m3">>}}, + amqp_channel:call(Ch, #'basic.get'{queue = Name, no_ack = true}), + 2000, + 200 + ), ok = emqtt:disconnect(C), delete_queue(Ch, Name), @@ -339,40 +390,55 @@ publish_to_all_queue_types(Config, QoS) -> NumMsgs = 2000, C = connect(?FUNCTION_NAME, Config, [{retry_interval, 2}]), - lists:foreach(fun(N) -> - case emqtt:publish(C, Topic, integer_to_binary(N), QoS) of - ok -> - ok; - {ok, _} -> - ok; - Other -> - ct:fail("Failed to publish: ~p", [Other]) - end - end, lists:seq(1, NumMsgs)), - - eventually(?_assert( - begin - L = rabbitmqctl_list(Config, 0, ["list_queues", "messages", "--no-table-headers"]), - length(L) =:= 4 andalso - lists:all(fun([Bin]) -> - N = binary_to_integer(Bin), - case QoS of - qos0 -> - N =:= NumMsgs; - qos1 -> - %% Allow for some duplicates when client resends - %% a message that gets acked at roughly the same time. - N >= NumMsgs andalso - N < NumMsgs * 2 - end - end, L) - end), 2000, 10), + lists:foreach( + fun(N) -> + case emqtt:publish(C, Topic, integer_to_binary(N), QoS) of + ok -> + ok; + {ok, _} -> + ok; + Other -> + ct:fail("Failed to publish: ~p", [Other]) + end + end, + lists:seq(1, NumMsgs) + ), + + eventually( + ?_assert( + begin + L = rabbitmqctl_list(Config, 0, ["list_queues", "messages", "--no-table-headers"]), + length(L) =:= 4 andalso + lists:all( + fun([Bin]) -> + N = binary_to_integer(Bin), + case QoS of + qos0 -> + N =:= NumMsgs; + qos1 -> + %% Allow for some duplicates when client resends + %% a message that gets acked at roughly the same time. + N >= NumMsgs andalso + N < NumMsgs * 2 + end + end, + L + ) + end + ), + 2000, + 10 + ), delete_queue(Ch, [CQ, CMQ, QQ, SQ]), ok = rabbit_ct_broker_helpers:clear_policy(Config, 0, CMQ), ok = emqtt:disconnect(C), - ?awaitMatch([], - all_connection_pids(Config), 10_000, 1000). + ?awaitMatch( + [], + all_connection_pids(Config), + 10_000, + 1000 + ). flow_classic_mirrored_queue(Config) -> QueueName = <<"flow">>, @@ -386,8 +452,9 @@ flow_quorum_queue(Config) -> flow_stream(Config) -> flow(Config, {rabbit, stream_messages_soft_limit, 1}, <<"stream">>). -flow(Config, {App, Par, Val}, QueueType) - when is_binary(QueueType) -> +flow(Config, {App, Par, Val}, QueueType) when + is_binary(QueueType) +-> {ok, DefaultVal} = rpc(Config, application, get_env, [App, Par]), Result = rpc_all(Config, application, set_env, [App, Par, Val]), ?assert(lists:all(fun(R) -> R =:= ok end, Result)), @@ -398,27 +465,47 @@ flow(Config, {App, Par, Val}, QueueType) bind(Ch, QueueName, Topic), NumMsgs = 1000, - C = connect(?FUNCTION_NAME, Config, [{retry_interval, 600}, - {max_inflight, NumMsgs}]), + C = connect(?FUNCTION_NAME, Config, [ + {retry_interval, 600}, + {max_inflight, NumMsgs} + ]), TestPid = self(), lists:foreach( - fun(N) -> - %% Publish async all messages at once to trigger flow control - ok = emqtt:publish_async(C, Topic, integer_to_binary(N), qos1, - {fun(N0, {ok, #{reason_code_name := success}}) -> - TestPid ! {self(), N0} - end, [N]}) - end, lists:seq(1, NumMsgs)), + fun(N) -> + %% Publish async all messages at once to trigger flow control + ok = emqtt:publish_async( + C, + Topic, + integer_to_binary(N), + qos1, + { + fun(N0, {ok, #{reason_code_name := success}}) -> + TestPid ! {self(), N0} + end, + [N] + } + ) + end, + lists:seq(1, NumMsgs) + ), ok = await_confirms_ordered(C, 1, NumMsgs), - eventually(?_assertEqual( - [[integer_to_binary(NumMsgs)]], - rabbitmqctl_list(Config, 0, ["list_queues", "messages", "--no-table-headers"]) - ), 1000, 10), + eventually( + ?_assertEqual( + [[integer_to_binary(NumMsgs)]], + rabbitmqctl_list(Config, 0, ["list_queues", "messages", "--no-table-headers"]) + ), + 1000, + 10 + ), delete_queue(Ch, QueueName), ok = emqtt:disconnect(C), - ?awaitMatch([], - all_connection_pids(Config), 10_000, 1000), + ?awaitMatch( + [], + all_connection_pids(Config), + 10_000, + 1000 + ), Result = rpc_all(Config, application, set_env, [App, Par, DefaultVal]), ok. @@ -432,20 +519,27 @@ events(Config) -> [E0, E1] = get_events(Server), assert_event_type(user_authentication_success, E0), - assert_event_prop([{name, <<"guest">>}, - {connection_type, network}], - E0), + assert_event_prop( + [ + {name, <<"guest">>}, + {connection_type, network} + ], + E0 + ), assert_event_type(connection_created, E1), [ConnectionPid] = all_connection_pids(Config), - Proto = case ?config(websocket, Config) of - true -> 'Web MQTT'; - false -> 'MQTT' - end, - ExpectedConnectionProps = [{protocol, {Proto, {3,1,1}}}, - {node, Server}, - {vhost, <<"/">>}, - {user, <<"guest">>}, - {pid, ConnectionPid}], + Proto = + case ?config(websocket, Config) of + true -> 'Web MQTT'; + false -> 'MQTT' + end, + ExpectedConnectionProps = [ + {protocol, {Proto, {3, 1, 1}}}, + {node, Server}, + {vhost, <<"/">>}, + {user, <<"guest">>}, + {pid, ConnectionPid} + ], assert_event_prop(ExpectedConnectionProps, E1), {ok, _, _} = emqtt:subscribe(C, <<"TopicA">>, qos0), @@ -453,37 +547,50 @@ events(Config) -> QueueNameBin = <<"mqtt-subscription-", ClientId/binary, "qos0">>, QueueName = {resource, <<"/">>, queue, QueueNameBin}, [E2, E3 | E4] = get_events(Server), - QueueType = case rabbit_ct_helpers:is_mixed_versions(Config) of - false -> - ?assertEqual([], E4), - rabbit_mqtt_qos0_queue; - true -> - %% Feature flag rabbit_mqtt_qos0_queue is disabled. - [ConsumerCreated] = E4, - assert_event_type(consumer_created, ConsumerCreated), - assert_event_prop([{queue, QueueName}, - {ack_required, false}, - {exclusive, false}, - {arguments, []}], - ConsumerCreated), - classic - end, + QueueType = + case rabbit_ct_helpers:is_mixed_versions(Config) of + false -> + ?assertEqual([], E4), + rabbit_mqtt_qos0_queue; + true -> + %% Feature flag rabbit_mqtt_qos0_queue is disabled. + [ConsumerCreated] = E4, + assert_event_type(consumer_created, ConsumerCreated), + assert_event_prop( + [ + {queue, QueueName}, + {ack_required, false}, + {exclusive, false}, + {arguments, []} + ], + ConsumerCreated + ), + classic + end, assert_event_type(queue_created, E2), - assert_event_prop([{name, QueueName}, - {durable, true}, - {auto_delete, false}, - {exclusive, true}, - {type, QueueType}, - {arguments, []}], - E2), + assert_event_prop( + [ + {name, QueueName}, + {durable, true}, + {auto_delete, false}, + {exclusive, true}, + {type, QueueType}, + {arguments, []} + ], + E2 + ), assert_event_type(binding_created, E3), - assert_event_prop([{source_name, <<"amq.topic">>}, - {source_kind, exchange}, - {destination_name, QueueNameBin}, - {destination_kind, queue}, - {routing_key, <<"TopicA">>}, - {arguments, []}], - E3), + assert_event_prop( + [ + {source_name, <<"amq.topic">>}, + {source_kind, exchange}, + {destination_name, QueueNameBin}, + {destination_kind, queue}, + {routing_key, <<"TopicA">>}, + {arguments, []} + ], + E3 + ), {ok, _, _} = emqtt:unsubscribe(C, <<"TopicA">>), @@ -511,7 +618,9 @@ events(Config) -> internal_event_handler(Config) -> Server = get_node_config(Config, 0, nodename), - ok = gen_event:call({rabbit_event, Server}, rabbit_mqtt_internal_event_handler, ignored_request, 1000). + ok = gen_event:call( + {rabbit_event, Server}, rabbit_mqtt_internal_event_handler, ignored_request, 1000 + ). global_counters_v3(Config) -> global_counters(Config, v3). @@ -539,62 +648,82 @@ global_counters(Config, ProtoVer) -> ok = expect_publishes(C, Topic1, [<<"testm1">>]), ok = expect_publishes(C, Topic2, [<<"testm2">>]), - ?assertEqual(#{publishers => 1, - consumers => 1, - messages_confirmed_total => 2, - messages_received_confirm_total => 2, - messages_received_total => 5, - messages_routed_total => 3, - messages_unroutable_dropped_total => 1, - messages_unroutable_returned_total => 1}, - get_global_counters(Config, ProtoVer)), + ?assertEqual( + #{ + publishers => 1, + consumers => 1, + messages_confirmed_total => 2, + messages_received_confirm_total => 2, + messages_received_total => 5, + messages_routed_total => 3, + messages_unroutable_dropped_total => 1, + messages_unroutable_returned_total => 1 + }, + get_global_counters(Config, ProtoVer) + ), case rabbit_ct_helpers:is_mixed_versions(Config) of false -> - ?assertEqual(#{messages_delivered_total => 2, - messages_acknowledged_total => 1, - messages_delivered_consume_auto_ack_total => 1, - messages_delivered_consume_manual_ack_total => 1, - messages_delivered_get_auto_ack_total => 0, - messages_delivered_get_manual_ack_total => 0, - messages_get_empty_total => 0, - messages_redelivered_total => 0}, - get_global_counters(Config, ProtoVer, 0, [{queue_type, rabbit_classic_queue}])), - ?assertEqual(#{messages_delivered_total => 1, - messages_acknowledged_total => 0, - messages_delivered_consume_auto_ack_total => 1, - messages_delivered_consume_manual_ack_total => 0, - messages_delivered_get_auto_ack_total => 0, - messages_delivered_get_manual_ack_total => 0, - messages_get_empty_total => 0, - messages_redelivered_total => 0}, - get_global_counters(Config, ProtoVer, 0, [{queue_type, rabbit_mqtt_qos0_queue}])); + ?assertEqual( + #{ + messages_delivered_total => 2, + messages_acknowledged_total => 1, + messages_delivered_consume_auto_ack_total => 1, + messages_delivered_consume_manual_ack_total => 1, + messages_delivered_get_auto_ack_total => 0, + messages_delivered_get_manual_ack_total => 0, + messages_get_empty_total => 0, + messages_redelivered_total => 0 + }, + get_global_counters(Config, ProtoVer, 0, [{queue_type, rabbit_classic_queue}]) + ), + ?assertEqual( + #{ + messages_delivered_total => 1, + messages_acknowledged_total => 0, + messages_delivered_consume_auto_ack_total => 1, + messages_delivered_consume_manual_ack_total => 0, + messages_delivered_get_auto_ack_total => 0, + messages_delivered_get_manual_ack_total => 0, + messages_get_empty_total => 0, + messages_redelivered_total => 0 + }, + get_global_counters(Config, ProtoVer, 0, [{queue_type, rabbit_mqtt_qos0_queue}]) + ); true -> %% Feature flag rabbit_mqtt_qos0_queue is disabled. - ?assertEqual(#{messages_delivered_total => 3, - messages_acknowledged_total => 1, - messages_delivered_consume_auto_ack_total => 2, - messages_delivered_consume_manual_ack_total => 1, - messages_delivered_get_auto_ack_total => 0, - messages_delivered_get_manual_ack_total => 0, - messages_get_empty_total => 0, - messages_redelivered_total => 0}, - get_global_counters(Config, ProtoVer, 0, [{queue_type, rabbit_classic_queue}])) + ?assertEqual( + #{ + messages_delivered_total => 3, + messages_acknowledged_total => 1, + messages_delivered_consume_auto_ack_total => 2, + messages_delivered_consume_manual_ack_total => 1, + messages_delivered_get_auto_ack_total => 0, + messages_delivered_get_manual_ack_total => 0, + messages_get_empty_total => 0, + messages_redelivered_total => 0 + }, + get_global_counters(Config, ProtoVer, 0, [{queue_type, rabbit_classic_queue}]) + ) end, {ok, _, _} = emqtt:unsubscribe(C, Topic1), ?assertEqual(1, maps:get(consumers, get_global_counters(Config, ProtoVer))), ok = emqtt:disconnect(C), - ?assertEqual(#{publishers => 0, - consumers => 0, - messages_confirmed_total => 2, - messages_received_confirm_total => 2, - messages_received_total => 5, - messages_routed_total => 3, - messages_unroutable_dropped_total => 1, - messages_unroutable_returned_total => 1}, - get_global_counters(Config, ProtoVer)). + ?assertEqual( + #{ + publishers => 0, + consumers => 0, + messages_confirmed_total => 2, + messages_received_confirm_total => 2, + messages_received_total => 5, + messages_routed_total => 3, + messages_unroutable_dropped_total => 1, + messages_unroutable_returned_total => 1 + }, + get_global_counters(Config, ProtoVer) + ). queue_down_qos1(Config) -> {Conn1, Ch1} = rabbit_ct_client_helpers:open_connection_and_channel(Config, 1), @@ -611,9 +740,14 @@ queue_down_qos1(Config) -> ok = rabbit_ct_broker_helpers:start_node(Config, 1), %% classic queue is up, therefore message should arrive - eventually(?_assertEqual([[<<"1">>]], - rabbitmqctl_list(Config, 1, ["list_queues", "messages", "--no-table-headers"])), - 500, 20), + eventually( + ?_assertEqual( + [[<<"1">>]], + rabbitmqctl_list(Config, 1, ["list_queues", "messages", "--no-table-headers"]) + ), + 500, + 20 + ), Ch0 = rabbit_ct_client_helpers:open_channel(Config, 0), delete_queue(Ch0, CQ), @@ -627,9 +761,16 @@ consuming_classic_mirrored_queue_down(Config) -> ClientId = Topic = PolicyName = atom_to_binary(?FUNCTION_NAME), ok = rabbit_ct_broker_helpers:set_policy( - Config, Server1, PolicyName, <<".*">>, <<"queues">>, - [{<<"ha-mode">>, <<"all">>}, - {<<"queue-master-locator">>, <<"client-local">>}]), + Config, + Server1, + PolicyName, + <<".*">>, + <<"queues">>, + [ + {<<"ha-mode">>, <<"all">>}, + {<<"queue-master-locator">>, <<"client-local">>} + ] + ), %% Declare queue leader on Server1. C1 = connect(ClientId, Config, Server1, [{clean_start, false}]), @@ -653,12 +794,16 @@ consuming_classic_mirrored_queue_down(Config) -> %% Cleanup ok = emqtt:disconnect(C2), ok = rabbit_ct_broker_helpers:start_node(Config, Server1), - ?assertMatch([_Q], - rpc(Config, Server1, rabbit_amqqueue, list, [])), + ?assertMatch( + [_Q], + rpc(Config, Server1, rabbit_amqqueue, list, []) + ), C3 = connect(ClientId, Config, Server2, [{clean_start, true}]), ok = emqtt:disconnect(C3), - ?assertEqual([], - rpc(Config, Server1, rabbit_amqqueue, list, [])), + ?assertEqual( + [], + rpc(Config, Server1, rabbit_amqqueue, list, []) + ), ok = rabbit_ct_broker_helpers:clear_policy(Config, Server1, PolicyName). %% Consuming classic queue on a different node goes down. @@ -675,8 +820,10 @@ consuming_classic_queue_down(Config) -> C2 = connect(ClientId, Config, Server3, [{clean_start, false}]), ProtoVer = v4, - ?assertMatch(#{consumers := 1}, - get_global_counters(Config, ProtoVer, Server3)), + ?assertMatch( + #{consumers := 1}, + get_global_counters(Config, ProtoVer, Server3) + ), %% Let's stop the queue leader node. process_flag(trap_exit, true), @@ -684,17 +831,24 @@ consuming_classic_queue_down(Config) -> %% When the dedicated MQTT connection (non-mirrored classic) queue goes down, it is reasonable %% that the server closes the MQTT connection because the MQTT client cannot consume anymore. - eventually(?_assertMatch(#{consumers := 0}, - get_global_counters(Config, ProtoVer, Server3)), - 1000, 5), + eventually( + ?_assertMatch( + #{consumers := 0}, + get_global_counters(Config, ProtoVer, Server3) + ), + 1000, + 5 + ), await_exit(C2), %% Cleanup ok = rabbit_ct_broker_helpers:start_node(Config, Server1), C3 = connect(ClientId, Config, Server3, [{clean_start, true}]), ok = emqtt:disconnect(C3), - ?assertEqual([], - rpc(Config, Server1, rabbit_amqqueue, list, [])), + ?assertEqual( + [], + rpc(Config, Server1, rabbit_amqqueue, list, []) + ), ok. delete_create_queue(Config) -> @@ -705,13 +859,13 @@ delete_create_queue(Config) -> Topic = atom_to_binary(?FUNCTION_NAME), DeclareQueues = fun() -> - declare_queue(Ch, CQ1, []), - bind(Ch, CQ1, Topic), - declare_queue(Ch, CQ2, []), - bind(Ch, CQ2, Topic), - declare_queue(Ch, QQ, [{<<"x-queue-type">>, longstr, <<"quorum">>}]), - bind(Ch, QQ, Topic) - end, + declare_queue(Ch, CQ1, []), + bind(Ch, CQ1, Topic), + declare_queue(Ch, CQ2, []), + bind(Ch, CQ2, Topic), + declare_queue(Ch, QQ, [{<<"x-queue-type">>, longstr, <<"quorum">>}]), + bind(Ch, QQ, Topic) + end, DeclareQueues(), %% some large retry_interval to avoid re-sending @@ -719,15 +873,26 @@ delete_create_queue(Config) -> NumMsgs = 50, TestPid = self(), spawn( - fun() -> - lists:foreach( + fun() -> + lists:foreach( fun(N) -> - ok = emqtt:publish_async(C, Topic, integer_to_binary(N), qos1, - {fun(N0, {ok, #{reason_code_name := success}}) -> - TestPid ! {self(), N0} - end, [N]}) - end, lists:seq(1, NumMsgs)) - end), + ok = emqtt:publish_async( + C, + Topic, + integer_to_binary(N), + qos1, + { + fun(N0, {ok, #{reason_code_name := success}}) -> + TestPid ! {self(), N0} + end, + [N] + } + ) + end, + lists:seq(1, NumMsgs) + ) + end + ), %% Delete queues while sending to them. %% We want to test the path where a queue is deleted while confirms are outstanding. @@ -748,12 +913,23 @@ delete_create_queue(Config) -> %% Sending a message to each of them should work. {ok, _} = emqtt:publish(C, Topic, <<"m">>, qos1), - eventually(?_assertEqual(lists:sort([[CQ1, <<"1">>], - %% This queue should have all messages because we did not delete it. - [CQ2, integer_to_binary(NumMsgs + 1)], - [QQ, <<"1">>]]), - lists:sort(rabbitmqctl_list(Config, 0, ["list_queues", "name", "messages", "--no-table-headers"]))), - 1000, 10), + eventually( + ?_assertEqual( + lists:sort([ + [CQ1, <<"1">>], + %% This queue should have all messages because we did not delete it. + [CQ2, integer_to_binary(NumMsgs + 1)], + [QQ, <<"1">>] + ]), + lists:sort( + rabbitmqctl_list(Config, 0, [ + "list_queues", "name", "messages", "--no-table-headers" + ]) + ) + ), + 1000, + 10 + ), delete_queue(Ch, [CQ1, CQ2, QQ]), ok = emqtt:disconnect(C). @@ -766,13 +942,15 @@ subscription_ttl(Config) -> ok = rpc(Config, application, set_env, [App, Par, TTL]), C = connect(ClientId, Config, [{clean_start, false}]), - {ok, _, [0, 1]} = emqtt:subscribe(C, [{<<"topic0">>, qos0}, - {<<"topic1">>, qos1}]), + {ok, _, [0, 1]} = emqtt:subscribe(C, [ + {<<"topic0">>, qos0}, + {<<"topic1">>, qos1} + ]), ok = emqtt:disconnect(C), ?assertEqual(2, rpc(Config, rabbit_amqqueue, count, [])), timer:sleep(TTL + 100), - ?assertEqual(0, rpc(Config, rabbit_amqqueue, count, [])), + ?assertEqual(0, rpc(Config, rabbit_amqqueue, count, [])), ok = rpc(Config, application, set_env, [App, Par, DefaultVal]). @@ -789,28 +967,36 @@ non_clean_sess_reconnect(Config, SubscriptionQoS) -> C1 = connect(ClientId, Config, [{clean_start, false}]), {ok, _, _} = emqtt:subscribe(C1, Topic, SubscriptionQoS), - ?assertMatch(#{consumers := 1}, - get_global_counters(Config, ProtoVer)), + ?assertMatch( + #{consumers := 1}, + get_global_counters(Config, ProtoVer) + ), ok = emqtt:disconnect(C1), - ?assertMatch(#{consumers := 0}, - get_global_counters(Config, ProtoVer)), + ?assertMatch( + #{consumers := 0}, + get_global_counters(Config, ProtoVer) + ), timer:sleep(20), ok = emqtt:publish(Pub, Topic, <<"msg-3-qos0">>, qos0), {ok, _} = emqtt:publish(Pub, Topic, <<"msg-4-qos1">>, qos1), C2 = connect(ClientId, Config, [{clean_start, false}]), - ?assertMatch(#{consumers := 1}, - get_global_counters(Config, ProtoVer)), + ?assertMatch( + #{consumers := 1}, + get_global_counters(Config, ProtoVer) + ), ok = emqtt:publish(Pub, Topic, <<"msg-5-qos0">>, qos0), {ok, _} = emqtt:publish(Pub, Topic, <<"msg-6-qos1">>, qos1), %% shouldn't receive message after unsubscribe {ok, _, _} = emqtt:unsubscribe(C2, Topic), - ?assertMatch(#{consumers := 0}, - get_global_counters(Config, ProtoVer)), + ?assertMatch( + #{consumers := 0}, + get_global_counters(Config, ProtoVer) + ), {ok, _} = emqtt:publish(Pub, Topic, <<"msg-7-qos0">>, qos1), %% "After the disconnection of a Session that had CleanSession set to 0, the Server MUST store @@ -818,8 +1004,12 @@ non_clean_sess_reconnect(Config, SubscriptionQoS) -> %% time of disconnection as part of the Session state [MQTT-3.1.2-5]. %% It MAY also store QoS 0 messages that meet the same criteria." %% Starting with RabbitMQ v3.12 we store QoS 0 messages as well. - ok = expect_publishes(C2, Topic, [<<"msg-3-qos0">>, <<"msg-4-qos1">>, - <<"msg-5-qos0">>, <<"msg-6-qos1">>]), + ok = expect_publishes(C2, Topic, [ + <<"msg-3-qos0">>, + <<"msg-4-qos1">>, + <<"msg-5-qos0">>, + <<"msg-6-qos1">> + ]), {publish_not_received, <<"msg-7-qos0">>} = expect_publishes(C2, Topic, [<<"msg-7-qos0">>]), ok = emqtt:disconnect(Pub), @@ -837,19 +1027,25 @@ non_clean_sess_reconnect_qos0_and_qos1(Config) -> C1 = connect(ClientId, Config, [{clean_start, false}]), {ok, _, [1, 0]} = emqtt:subscribe(C1, [{Topic1, qos1}, {Topic0, qos0}]), - ?assertMatch(#{consumers := 1}, - get_global_counters(Config, ProtoVer)), + ?assertMatch( + #{consumers := 1}, + get_global_counters(Config, ProtoVer) + ), ok = emqtt:disconnect(C1), - ?assertMatch(#{consumers := 0}, - get_global_counters(Config, ProtoVer)), + ?assertMatch( + #{consumers := 0}, + get_global_counters(Config, ProtoVer) + ), {ok, _} = emqtt:publish(Pub, Topic0, <<"msg-0">>, qos1), {ok, _} = emqtt:publish(Pub, Topic1, <<"msg-1">>, qos1), C2 = connect(ClientId, Config, [{clean_start, false}]), - ?assertMatch(#{consumers := 1}, - get_global_counters(Config, ProtoVer)), + ?assertMatch( + #{consumers := 1}, + get_global_counters(Config, ProtoVer) + ), ok = expect_publishes(C2, Topic0, [<<"msg-0">>]), ok = expect_publishes(C2, Topic1, [<<"msg-1">>]), @@ -863,8 +1059,10 @@ subscribe_same_topic_same_qos(Config) -> C = connect(?FUNCTION_NAME, Config), Topic = <<"a/b">>, - {ok, _} = emqtt:publish(C, Topic, <<"retained">>, [{retain, true}, - {qos, 1}]), + {ok, _} = emqtt:publish(C, Topic, <<"retained">>, [ + {retain, true}, + {qos, 1} + ]), %% Subscribe with QoS 0 {ok, _, [0]} = emqtt:subscribe(C, Topic, qos0), {ok, _} = emqtt:publish(C, Topic, <<"msg1">>, qos1), @@ -873,17 +1071,22 @@ subscribe_same_topic_same_qos(Config) -> {ok, _} = emqtt:publish(C, Topic, <<"msg2">>, qos1), %% "Any existing retained messages matching the Topic Filter MUST be re-sent" [MQTT-3.8.4-3] - ok = expect_publishes(C, Topic, [<<"retained">>, <<"msg1">>, - <<"retained">>, <<"msg2">> - ]), + ok = expect_publishes(C, Topic, [ + <<"retained">>, + <<"msg1">>, + <<"retained">>, + <<"msg2">> + ]), ok = emqtt:disconnect(C). subscribe_same_topic_different_qos(Config) -> C = connect(?FUNCTION_NAME, Config, [{clean_start, false}]), Topic = <<"b/c">>, - {ok, _} = emqtt:publish(C, Topic, <<"retained">>, [{retain, true}, - {qos, 1}]), + {ok, _} = emqtt:publish(C, Topic, <<"retained">>, [ + {retain, true}, + {qos, 1} + ]), %% Subscribe with QoS 0 {ok, _, [0]} = emqtt:subscribe(C, Topic, qos0), {ok, _} = emqtt:publish(C, Topic, <<"msg1">>, qos1), @@ -895,9 +1098,14 @@ subscribe_same_topic_different_qos(Config) -> {ok, _} = emqtt:publish(C, Topic, <<"msg3">>, qos1), %% "Any existing retained messages matching the Topic Filter MUST be re-sent" [MQTT-3.8.4-3] - ok = expect_publishes(C, Topic, [<<"retained">>, <<"msg1">>, - <<"retained">>, <<"msg2">>, - <<"retained">>, <<"msg3">>]), + ok = expect_publishes(C, Topic, [ + <<"retained">>, + <<"msg1">>, + <<"retained">>, + <<"msg2">>, + <<"retained">>, + <<"msg3">> + ]), %% There should be exactly one consumer for each queue: qos0 and qos1 Consumers = rpc(Config, rabbit_amqqueue, consumers_all, [<<"/">>]), @@ -910,9 +1118,13 @@ subscribe_same_topic_different_qos(Config) -> subscribe_multiple(Config) -> C = connect(?FUNCTION_NAME, Config), %% Subscribe to multiple topics at once - ?assertMatch({ok, _, [0, 1]}, - emqtt:subscribe(C, [{<<"topic0">>, qos0}, - {<<"topic1">>, qos1}])), + ?assertMatch( + {ok, _, [0, 1]}, + emqtt:subscribe(C, [ + {<<"topic0">>, qos0}, + {<<"topic1">>, qos1} + ]) + ), ok = emqtt:disconnect(C). large_message_mqtt_to_mqtt(Config) -> @@ -934,10 +1146,14 @@ large_message_amqp_to_mqtt(Config) -> Ch = rabbit_ct_client_helpers:open_channel(Config), Payload0 = binary:copy(<<"x">>, 8_000_000), Payload = <<Payload0/binary, "y">>, - amqp_channel:call(Ch, - #'basic.publish'{exchange = <<"amq.topic">>, - routing_key = Topic}, - #amqp_msg{payload = Payload}), + amqp_channel:call( + Ch, + #'basic.publish'{ + exchange = <<"amq.topic">>, + routing_key = Topic + }, + #amqp_msg{payload = Payload} + ), ok = expect_publishes(C, Topic, [Payload]), ok = emqtt:disconnect(C). @@ -947,10 +1163,14 @@ amqp_to_mqtt_qos0(Config) -> {ok, _, [0]} = emqtt:subscribe(C, {Topic, qos0}), Ch = rabbit_ct_client_helpers:open_channel(Config), - amqp_channel:call(Ch, - #'basic.publish'{exchange = <<"amq.topic">>, - routing_key = Topic}, - #amqp_msg{payload = Payload}), + amqp_channel:call( + Ch, + #'basic.publish'{ + exchange = <<"amq.topic">>, + routing_key = Topic + }, + #amqp_msg{payload = Payload} + ), ok = expect_publishes(C, Topic, [Payload]), ok = emqtt:disconnect(C). @@ -962,9 +1182,12 @@ many_qos1_messages(Config) -> {ok, _, [1]} = emqtt:subscribe(C, {Topic, qos1}), NumMsgs = 16#ffff + 100, Payloads = lists:map(fun integer_to_binary/1, lists:seq(1, NumMsgs)), - lists:foreach(fun(P) -> - {ok, _} = emqtt:publish(C, Topic, P, qos1) - end, Payloads), + lists:foreach( + fun(P) -> + {ok, _} = emqtt:publish(C, Topic, P, qos1) + end, + Payloads + ), expect_publishes(C, Topic, Payloads), ok = emqtt:disconnect(C). @@ -995,15 +1218,21 @@ management_plugin_connection(Config) -> C = connect(ClientId, Config, [{keepalive, KeepaliveSecs}]), eventually(?_assertEqual(1, length(http_get(Config, "/connections"))), 1000, 10), - [#{client_properties := #{client_id := ClientId}, - timeout := KeepaliveSecs, - node := Node, - name := ConnectionName}] = http_get(Config, "/connections"), + [ + #{ + client_properties := #{client_id := ClientId}, + timeout := KeepaliveSecs, + node := Node, + name := ConnectionName + } + ] = http_get(Config, "/connections"), process_flag(trap_exit, true), - http_delete(Config, - "/connections/" ++ binary_to_list(uri_string:quote((ConnectionName))), - ?NO_CONTENT), + http_delete( + Config, + "/connections/" ++ binary_to_list(uri_string:quote((ConnectionName))), + ?NO_CONTENT + ), await_exit(C), ?assertEqual([], http_get(Config, "/connections")), eventually(?_assertEqual([], all_connection_pids(Config)), 500, 3). @@ -1028,25 +1257,54 @@ cli_list_queues(Config) -> {ok, _, _} = emqtt:subscribe(C, <<"a/b/c">>, qos0), Qs = rabbit_ct_broker_helpers:rabbitmqctl_list( - Config, 1, - ["list_queues", "--no-table-headers", - "type", "name", "state", "durable", "auto_delete", - "arguments", "pid", "owner_pid", "messages", "exclusive_consumer_tag" - ]), - ExpectedQueueType = case rabbit_ct_helpers:is_mixed_versions(Config) of - false -> - <<"MQTT QoS 0">>; - true -> - <<"classic">> - end, - ?assertMatch([[ExpectedQueueType, <<"mqtt-subscription-cli_list_queuesqos0">>, - <<"running">>, <<"true">>, <<"false">>, <<"[]">>, _, _, <<"0">>, <<"">>]], - Qs), - - ?assertEqual([], - rabbit_ct_broker_helpers:rabbitmqctl_list( - Config, 1, ["list_queues", "--local", "--no-table-headers"]) - ), + Config, + 1, + [ + "list_queues", + "--no-table-headers", + "type", + "name", + "state", + "durable", + "auto_delete", + "arguments", + "pid", + "owner_pid", + "messages", + "exclusive_consumer_tag" + ] + ), + ExpectedQueueType = + case rabbit_ct_helpers:is_mixed_versions(Config) of + false -> + <<"MQTT QoS 0">>; + true -> + <<"classic">> + end, + ?assertMatch( + [ + [ + ExpectedQueueType, + <<"mqtt-subscription-cli_list_queuesqos0">>, + <<"running">>, + <<"true">>, + <<"false">>, + <<"[]">>, + _, + _, + <<"0">>, + <<"">> + ] + ], + Qs + ), + + ?assertEqual( + [], + rabbit_ct_broker_helpers:rabbitmqctl_list( + Config, 1, ["list_queues", "--local", "--no-table-headers"] + ) + ), ok = emqtt:disconnect(C). @@ -1078,30 +1336,39 @@ keepalive(Config) -> ProtoVer = v4, WillTopic = <<"will/topic">>, WillPayload = <<"will-payload">>, - C1 = connect(?FUNCTION_NAME, Config, [{keepalive, KeepaliveSecs}, - {proto_ver, ProtoVer}, - {will_topic, WillTopic}, - {will_payload, WillPayload}, - {will_retain, true}, - {will_qos, 0}]), + C1 = connect(?FUNCTION_NAME, Config, [ + {keepalive, KeepaliveSecs}, + {proto_ver, ProtoVer}, + {will_topic, WillTopic}, + {will_payload, WillPayload}, + {will_retain, true}, + {will_qos, 0} + ]), ok = emqtt:publish(C1, <<"ignored">>, <<"msg">>), %% Connection should stay up when client sends PING requests. timer:sleep(KeepaliveMs), - ?assertMatch(#{publishers := 1}, - util:get_global_counters(Config, ProtoVer)), + ?assertMatch( + #{publishers := 1}, + util:get_global_counters(Config, ProtoVer) + ), %% Mock the server socket to not have received any bytes. rabbit_ct_broker_helpers:setup_meck(Config), Mod = rabbit_net, ok = rpc(Config, meck, new, [Mod, [no_link, passthrough]]), - ok = rpc(Config, meck, expect, [Mod, getstat, 2, {ok, [{recv_oct, 999}]} ]), + ok = rpc(Config, meck, expect, [Mod, getstat, 2, {ok, [{recv_oct, 999}]}]), process_flag(trap_exit, true), %% We expect the server to respect the keepalive closing the connection. - eventually(?_assertMatch(#{publishers := 0}, - util:get_global_counters(Config, ProtoVer)), - KeepaliveMs, 3 * KeepaliveSecs), + eventually( + ?_assertMatch( + #{publishers := 0}, + util:get_global_counters(Config, ProtoVer) + ), + KeepaliveMs, + 3 * KeepaliveSecs + ), await_exit(C1), true = rpc(Config, meck, validate, [Mod]), @@ -1109,12 +1376,16 @@ keepalive(Config) -> C2 = connect(<<"client2">>, Config), {ok, _, [0]} = emqtt:subscribe(C2, WillTopic), - receive {publish, #{client_pid := C2, - dup := false, - qos := 0, - retain := true, - topic := WillTopic, - payload := WillPayload}} -> ok + receive + {publish, #{ + client_pid := C2, + dup := false, + qos := 0, + retain := true, + topic := WillTopic, + payload := WillPayload + }} -> + ok after 3000 -> ct:fail("missing will") end, ok = emqtt:disconnect(C2). @@ -1129,7 +1400,7 @@ keepalive_turned_off(Config) -> rabbit_ct_broker_helpers:setup_meck(Config), Mod = rabbit_net, ok = rpc(Config, meck, new, [Mod, [no_link, passthrough]]), - ok = rpc(Config, meck, expect, [Mod, getstat, 2, {ok, [{recv_oct, 999}]} ]), + ok = rpc(Config, meck, expect, [Mod, getstat, 2, {ok, [{recv_oct, 999}]}]), rabbit_ct_helpers:consistently(?_assert(erlang:is_process_alive(C))), @@ -1167,9 +1438,11 @@ block(Config) -> %% Unblock rpc(Config, vm_memory_monitor, set_vm_memory_high_watermark, [0.4]), - ok = expect_publishes(C, Topic, [<<"Not blocked yet">>, - <<"Now blocked">>, - <<"Still blocked">>]), + ok = expect_publishes(C, Topic, [ + <<"Not blocked yet">>, + <<"Now blocked">>, + <<"Still blocked">> + ]), ok = emqtt:disconnect(C). block_only_publisher(Config) -> @@ -1237,7 +1510,9 @@ clean_session_disconnect_client(Config) -> ok = emqtt:disconnect(C), %% After terminating a clean session, we expect any session state to be cleaned up on the server. - timer:sleep(200), %% Give some time to clean up exclusive classic queue. + + %% Give some time to clean up exclusive classic queue. + timer:sleep(200), L = rpc(Config, rabbit_amqqueue, list, []), ?assertEqual(0, length(L)). @@ -1280,9 +1555,12 @@ trace(Config) -> Ch = rabbit_ct_client_helpers:open_channel(Config), declare_queue(Ch, TraceQ, []), #'queue.bind_ok'{} = amqp_channel:call( - Ch, #'queue.bind'{queue = TraceQ, - exchange = <<"amq.rabbitmq.trace">>, - routing_key = <<"#">>}), + Ch, #'queue.bind'{ + queue = TraceQ, + exchange = <<"amq.rabbitmq.trace">>, + routing_key = <<"#">> + } + ), %% We expect traced messages for connections created before and connections %% created after tracing is enabled. @@ -1294,45 +1572,65 @@ trace(Config) -> {ok, _} = emqtt:publish(Pub, Topic, Payload, qos1), ok = expect_publishes(Sub, Topic, [Payload]), - {#'basic.get_ok'{routing_key = <<"publish.amq.topic">>}, - #amqp_msg{props = #'P_basic'{headers = PublishHeaders}, - payload = Payload}} = - amqp_channel:call(Ch, #'basic.get'{queue = TraceQ, no_ack = false}), - ?assertMatch(#{<<"exchange_name">> := <<"amq.topic">>, - <<"routing_keys">> := [Topic], - <<"connection">> := <<"127.0.0.1:", _/binary>>, - <<"node">> := Server, - <<"vhost">> := <<"/">>, - <<"channel">> := 0, - <<"user">> := <<"guest">>, - <<"properties">> := #{<<"delivery_mode">> := 2, - <<"headers">> := #{<<"x-mqtt-publish-qos">> := 1, - <<"x-mqtt-dup">> := false}}, - <<"routed_queues">> := [<<"mqtt-subscription-trace_subscriberqos0">>]}, - rabbit_misc:amqp_table(PublishHeaders)), - - {#'basic.get_ok'{routing_key = <<"deliver.mqtt-subscription-trace_subscriberqos0">>}, - #amqp_msg{props = #'P_basic'{headers = DeliverHeaders}, - payload = Payload}} = - amqp_channel:call(Ch, #'basic.get'{queue = TraceQ, no_ack = false}), - ?assertMatch(#{<<"exchange_name">> := <<"amq.topic">>, - <<"routing_keys">> := [Topic], - <<"connection">> := <<"127.0.0.1:", _/binary>>, - <<"node">> := Server, - <<"vhost">> := <<"/">>, - <<"channel">> := 0, - <<"user">> := <<"guest">>, - <<"properties">> := #{<<"delivery_mode">> := 2, - <<"headers">> := #{<<"x-mqtt-publish-qos">> := 1, - <<"x-mqtt-dup">> := false}}, - <<"redelivered">> := 0}, - rabbit_misc:amqp_table(DeliverHeaders)), + {#'basic.get_ok'{routing_key = <<"publish.amq.topic">>}, #amqp_msg{ + props = #'P_basic'{headers = PublishHeaders}, + payload = Payload + }} = + amqp_channel:call(Ch, #'basic.get'{queue = TraceQ, no_ack = false}), + ?assertMatch( + #{ + <<"exchange_name">> := <<"amq.topic">>, + <<"routing_keys">> := [Topic], + <<"connection">> := <<"127.0.0.1:", _/binary>>, + <<"node">> := Server, + <<"vhost">> := <<"/">>, + <<"channel">> := 0, + <<"user">> := <<"guest">>, + <<"properties">> := #{ + <<"delivery_mode">> := 2, + <<"headers">> := #{ + <<"x-mqtt-publish-qos">> := 1, + <<"x-mqtt-dup">> := false + } + }, + <<"routed_queues">> := [<<"mqtt-subscription-trace_subscriberqos0">>] + }, + rabbit_misc:amqp_table(PublishHeaders) + ), + + {#'basic.get_ok'{routing_key = <<"deliver.mqtt-subscription-trace_subscriberqos0">>}, #amqp_msg{ + props = #'P_basic'{headers = DeliverHeaders}, + payload = Payload + }} = + amqp_channel:call(Ch, #'basic.get'{queue = TraceQ, no_ack = false}), + ?assertMatch( + #{ + <<"exchange_name">> := <<"amq.topic">>, + <<"routing_keys">> := [Topic], + <<"connection">> := <<"127.0.0.1:", _/binary>>, + <<"node">> := Server, + <<"vhost">> := <<"/">>, + <<"channel">> := 0, + <<"user">> := <<"guest">>, + <<"properties">> := #{ + <<"delivery_mode">> := 2, + <<"headers">> := #{ + <<"x-mqtt-publish-qos">> := 1, + <<"x-mqtt-dup">> := false + } + }, + <<"redelivered">> := 0 + }, + rabbit_misc:amqp_table(DeliverHeaders) + ), {ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["trace_off"]), {ok, _} = emqtt:publish(Pub, Topic, Payload, qos1), ok = expect_publishes(Sub, Topic, [Payload]), - ?assertMatch(#'basic.get_empty'{}, - amqp_channel:call(Ch, #'basic.get'{queue = TraceQ, no_ack = false})), + ?assertMatch( + #'basic.get_empty'{}, + amqp_channel:call(Ch, #'basic.get'{queue = TraceQ, no_ack = false}) + ), delete_queue(Ch, TraceQ), [ok = emqtt:disconnect(C) || C <- [Pub, Sub]]. @@ -1351,7 +1649,7 @@ await_confirms_ordered(From, N, To) -> Got -> ct:fail("Received unexpected message. Expected: ~p Got: ~p", [Expected, Got]) after 10_000 -> - ct:fail("Did not receive expected message: ~p", [Expected]) + ct:fail("Did not receive expected message: ~p", [Expected]) end. await_confirms_unordered(_, 0) -> @@ -1363,32 +1661,45 @@ await_confirms_unordered(From, Left) -> Other -> ct:fail("Received unexpected message: ~p", [Other]) after 10_000 -> - ct:fail("~b confirms are missing", [Left]) + ct:fail("~b confirms are missing", [Left]) end. -declare_queue(Ch, QueueName, Args) - when is_pid(Ch), is_binary(QueueName), is_list(Args) -> +declare_queue(Ch, QueueName, Args) when + is_pid(Ch), is_binary(QueueName), is_list(Args) +-> #'queue.declare_ok'{} = amqp_channel:call( - Ch, #'queue.declare'{ - queue = QueueName, - durable = true, - arguments = Args}). - -delete_queue(Ch, QueueNames) - when is_pid(Ch), is_list(QueueNames) -> + Ch, #'queue.declare'{ + queue = QueueName, + durable = true, + arguments = Args + } + ). + +delete_queue(Ch, QueueNames) when + is_pid(Ch), is_list(QueueNames) +-> lists:foreach( - fun(Q) -> - delete_queue(Ch, Q) - end, QueueNames); -delete_queue(Ch, QueueName) - when is_pid(Ch), is_binary(QueueName) -> + fun(Q) -> + delete_queue(Ch, Q) + end, + QueueNames + ); +delete_queue(Ch, QueueName) when + is_pid(Ch), is_binary(QueueName) +-> #'queue.delete_ok'{} = amqp_channel:call( - Ch, #'queue.delete'{ - queue = QueueName}). - -bind(Ch, QueueName, Topic) - when is_pid(Ch), is_binary(QueueName), is_binary(Topic) -> + Ch, #'queue.delete'{ + queue = QueueName + } + ). + +bind(Ch, QueueName, Topic) when + is_pid(Ch), is_binary(QueueName), is_binary(Topic) +-> #'queue.bind_ok'{} = amqp_channel:call( - Ch, #'queue.bind'{queue = QueueName, - exchange = <<"amq.topic">>, - routing_key = Topic}). + Ch, #'queue.bind'{ + queue = QueueName, + exchange = <<"amq.topic">>, + routing_key = Topic + } + ). diff --git a/deps/rabbitmq_mqtt/test/util.erl b/deps/rabbitmq_mqtt/test/util.erl index 44c684a6c5..38689b2110 100644 --- a/deps/rabbitmq_mqtt/test/util.erl +++ b/deps/rabbitmq_mqtt/test/util.erl @@ -4,46 +4,58 @@ -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) -> @@ -51,20 +63,27 @@ 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) -> @@ -78,11 +97,14 @@ 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) -> - timer:sleep(300), %% events are sent and processed asynchronously + %% events are sent and processed asynchronously + timer:sleep(300), Result = gen_event:call({rabbit_event, Node}, event_recorder, take_state), ?assert(is_list(Result)), Result. @@ -92,24 +114,26 @@ 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) -> @@ -120,21 +144,27 @@ 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. diff --git a/deps/rabbitmq_mqtt/test/util_SUITE.erl b/deps/rabbitmq_mqtt/test/util_SUITE.erl index 3d058500ab..66b6f15c14 100644 --- a/deps/rabbitmq_mqtt/test/util_SUITE.erl +++ b/deps/rabbitmq_mqtt/test/util_SUITE.erl @@ -12,19 +12,18 @@ 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() -> diff --git a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_app.erl b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_app.erl index 7070128205..7dafc013f2 100644 --- a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_app.erl +++ b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_app.erl @@ -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,7 +58,8 @@ 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. @@ -70,36 +71,38 @@ 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; @@ -107,25 +110,28 @@ 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; @@ -133,34 +139,45 @@ 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) -> @@ -177,7 +194,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). diff --git a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl index 72f52b3977..2f017af696 100644 --- a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl +++ b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl @@ -24,23 +24,25 @@ -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{}. @@ -58,14 +60,22 @@ 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) -> @@ -75,22 +85,20 @@ 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(), @@ -100,12 +108,15 @@ 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}; @@ -113,24 +124,28 @@ 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) -> @@ -138,8 +153,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) -> @@ -149,39 +164,66 @@ 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}; @@ -189,18 +231,24 @@ websocket_info({keepalive, Req}, State = #state{keepalive = KState0, ?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}); @@ -227,10 +275,16 @@ 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), @@ -252,29 +306,49 @@ 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} -> @@ -289,9 +363,11 @@ 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. @@ -308,26 +384,34 @@ 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. @@ -341,18 +425,23 @@ 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), @@ -364,12 +453,13 @@ 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; @@ -383,22 +473,23 @@ 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); diff --git a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_stream_handler.erl b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_stream_handler.erl index 68d6ad2f29..b407623da6 100644 --- a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_stream_handler.erl +++ b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_stream_handler.erl @@ -14,7 +14,6 @@ -export([terminate/3]). -export([early_error/5]). - -record(state, {next}). init(StreamID, Req, Opts) -> diff --git a/deps/rabbitmq_web_mqtt/test/config_schema_SUITE.erl b/deps/rabbitmq_web_mqtt/test/config_schema_SUITE.erl index ec256f4421..2106d0a4ed 100644 --- a/deps/rabbitmq_web_mqtt/test/config_schema_SUITE.erl +++ b/deps/rabbitmq_web_mqtt/test/config_schema_SUITE.erl @@ -23,7 +23,6 @@ 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). @@ -31,15 +30,19 @@ 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). %% ------------------------------------------------------------------- @@ -47,9 +50,13 @@ 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). - diff --git a/deps/rabbitmq_web_mqtt/test/proxy_protocol_SUITE.erl b/deps/rabbitmq_web_mqtt/test/proxy_protocol_SUITE.erl index 95cd3e5c44..c6512cd6b6 100644 --- a/deps/rabbitmq_web_mqtt/test/proxy_protocol_SUITE.erl +++ b/deps/rabbitmq_web_mqtt/test/proxy_protocol_SUITE.erl @@ -7,7 +7,6 @@ -module(proxy_protocol_SUITE). - -compile([export_all, nowarn_export_all]). -include_lib("common_test/include/ct.hrl"). @@ -15,20 +14,24 @@ 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(), @@ -38,22 +41,29 @@ 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), @@ -64,12 +74,16 @@ 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). @@ -81,13 +95,23 @@ 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. diff --git a/deps/rabbitmq_web_mqtt/test/system_SUITE.erl b/deps/rabbitmq_web_mqtt/test/system_SUITE.erl index 19b0f873c4..6642ae2d5a 100644 --- a/deps/rabbitmq_web_mqtt/test/system_SUITE.erl +++ b/deps/rabbitmq_web_mqtt/test/system_SUITE.erl @@ -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,15 +35,19 @@ 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. @@ -72,7 +76,9 @@ 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)). @@ -110,7 +116,8 @@ 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. diff --git a/rebar.config b/rebar.config index 0590027d96..e291e7625a 100644 --- a/rebar.config +++ b/rebar.config @@ -14,3 +14,10 @@ inline_items => {when_under, 4} }} ]}. + +{project_plugins, [erlfmt]}. +{erlfmt, [ + write, + {print_width, 100}, + {files, "deps/{rabbitmq_mqtt,rabbitmq_web_mqtt}/{test,src}/*.erl"} +]}. |