Merge pull request #13871 from rabbitmq/md/health-check-multi-protocol-listener

Allow multiple protocols in protocol listener health check
This commit is contained in:
Michael Klishin 2025-05-08 20:22:22 +04:00 committed by GitHub
commit 98ba31a381
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 35 additions and 20 deletions

View File

@ -1202,10 +1202,12 @@ or:
<td></td>
<td></td>
<td></td>
<td class="path">/api/health/checks/protocol-listener/<i>protocol</i></td>
<td class="path">/api/health/checks/protocol-listener/<i>protocols</i></td>
<td>
Responds a 200 OK if there is an active listener for the given protocol,
otherwise responds with a 503 Service Unavailable. Valid protocol names are: amqp091, amqp10, mqtt, stomp, web-mqtt, web-stomp.
Responds a 200 OK if all given protocols have active listeners,
otherwise responds with a 503 Service Unavailable. Multiple protocols
may be provided by separating the names with commas. Valid protocol
names are: amqp091, amqp10, mqtt, stomp, web-mqtt, web-stomp.
</td>
</tr>
<tr>

View File

@ -200,7 +200,7 @@ dispatcher() ->
{"/health/checks/metadata-store/initialized/with-data", rabbit_mgmt_wm_health_check_metadata_store_initialized_with_data, []},
{"/health/checks/certificate-expiration/:within/:unit", rabbit_mgmt_wm_health_check_certificate_expiration, []},
{"/health/checks/port-listener/:port", rabbit_mgmt_wm_health_check_port_listener, []},
{"/health/checks/protocol-listener/:protocol", rabbit_mgmt_wm_health_check_protocol_listener, []},
{"/health/checks/protocol-listener/:protocols", rabbit_mgmt_wm_health_check_protocol_listener, []},
{"/health/checks/virtual-hosts", rabbit_mgmt_wm_health_check_virtual_hosts, []},
{"/health/checks/quorum-queues-without-elected-leaders/all-vhosts/", rabbit_mgmt_wm_health_check_quorum_queues_without_elected_leaders_across_all_vhosts, []},
{"/health/checks/quorum-queues-without-elected-leaders/vhost/:vhost/", rabbit_mgmt_wm_health_check_quorum_queues_without_elected_leaders, []},

View File

@ -27,32 +27,37 @@ content_types_provided(ReqData, Context) ->
{rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
resource_exists(ReqData, Context) ->
{case protocol(ReqData) of
{case protocols(ReqData) of
none -> false;
_ -> true
end, ReqData, Context}.
to_json(ReqData, Context) ->
Protocol = normalize_protocol(protocol(ReqData)),
Listeners = rabbit_networking:active_listeners(),
Local = [L || #listener{node = N} = L <- Listeners, N == node()],
ProtoListeners = [L || #listener{protocol = P} = L <- Local, atom_to_list(P) == Protocol],
case ProtoListeners of
Protocols = string:split(protocols(ReqData), ",", all),
RequestedProtocols = sets:from_list(
[normalize_protocol(P) || P <- Protocols],
[{version, 2}]),
Listeners = rabbit_networking:node_listeners(node()),
ActiveProtocols = sets:from_list(
[atom_to_list(P) || #listener{protocol = P} <- Listeners],
[{version, 2}]),
MissingProtocols = sets:to_list(sets:subtract(RequestedProtocols, ActiveProtocols)),
case MissingProtocols of
[] ->
Msg = <<"No active listener">>,
failure(Msg, Protocol, [P || #listener{protocol = P} <- Local], ReqData, Context);
Body = #{status => ok,
protocols => [list_to_binary(P) || P <- sets:to_list(ActiveProtocols)]},
rabbit_mgmt_util:reply(Body, ReqData, Context);
_ ->
Body = #{status => ok,
protocol => list_to_binary(Protocol)},
rabbit_mgmt_util:reply(Body, ReqData, Context)
Msg = <<"No active listener">>,
failure(Msg, MissingProtocols, sets:to_list(ActiveProtocols), ReqData, Context)
end.
failure(Message, Missing, Protocols, ReqData, Context) ->
Body = #{
status => failed,
reason => Message,
missing => list_to_binary(Missing),
protocols => Protocols
missing => [list_to_binary(P) || P <- Missing],
protocols => [list_to_binary(P) || P <- Protocols]
},
{Response, ReqData1, Context1} = rabbit_mgmt_util:reply(Body, ReqData, Context),
{stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
@ -60,8 +65,8 @@ failure(Message, Missing, Protocols, ReqData, Context) ->
is_authorized(ReqData, Context) ->
rabbit_mgmt_util:is_authorized(ReqData, Context).
protocol(ReqData) ->
rabbit_mgmt_util:id(protocol, ReqData).
protocols(ReqData) ->
rabbit_mgmt_util:id(protocols, ReqData).
normalize_protocol(Protocol) ->
case string:lowercase(binary_to_list(Protocol)) of

View File

@ -384,7 +384,7 @@ protocol_listener_test(Config) ->
Body0 = http_get_failed(Config, "/health/checks/protocol-listener/mqtt"),
?assertEqual(<<"failed">>, maps:get(<<"status">>, Body0)),
?assertEqual(true, maps:is_key(<<"reason">>, Body0)),
?assertEqual(<<"mqtt">>, maps:get(<<"missing">>, Body0)),
?assertEqual([<<"mqtt">>], maps:get(<<"missing">>, Body0)),
?assert(lists:member(<<"http">>, maps:get(<<"protocols">>, Body0))),
?assert(lists:member(<<"clustering">>, maps:get(<<"protocols">>, Body0))),
?assert(lists:member(<<"amqp">>, maps:get(<<"protocols">>, Body0))),
@ -394,6 +394,14 @@ protocol_listener_test(Config) ->
http_get_failed(Config, "/health/checks/protocol-listener/stomp"),
http_get_failed(Config, "/health/checks/protocol-listener/stomp1.0"),
%% Multiple protocols may be supplied. The health check only returns OK if
%% all requested protocols are available.
Body1 = http_get_failed(Config, "/health/checks/protocol-listener/amqp,mqtt"),
?assertEqual(<<"failed">>, maps:get(<<"status">>, Body1)),
?assertEqual(true, maps:is_key(<<"reason">>, Body1)),
?assert(lists:member(<<"mqtt">>, maps:get(<<"missing">>, Body1))),
?assert(lists:member(<<"amqp">>, maps:get(<<"protocols">>, Body1))),
passed.
port_listener_test(Config) ->