summaryrefslogtreecommitdiff
path: root/deps/rabbitmq_management/src
diff options
context:
space:
mode:
authordcorbacho <dparracorbacho@piotal.io>2020-11-18 14:27:41 +0000
committerdcorbacho <dparracorbacho@piotal.io>2020-11-18 14:27:41 +0000
commitf23a51261d9502ec39df0f8db47ba6b22aa7659f (patch)
tree53dcdf46e7dc2c14e81ee960bce8793879b488d3 /deps/rabbitmq_management/src
parentafa2c2bf6c7e0e9b63f4fb53dc931c70388e1c82 (diff)
parent9f6d64ec4a4b1eeac24d7846c5c64fd96798d892 (diff)
downloadrabbitmq-server-git-stream-timestamp-offset.tar.gz
Merge remote-tracking branch 'origin/master' into stream-timestamp-offsetstream-timestamp-offset
Diffstat (limited to 'deps/rabbitmq_management/src')
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_app.erl206
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_cors.erl84
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_csp.erl28
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_db.erl799
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_db_cache.erl143
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_db_cache_sup.erl29
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl185
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_extension.erl16
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_headers.erl34
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_hsts.erl28
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_load_definitions.erl27
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_reset_handler.erl79
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_stats.erl739
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_sup.erl42
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_sup_sup.erl28
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_util.erl1220
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_aliveness_test.erl71
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl51
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_auth_attempts.erl79
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_binding.erl141
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_bindings.erl149
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_channel.erl68
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_channels.erl50
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_channels_vhost.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_cluster_name.erl59
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_connection.erl98
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_channels.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_connections.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_connections_vhost.erl49
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_consumers.erl59
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_definitions.erl298
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange.erl90
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange_publish.erl105
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_exchanges.erl75
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_extensions.erl33
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl55
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flags.erl45
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameter.erl71
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameters.erl36
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_alarms.erl56
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_certificate_expiration.erl178
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_local_alarms.erl56
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_quorum_critical.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_port_listener.erl66
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_protocol_listener.erl127
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_virtual_hosts.erl48
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_healthchecks.erl76
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_limit.erl64
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_limits.erl59
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_login.erl53
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_node.erl78
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory.erl75
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory_ets.erl85
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_nodes.erl56
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policies.erl49
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policy.erl83
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_overview.erl182
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_parameter.erl88
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_parameters.erl78
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_permission.erl102
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions.erl38
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_user.erl47
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_vhost.erl44
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_policies.erl49
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_policy.erl82
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_queue.erl113
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_actions.erl68
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_get.erl166
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_purge.erl42
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_queues.erl113
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_rebalance_queues.erl58
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_redirect.erl13
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_reset.erl54
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl65
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permission.erl103
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions.erl38
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_user.erl47
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_vhost.erl44
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_user.erl73
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limit.erl62
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limits.erl63
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_users.erl59
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_users_bulk_delete.erl47
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost.erl97
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost_restart.erl56
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_vhosts.erl69
-rw-r--r--deps/rabbitmq_management/src/rabbit_mgmt_wm_whoami.erl36
88 files changed, 9044 insertions, 0 deletions
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_app.erl b/deps/rabbitmq_management/src/rabbit_mgmt_app.erl
new file mode 100644
index 0000000000..256ae56cc6
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_app.erl
@@ -0,0 +1,206 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_app).
+
+-behaviour(application).
+-export([start/2, stop/1, reset_dispatcher/1]).
+
+-ifdef(TEST).
+-export([get_listeners_config/0]).
+-endif.
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+-define(TCP_CONTEXT, rabbitmq_management_tcp).
+-define(TLS_CONTEXT, rabbitmq_management_tls).
+-define(DEFAULT_PORT, 15672).
+-define(DEFAULT_TLS_PORT, 15671).
+
+-rabbit_boot_step({rabbit_management_load_definitions,
+ [{description, "Imports definition file at management.load_definitions"},
+ {mfa, {rabbit_mgmt_load_definitions, boot, []}},
+ {enables, empty_db_check}]}).
+
+start(_Type, _StartArgs) ->
+ case application:get_env(rabbitmq_management_agent, disable_metrics_collector, false) of
+ false ->
+ start();
+ true ->
+ rabbit_log:warning("Metrics collection disabled in management agent, "
+ "management only interface started", []),
+ start()
+ end.
+
+stop(_State) ->
+ unregister_all_contexts(),
+ ok.
+
+%% At the point at which this is invoked we have both newly enabled
+%% apps and about-to-disable apps running (so that
+%% rabbit_mgmt_reset_handler can look at all of them to find
+%% extensions). Therefore we have to explicitly exclude
+%% about-to-disable apps from our new dispatcher.
+reset_dispatcher(IgnoreApps) ->
+ unregister_all_contexts(),
+ start_configured_listeners(IgnoreApps, false).
+
+-spec start_configured_listeners([atom()], boolean()) -> ok.
+start_configured_listeners(IgnoreApps, NeedLogStartup) ->
+ [start_listener(Listener, IgnoreApps, NeedLogStartup)
+ || Listener <- get_listeners_config()],
+ ok.
+
+get_listeners_config() ->
+ Listeners = case {has_configured_legacy_listener(),
+ has_configured_tcp_listener(),
+ has_configured_tls_listener()} of
+ {false, false, false} ->
+ %% nothing is configured
+ [get_tcp_listener()];
+ {false, false, true} ->
+ [get_tls_listener()];
+ {false, true, false} ->
+ [get_tcp_listener()];
+ {false, true, true} ->
+ [get_tcp_listener(),
+ get_tls_listener()];
+ {true, false, false} ->
+ [get_legacy_listener()];
+ {true, false, true} ->
+ [get_legacy_listener(),
+ get_tls_listener()];
+ {true, true, false} ->
+ %% This combination makes some sense:
+ %% legacy listener can be used to set up TLS :/
+ [get_legacy_listener(),
+ get_tcp_listener()];
+ {true, true, true} ->
+ %% what is happening?
+ rabbit_log:warning("Management plugin: TCP, TLS and a legacy (management.listener.*) listener are all configured. "
+ "Only two listeners at a time are supported. "
+ "Ignoring the legacy listener"),
+ [get_tcp_listener(),
+ get_tls_listener()]
+ end,
+ maybe_disable_sendfile(Listeners).
+
+maybe_disable_sendfile(Listeners) ->
+ DisableSendfile = #{sendfile => false},
+ F = fun(L0) ->
+ CowboyOptsL0 = proplists:get_value(cowboy_opts, L0, []),
+ CowboyOptsM0 = maps:from_list(CowboyOptsL0),
+ CowboyOptsM1 = maps:merge(DisableSendfile, CowboyOptsM0),
+ CowboyOptsL1 = maps:to_list(CowboyOptsM1),
+ L1 = lists:keydelete(cowboy_opts, 1, L0),
+ [{cowboy_opts, CowboyOptsL1}|L1]
+ end,
+ lists:map(F, Listeners).
+
+has_configured_legacy_listener() ->
+ has_configured_listener(listener).
+
+has_configured_tcp_listener() ->
+ has_configured_listener(tcp_config).
+
+has_configured_tls_listener() ->
+ has_configured_listener(ssl_config).
+
+has_configured_listener(Key) ->
+ case application:get_env(rabbitmq_management, Key, undefined) of
+ undefined -> false;
+ _ -> true
+ end.
+
+get_legacy_listener() ->
+ {ok, Listener0} = application:get_env(rabbitmq_management, listener),
+ {ok, Listener1} = ensure_port(tcp, Listener0),
+ Listener1.
+
+get_tls_listener() ->
+ {ok, Listener0} = application:get_env(rabbitmq_management, ssl_config),
+ {ok, Listener1} = ensure_port(tls, Listener0),
+ Port = proplists:get_value(port, Listener1),
+ case proplists:get_value(cowboy_opts, Listener0) of
+ undefined ->
+ [
+ {port, Port},
+ {ssl, true},
+ {ssl_opts, Listener0}
+ ];
+ CowboyOpts ->
+ Listener1 = lists:keydelete(cowboy_opts, 1, Listener0),
+ [
+ {port, Port},
+ {ssl, true},
+ {ssl_opts, Listener1},
+ {cowboy_opts, CowboyOpts}
+ ]
+ end.
+
+get_tcp_listener() ->
+ Listener0 = application:get_env(rabbitmq_management, tcp_config, []),
+ {ok, Listener1} = ensure_port(tcp, Listener0),
+ Listener1.
+
+start_listener(Listener, IgnoreApps, NeedLogStartup) ->
+ {Type, ContextName} = case is_tls(Listener) of
+ true -> {tls, ?TLS_CONTEXT};
+ false -> {tcp, ?TCP_CONTEXT}
+ end,
+ {ok, _} = register_context(ContextName, Listener, IgnoreApps),
+ case NeedLogStartup of
+ true -> log_startup(Type, Listener);
+ false -> ok
+ end,
+ ok.
+
+register_context(ContextName, Listener, IgnoreApps) ->
+ Dispatcher = rabbit_mgmt_dispatcher:build_dispatcher(IgnoreApps),
+ rabbit_web_dispatch:register_context_handler(
+ ContextName, Listener, "",
+ Dispatcher, "RabbitMQ Management").
+
+unregister_all_contexts() ->
+ rabbit_web_dispatch:unregister_context(?TCP_CONTEXT),
+ rabbit_web_dispatch:unregister_context(?TLS_CONTEXT).
+
+ensure_port(tls, Listener) ->
+ do_ensure_port(?DEFAULT_TLS_PORT, Listener);
+ensure_port(tcp, Listener) ->
+ do_ensure_port(?DEFAULT_PORT, Listener).
+
+do_ensure_port(Port, Listener) ->
+ %% include default port if it's not provided in the config
+ %% as Cowboy won't start if the port is missing
+ M0 = maps:from_list(Listener),
+ M1 = maps:merge(#{port => Port}, M0),
+ {ok, maps:to_list(M1)}.
+
+log_startup(tcp, Listener) ->
+ rabbit_log:info("Management plugin: HTTP (non-TLS) listener started on port ~w", [port(Listener)]);
+log_startup(tls, Listener) ->
+ rabbit_log:info("Management plugin: HTTPS listener started on port ~w", [port(Listener)]).
+
+
+port(Listener) ->
+ proplists:get_value(port, Listener, ?DEFAULT_PORT).
+
+is_tls(Listener) ->
+ case proplists:get_value(ssl, Listener) of
+ undefined -> false;
+ false -> false;
+ _ -> true
+ end.
+
+start() ->
+ %% Modern TCP listener uses management.tcp.*.
+ %% Legacy TCP (or TLS) listener uses management.listener.*.
+ %% Modern TLS listener uses management.ssl.*
+ start_configured_listeners([], true),
+ rabbit_mgmt_sup_sup:start_link().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_cors.erl b/deps/rabbitmq_management/src/rabbit_mgmt_cors.erl
new file mode 100644
index 0000000000..3871c1f8e2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_cors.erl
@@ -0,0 +1,84 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% Useful documentation about CORS:
+%% * https://tools.ietf.org/html/rfc6454
+%% * https://www.w3.org/TR/cors/
+%% * https://staticapps.org/articles/cross-domain-requests-with-cors/
+-module(rabbit_mgmt_cors).
+
+-export([set_headers/2]).
+
+set_headers(ReqData, Module) ->
+ %% Send vary: origin by default if nothing else was set.
+ ReqData1 = cowboy_req:set_resp_header(<<"vary">>, <<"origin">>, ReqData),
+ case match_origin(ReqData1) of
+ false ->
+ ReqData1;
+ Origin ->
+ ReqData2 = case cowboy_req:method(ReqData1) of
+ <<"OPTIONS">> -> handle_options(ReqData1, Module);
+ _ -> ReqData1
+ end,
+ ReqData3 = cowboy_req:set_resp_header(<<"access-control-allow-origin">>,
+ Origin,
+ ReqData2),
+ cowboy_req:set_resp_header(<<"access-control-allow-credentials">>,
+ "true",
+ ReqData3)
+ end.
+
+%% Set max-age from configuration (default: 30 minutes).
+%% Set allow-methods from what is defined in Module:allowed_methods/2.
+%% Set allow-headers to the same as the request (accept all headers).
+handle_options(ReqData0, Module) ->
+ MaxAge = application:get_env(rabbitmq_management, cors_max_age, 1800),
+ Methods = case erlang:function_exported(Module, allowed_methods, 2) of
+ false -> [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>];
+ true -> element(1, Module:allowed_methods(undefined, undefined))
+ end,
+ AllowMethods = string:join([binary_to_list(M) || M <- Methods], ", "),
+ ReqHeaders = cowboy_req:header(<<"access-control-request-headers">>, ReqData0),
+
+ ReqData1 = case MaxAge of
+ undefined -> ReqData0;
+ _ -> cowboy_req:set_resp_header(<<"access-control-max-age">>,
+ integer_to_list(MaxAge),
+ ReqData0)
+ end,
+ ReqData2 = case ReqHeaders of
+ undefined -> ReqData1;
+ _ -> cowboy_req:set_resp_header(<<"access-control-allow-headers">>,
+ ReqHeaders,
+ ReqData0)
+ end,
+ cowboy_req:set_resp_header(<<"access-control-allow-methods">>,
+ AllowMethods,
+ ReqData2).
+
+%% If the origin header is missing or "null", we disable CORS.
+%% Otherwise, we only enable it if the origin is found in the
+%% cors_allow_origins configuration variable, or if "*" is (it
+%% allows all origins).
+match_origin(ReqData) ->
+ case cowboy_req:header(<<"origin">>, ReqData) of
+ undefined -> false;
+ <<"null">> -> false;
+ Origin ->
+ AllowedOrigins = application:get_env(rabbitmq_management,
+ cors_allow_origins, []),
+ case lists:member(binary_to_list(Origin), AllowedOrigins) of
+ true ->
+ Origin;
+ false ->
+ %% Maybe the configuration explicitly allows "*".
+ case lists:member("*", AllowedOrigins) of
+ true -> Origin;
+ false -> false
+ end
+ end
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_csp.erl b/deps/rabbitmq_management/src/rabbit_mgmt_csp.erl
new file mode 100644
index 0000000000..80bba64fc2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_csp.erl
@@ -0,0 +1,28 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% Sets CSP header(s) on the response if configured,
+%% see https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP.
+
+-module(rabbit_mgmt_csp).
+
+-export([set_headers/1]).
+
+-define(CSP_HEADER, <<"content-security-policy">>).
+
+%%
+%% API
+%%
+
+set_headers(ReqData) ->
+ case application:get_env(rabbitmq_management, content_security_policy) of
+ undefined -> ReqData;
+ {ok, Value} ->
+ cowboy_req:set_resp_header(?CSP_HEADER,
+ rabbit_data_coercion:to_binary(Value),
+ ReqData)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_db.erl b/deps/rabbitmq_management/src/rabbit_mgmt_db.erl
new file mode 100644
index 0000000000..c45e7b86f6
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_db.erl
@@ -0,0 +1,799 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_db).
+
+%% pg2 is deprecated in OTP 23.
+-compile(nowarn_deprecated_function).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_metrics.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit_core_metrics.hrl").
+
+-behaviour(gen_server2).
+
+-export([start_link/0]).
+
+-export([augment_exchanges/3, augment_queues/3,
+ augment_nodes/2, augment_vhosts/2,
+ get_channel/2, get_connection/2,
+ get_all_channels/1, get_all_connections/1,
+ get_all_consumers/0, get_all_consumers/1,
+ get_overview/2, get_overview/1]).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3, handle_pre_hibernate/1,
+ format_message_queue/2]).
+
+-import(rabbit_misc, [pget/3]).
+
+-type maybe_slide() :: exometer_slide:slide() | not_found.
+-type slide_data() :: #{atom() => {maybe_slide(), maybe_slide()}}.
+-type maybe_range() :: rabbit_mgmt_stats:maybe_range().
+-type ranges() :: {maybe_range(), maybe_range(), maybe_range(), maybe_range()}.
+-type mfargs() :: {module(), atom(), [any()]}.
+-type lookup_key() :: atom() | {atom(), any()}.
+
+-define(NO_RANGES, {no_range, no_range, no_range, no_range}).
+
+-define(DEFAULT_TIMEOUT, 30000).
+
+%% The management database responds to queries from the various
+%% rabbit_mgmt_wm_* modules. It calls out to all rabbit nodes to fetch
+%% node-local data and aggregates it before returning it. It uses a worker-
+%% pool to provide a degree of parallelism.
+%%
+%% The management database reads metrics and stats written by the
+%% rabbit_mgmt_metrics_collector(s).
+%%
+%% The metrics collectors (there is one for each stats table - see ?TABLES
+%% in rabbit_mgmt_metrics.hrl) periodically read their corresponding core
+%% metrics ETS tables and aggregate the data into the management specific ETS
+%% tables.
+%%
+%% The metric collectors consume two types of core metrics: created (when an
+%% object is created, containing immutable facts about it) and stats (emitted on
+%% a timer, with mutable facts about the object). Deleted events are handled
+%% by the rabbit_mgmt_metrics_gc process.
+%% In this context "objects" means connections, channels, exchanges, queues,
+%% consumers, vhosts and nodes. Note that we do not care about users,
+%% permissions, bindings, parameters or policies.
+%%
+%% Connections and channels are identified by pids. Queues and
+%% exchanges are identified by names (which are #resource{}s). VHosts
+%% and nodes are identified by names which are binaries. And consumers
+%% are identified by {ChPid, QName, CTag}.
+%%
+%% The management collectors records the "created" metrics for
+%% connections, channels and consumers, and can thus be authoritative
+%% about those objects. For queues, exchanges and nodes we go to
+%% Mnesia to find out the immutable details of the objects.
+%%
+%% For everything other than consumers, the collectors can then augment
+%% these immutable details with stats, as the object changes. (We
+%% never emit anything very interesting about consumers).
+%%
+%% Stats on the inbound side are referred to as coarse and
+%% fine-grained. Fine grained statistics are the message rates
+%% maintained by channels and associated with tuples: {publishing
+%% channel, exchange}, {publishing channel, exchange, queue} and
+%% {queue, consuming channel}. Coarse grained stats are everything
+%% else and are associated with only one object, not a tuple.
+%%
+%% Within the management database though we rearrange things a bit: we
+%% refer to basic stats, simple stats and detail stats.
+%%
+%% Basic stats are those coarse grained stats for which we do not
+%% retain a history and do not perform any calculations -
+%% e.g. connection.state or channel.prefetch_count.
+%%
+%% Simple stats are those for which we do history / calculations which
+%% are associated with one object *after aggregation* - so these might
+%% originate with coarse grained stats - e.g. connection.send_oct or
+%% queue.messages_ready. But they might also originate from fine
+%% grained stats which have been aggregated - e.g. the message rates
+%% for a vhost or queue.
+%%
+%% Finally, detailed stats are those for which we do history /
+%% calculations which are associated with two objects. These
+%% have to have originated as fine grained stats, but can still have
+%% been aggregated.
+%%
+%% Created metrics and basic stats are stored in ETS tables by object.
+%% Simple and detailed stats (which only differ depending on how
+%% they're keyed) are stored in aggregated stats tables
+%% (see rabbit_mgmt_stats.erl and include/rabbit_mgmt_metrics.hrl)
+%%
+%% Keys from simple and detailed stats are aggregated in several
+%% records, stored in different ETS tables. We store a base counter
+%% for everything that happened before the samples we have kept,
+%% and a series of records which add the timestamp as part of the key.
+%%
+%% There is also a GC process to handle the deleted/closed
+%% rabbit events to remove the corresponding objects from the aggregated
+%% stats ETS tables.
+%%
+%% We also have an old_aggr_stats table to let us calculate instantaneous
+%% rates, in order to apportion simple / detailed stats into time
+%% slices as they come in. These instantaneous rates are not returned
+%% in response to any query, the rates shown in the API are calculated
+%% at query time. old_aggr_stats contains both coarse and fine
+%% entries. Coarse entries are pruned when the corresponding object is
+%% deleted, and fine entries are pruned when the emitting channel is
+%% closed, and whenever we receive new fine stats from a channel. So
+%% it's quite close to being a cache of "the previous stats we
+%% received".
+%%
+%% Overall the object is to do some aggregation when metrics are read
+%% and only aggregate metrics between nodes at query time.
+
+%%----------------------------------------------------------------------------
+%% API
+%%----------------------------------------------------------------------------
+
+start_link() ->
+ gen_server2:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+augment_exchanges(Xs, Ranges, basic) ->
+ submit(fun(Interval) -> list_exchange_stats(Ranges, Xs, Interval) end);
+augment_exchanges(Xs, Ranges, _) ->
+ submit(fun(Interval) -> detail_exchange_stats(Ranges, Xs, Interval) end).
+
+%% we can only cache if no ranges are requested.
+%% The mgmt ui doesn't use ranges for queue listings
+-spec augment_queues([proplists:proplist()], ranges(), basic | full) -> any().
+augment_queues(Qs, ?NO_RANGES = Ranges, basic) ->
+ submit_cached(queues,
+ fun(Interval, Queues) ->
+ list_queue_stats(Ranges, Queues, Interval)
+ end, Qs, max(60000, length(Qs) * 2));
+augment_queues(Qs, Ranges, basic) ->
+ submit(fun(Interval) -> list_queue_stats(Ranges, Qs, Interval) end);
+augment_queues(Qs, Ranges, _) ->
+ submit(fun(Interval) -> detail_queue_stats(Ranges, Qs, Interval) end).
+
+augment_vhosts(VHosts, Ranges) ->
+ submit(fun(Interval) -> vhost_stats(Ranges, VHosts, Interval) end).
+
+augment_nodes(Nodes, Ranges) ->
+ submit(fun(Interval) -> node_stats(Ranges, Nodes, Interval) end).
+
+get_channel(Name, Ranges) ->
+ submit(fun(Interval) ->
+ case created_stats_delegated(Name, channel_created_stats) of
+ not_found -> not_found;
+ Ch -> [Result] =
+ detail_channel_stats(Ranges, [Ch], Interval),
+ Result
+ end
+ end).
+
+get_connection(Name, Ranges) ->
+ submit(fun(Interval) ->
+ case created_stats_delegated(Name, connection_created_stats) of
+ not_found -> not_found;
+ C ->
+ [Result] = connection_stats(Ranges, [C], Interval),
+ Result
+ end
+ end).
+
+get_all_channels(?NO_RANGES = Ranges) ->
+ submit_cached(channels,
+ fun(Interval) ->
+ Chans = created_stats_delegated(channel_created_stats),
+ list_channel_stats(Ranges, Chans, Interval)
+ end);
+get_all_channels(Ranges) ->
+ submit(fun(Interval) ->
+ Chans = created_stats_delegated(channel_created_stats),
+ list_channel_stats(Ranges, Chans, Interval)
+ end).
+
+get_all_connections(?NO_RANGES = Ranges) ->
+ submit_cached(connections,
+ fun(Interval) ->
+ Chans = created_stats_delegated(connection_created_stats),
+ connection_stats(Ranges, Chans, Interval)
+ end);
+get_all_connections(Ranges) ->
+ submit(fun(Interval) ->
+ Chans = created_stats_delegated(connection_created_stats),
+ connection_stats(Ranges, Chans, Interval)
+ end).
+
+get_all_consumers() -> get_all_consumers(all).
+get_all_consumers(VHosts) ->
+ submit(fun(_Interval) -> consumers_stats(VHosts) end).
+
+get_overview(Ranges) -> get_overview(all, Ranges).
+get_overview(User, Ranges) ->
+ submit(fun(Interval) -> overview(User, Ranges, Interval) end).
+
+%%----------------------------------------------------------------------------
+%% Internal, gen_server2 callbacks
+%%----------------------------------------------------------------------------
+
+-record(state, {interval}).
+
+init([]) ->
+ {ok, Interval} = application:get_env(rabbit, collect_statistics_interval),
+ rabbit_log:info("Statistics database started."),
+ {ok, #state{interval = Interval}, hibernate,
+ {backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE}}.
+
+handle_call(_Request, _From, State) ->
+ reply(not_understood, State).
+
+handle_cast(_Request, State) ->
+ noreply(State).
+
+handle_info(_Info, State) ->
+ noreply(State).
+
+terminate(_Arg, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+reply(Reply, NewState) -> {reply, Reply, NewState, hibernate}.
+noreply(NewState) -> {noreply, NewState, hibernate}.
+
+handle_pre_hibernate(State) ->
+ %% rabbit_event can end up holding on to some memory after a busy
+ %% workout, but it's not a gen_server so we can't make it
+ %% hibernate. The best we can do is forcibly GC it here (if
+ %% rabbit_mgmt_db is hibernating the odds are rabbit_event is
+ %% quiescing in some way too).
+ _ = rpc:multicall(
+ rabbit_nodes:all_running(), rabbit_mgmt_db_handler, gc, []),
+ {hibernate, State}.
+
+format_message_queue(Opt, MQ) -> rabbit_misc:format_message_queue(Opt, MQ).
+
+%%----------------------------------------------------------------------------
+%% Internal, utilities
+%%----------------------------------------------------------------------------
+
+pget(Key, List) -> pget(Key, List, unknown).
+
+-type id_name() :: 'name' | 'route' | 'pid'.
+-spec id_name(atom()) -> id_name().
+
+%% id_name() and id() are for use when handling events, id_lookup()
+%% for when augmenting. The difference is that when handling events a
+%% queue name will be a resource, but when augmenting we will be
+%% passed a queue proplist that will already have been formatted -
+%% i.e. it will have name and vhost keys.
+id_name(node_stats) -> name;
+id_name(node_node_stats) -> route;
+id_name(vhost_stats) -> name;
+id_name(queue_stats) -> name;
+id_name(exchange_stats) -> name;
+id_name(channel_stats) -> pid;
+id_name(connection_stats) -> pid.
+
+id(Type, List) -> pget(id_name(Type), List).
+
+id_lookup(queue_stats, List) ->
+ rabbit_misc:r(pget(vhost, List), queue, pget(name, List));
+id_lookup(exchange_stats, List) ->
+ rabbit_misc:r(pget(vhost, List), exchange, pget(name, List));
+id_lookup(Type, List) ->
+ id(Type, List).
+
+
+%%----------------------------------------------------------------------------
+%% Internal, querying side api
+%%----------------------------------------------------------------------------
+
+overview(User, Ranges, Interval) ->
+ VHosts = case User of
+ all -> rabbit_vhost:list_names();
+ _ -> rabbit_mgmt_util:list_visible_vhosts_names(User)
+ end,
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, overview_data,
+ [User, Ranges, VHosts]}),
+ MessageStats = lists:append(
+ [format_range(DataLookup, vhost_stats_fine_stats,
+ pick_range(fine_stats, Ranges), Interval),
+ format_range(DataLookup, vhost_msg_rates,
+ pick_range(queue_msg_rates, Ranges), Interval),
+ format_range(DataLookup, vhost_stats_deliver_stats,
+ pick_range(deliver_get, Ranges), Interval)]),
+
+ ChurnRates = format_range(DataLookup, connection_churn_rates,
+ pick_range(queue_msg_counts, Ranges), Interval),
+ QueueStats = format_range(DataLookup, vhost_msg_stats,
+ pick_range(queue_msg_counts, Ranges), Interval),
+ %% Filtering out the user's consumers would be rather expensive so let's
+ %% just not show it
+ Consumers = case User of
+ all -> [{consumers, maps:get(consumers_count, DataLookup)}];
+ _ -> []
+ end,
+ ObjectTotals = Consumers ++
+ [{queues, length([Q || V <- VHosts, Q <- rabbit_amqqueue:list(V)])},
+ {exchanges, length([X || V <- VHosts, X <- rabbit_exchange:list(V)])},
+ {connections, maps:get(connections_count, DataLookup)},
+ {channels, maps:get(channels_count, DataLookup)}],
+
+ [{message_stats, MessageStats},
+ {churn_rates, ChurnRates},
+ {queue_totals, QueueStats},
+ {object_totals, ObjectTotals},
+ {statistics_db_event_queue, event_queue()}]. % TODO: event queue?
+
+event_queue() ->
+ lists:foldl(fun ({T, _}, Sum) ->
+ case whereis(rabbit_mgmt_metrics_collector:name(T)) of
+ P when is_pid(P) ->
+ {message_queue_len, Len} =
+ erlang:process_info(P, message_queue_len),
+ Sum + Len;
+ _ -> Sum
+ end
+ end, 0, ?CORE_TABLES).
+
+consumers_stats(VHost) ->
+ Data = get_data_from_nodes({rabbit_mgmt_data, consumer_data, [VHost]}),
+ Consumers = rabbit_mgmt_data_compat:fill_consumer_active_fields(
+ [V || {_,V} <- maps:to_list(Data)]),
+ ChPids = [ pget(channel_pid, Con)
+ || Con <- Consumers, [] =:= pget(channel_details, Con)],
+ ChDets = get_channel_detail_lookup(ChPids),
+ [merge_channel_into_obj(Con, ChDets) || Con <- Consumers].
+
+-spec list_queue_stats(ranges(), [proplists:proplist()], integer()) ->
+ [proplists:proplist()].
+list_queue_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(queue_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_list_queue_data, [Ids, Ranges]}),
+ adjust_hibernated_memory_use(
+ [begin
+ Id = id_lookup(queue_stats, Obj),
+ Pid = pget(pid, Obj),
+ QueueData = maps:get(Id, DataLookup),
+ Props = maps:get(queue_stats, QueueData),
+ Stats = queue_stats(QueueData, Ranges, Interval),
+ {Pid, combine(Props, Obj) ++ Stats}
+ end || Obj <- Objs]).
+
+detail_queue_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(queue_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_detail_queue_data,
+ [Ids, Ranges]}),
+
+ QueueStats = adjust_hibernated_memory_use(
+ [begin
+ Id = id_lookup(queue_stats, Obj),
+ Pid = pget(pid, Obj),
+ QueueData = maps:get(Id, DataLookup),
+ Props = maps:get(queue_stats, QueueData),
+ Stats = queue_stats(QueueData, Ranges, Interval),
+ ConsumerStats = rabbit_mgmt_data_compat:fill_consumer_active_fields(
+ maps:get(consumer_stats, QueueData)),
+ Consumers = [{consumer_details, ConsumerStats}],
+ StatsD = [{deliveries,
+ detail_stats(QueueData, channel_queue_stats_deliver_stats,
+ deliver_get, second(Id), Ranges, Interval)},
+ {incoming,
+ detail_stats(QueueData, queue_exchange_stats_publish,
+ fine_stats, first(Id), Ranges, Interval)}],
+ Details = augment_details(Obj, []),
+ {Pid, combine(Props, Obj) ++ Stats ++ StatsD ++ Consumers ++ Details}
+ end || Obj <- Objs]),
+
+ % patch up missing channel details
+ ChPids = lists:usort(get_pids_for_missing_channel_details(QueueStats)),
+ ChDets = get_channel_detail_lookup(ChPids),
+ Merged = merge_channel_details(QueueStats, ChDets),
+ Merged.
+
+node_node_stats(Lookup, Node, Ranges, Interval) ->
+ LocalNodeNodeMetrics = maps:from_list(ets:tab2list(node_node_metrics)),
+ RemoteNodeNodeMetrics = maps:get(node_node_metrics, Lookup),
+ NodeNodeMetrics = maps:merge(LocalNodeNodeMetrics, RemoteNodeNodeMetrics),
+ node_node_stats(Lookup, Node, Ranges, Interval, NodeNodeMetrics).
+
+node_node_stats(Lookup, Node, Ranges, Interval, NodeNodeMetrics) ->
+ Table = node_node_coarse_stats,
+ Type = coarse_node_node_stats,
+ [begin
+ {Stats, DetailId} = get_detail_stats(Key, Lookup, Table, Type,
+ first(Node), Ranges, Interval),
+ NodeMetrics = maybe_fetch_value(Key, NodeNodeMetrics),
+ lists:flatten([{stats, Stats}, DetailId, NodeMetrics])
+ end || {{T, Key}, _} <- maps:to_list(Lookup), T =:= Table].
+
+detail_stats(Lookup, Table, Type, Id, Ranges, Interval) ->
+ [begin
+ {Stats, DetailId} = get_detail_stats(Key, Lookup, Table, Type,
+ Id, Ranges, Interval),
+ [{stats, Stats}|DetailId] %TODO: not actually delegated
+ end || {{T, Key}, _} <- maps:to_list(Lookup), T =:= Table].
+
+get_detail_stats(Key, Lookup, Table, Type, Id, Ranges, Interval) ->
+ Range = pick_range(Type, Ranges),
+ Stats = format_range(Lookup, {Table, Key}, Range, Interval),
+ DetailId = format_detail_id(revert(Id, Key)),
+ {Stats, DetailId}.
+
+queue_stats(QueueData, Ranges, Interval) ->
+ message_stats(format_range(QueueData, queue_stats_publish,
+ pick_range(fine_stats, Ranges), Interval) ++
+ format_range(QueueData, queue_stats_deliver_stats,
+ pick_range(deliver_get, Ranges), Interval)) ++
+ format_range(QueueData, queue_process_stats,
+ pick_range(process_stats, Ranges), Interval) ++
+ format_range(QueueData, queue_msg_stats,
+ pick_range(queue_msg_counts, Ranges), Interval).
+
+channel_stats(ChannelData, Ranges, Interval) ->
+ message_stats(format_range(ChannelData, channel_stats_fine_stats,
+ pick_range(fine_stats, Ranges), Interval) ++
+ format_range(ChannelData, channel_stats_deliver_stats,
+ pick_range(deliver_get, Ranges), Interval)) ++
+ format_range(ChannelData, channel_process_stats,
+ pick_range(process_stats, Ranges), Interval).
+
+-spec format_range(slide_data(), lookup_key(), maybe_range(), non_neg_integer()) ->
+ proplists:proplist().
+format_range(Data, Key, Range0, Interval) ->
+ Table = case Key of
+ {T, _} -> T;
+ T -> T
+ end,
+ InstantRateFun = fun() -> fetch_slides(1, Key, Data) end,
+ SamplesFun = fun() -> fetch_slides(2, Key, Data) end,
+ Now = exometer_slide:timestamp(),
+ rabbit_mgmt_stats:format_range(Range0, Now, Table, Interval, InstantRateFun,
+ SamplesFun).
+
+%% basic.get-empty metric
+fetch_slides(Ele, Key, Data)
+ when Key =:= channel_queue_stats_deliver_stats orelse
+ Key =:= channel_stats_deliver_stats orelse
+ Key =:= queue_stats_deliver_stats orelse
+ Key =:= vhost_stats_deliver_stats orelse
+ (is_tuple(Key) andalso
+ (element(1, Key) =:= channel_queue_stats_deliver_stats orelse
+ element(1, Key) =:= channel_stats_deliver_stats orelse
+ element(1, Key) =:= queue_stats_deliver_stats orelse
+ element(1, Key) =:= vhost_stats_deliver_stats)) ->
+ case element(Ele, maps:get(Key, Data)) of
+ not_found -> [];
+ Slides when is_list(Slides) ->
+ [rabbit_mgmt_data_compat:fill_get_empty_queue_metric(S)
+ || S <- Slides, not_found =/= S];
+ Slide ->
+ [rabbit_mgmt_data_compat:fill_get_empty_queue_metric(Slide)]
+ end;
+%% drop_unroutable metric
+fetch_slides(Ele, Key, Data)
+ when Key =:= channel_stats_fine_stats orelse
+ Key =:= channel_exchange_stats_fine_stats orelse
+ Key =:= vhost_stats_fine_stats orelse
+ (is_tuple(Key) andalso
+ (element(1, Key) =:= channel_stats_fine_stats orelse
+ element(1, Key) =:= channel_exchange_stats_fine_stats orelse
+ element(1, Key) =:= vhost_stats_fine_stats)) ->
+ case element(Ele, maps:get(Key, Data)) of
+ not_found -> [];
+ Slides when is_list(Slides) ->
+ [rabbit_mgmt_data_compat:fill_drop_unroutable_metric(S)
+ || S <- Slides, not_found =/= S];
+ Slide ->
+ [rabbit_mgmt_data_compat:fill_drop_unroutable_metric(Slide)]
+ end;
+fetch_slides(Ele, Key, Data) ->
+ case element(Ele, maps:get(Key, Data)) of
+ not_found -> [];
+ Slides when is_list(Slides) ->
+ [S || S <- Slides, not_found =/= S];
+ Slide ->
+ [Slide]
+ end.
+
+get_channel_detail_lookup(ChPids) ->
+ ChDets = delegate_invoke({rabbit_mgmt_data, augment_channel_pids, [ChPids]}),
+ maps:from_list([{pget(pid, C), C} || [_|_] = C <- lists:append(ChDets)]).
+
+merge_channel_details(QueueStats, Lookup) ->
+ [begin
+ Cons = pget(consumer_details, QueueStat),
+ Cons1 = [merge_channel_into_obj(Con, Lookup) || Con <- Cons],
+ rabbit_misc:pset(consumer_details, Cons1, QueueStat)
+ end || QueueStat <- QueueStats].
+
+merge_channel_into_obj(Obj, ChDet) ->
+ case pget(channel_details, Obj) of
+ [] -> case maps:find(pget(channel_pid, Obj), ChDet) of
+ {ok, CHd} ->
+ rabbit_misc:pset(channel_details, CHd, Obj);
+ error ->
+ Obj
+ end;
+ _ -> Obj
+ end.
+
+get_pids_for_missing_channel_details(QueueStats) ->
+ CDs = lists:append([pget(consumer_details, QueueStat) || QueueStat <- QueueStats]),
+ [ pget(channel_pid, CD) || CD <- CDs, [] =:= pget(channel_details, CD)].
+
+
+list_exchange_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(exchange_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_exchange_data, [Ids, Ranges]}),
+ [begin
+ Id = id_lookup(exchange_stats, Obj),
+ ExData = maps:get(Id, DataLookup),
+ Stats = message_stats(format_range(ExData, exchange_stats_publish_out,
+ pick_range(fine_stats, Ranges), Interval) ++
+ format_range(ExData, exchange_stats_publish_in,
+ pick_range(deliver_get, Ranges), Interval)),
+ Obj ++ Stats
+ end || Obj <- Objs].
+
+detail_exchange_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(exchange_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_exchange_data, [Ids, Ranges]}),
+ [begin
+ Id = id_lookup(exchange_stats, Obj),
+ ExData = maps:get(Id, DataLookup),
+ Stats = message_stats(format_range(ExData, exchange_stats_publish_out,
+ pick_range(fine_stats, Ranges), Interval) ++
+ format_range(ExData, exchange_stats_publish_in,
+ pick_range(deliver_get, Ranges), Interval)),
+ StatsD = [{incoming,
+ detail_stats(ExData, channel_exchange_stats_fine_stats,
+ fine_stats, second(Id), Ranges, Interval)},
+ {outgoing,
+ detail_stats(ExData, queue_exchange_stats_publish,
+ fine_stats, second(Id), Ranges, Interval)}],
+ %% remove live state? not sure it has!
+ Obj ++ StatsD ++ Stats
+ end || Obj <- Objs].
+
+connection_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(connection_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_connection_data, [Ids, Ranges]}),
+ [begin
+ Id = id_lookup(connection_stats, Obj),
+ ConnData = maps:get(Id, DataLookup),
+ Props = maps:get(connection_stats, ConnData),
+ Stats = format_range(ConnData, connection_stats_coarse_conn_stats,
+ pick_range(coarse_conn_stats, Ranges), Interval),
+ Details = augment_details(Obj, []), % TODO: not delegated
+ combine(Props, Obj) ++ Details ++ Stats
+ end || Obj <- Objs].
+
+list_channel_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(channel_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_list_channel_data, [Ids, Ranges]}),
+ ChannelStats =
+ [begin
+ Id = id_lookup(channel_stats, Obj),
+ ChannelData = maps:get(Id, DataLookup),
+ Props = maps:get(channel_stats, ChannelData),
+ Stats = channel_stats(ChannelData, Ranges, Interval),
+ combine(Props, Obj) ++ Stats
+ end || Obj <- Objs],
+ ChannelStats.
+
+detail_channel_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(channel_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_detail_channel_data,
+ [Ids, Ranges]}),
+ ChannelStats =
+ [begin
+ Id = id_lookup(channel_stats, Obj),
+ ChannelData = maps:get(Id, DataLookup),
+ Props = maps:get(channel_stats, ChannelData),
+ Stats = channel_stats(ChannelData, Ranges, Interval),
+ ConsumerStats = rabbit_mgmt_data_compat:fill_consumer_active_fields(
+ maps:get(consumer_stats, ChannelData)),
+ Consumers = [{consumer_details, ConsumerStats}],
+ StatsD = [{publishes,
+ detail_stats(ChannelData, channel_exchange_stats_fine_stats,
+ fine_stats, first(Id), Ranges, Interval)},
+ {deliveries,
+ detail_stats(ChannelData, channel_queue_stats_deliver_stats,
+ fine_stats, first(Id), Ranges, Interval)}],
+ combine(Props, Obj) ++ Consumers ++ Stats ++ StatsD
+ end || Obj <- Objs],
+ rabbit_mgmt_format:strip_pids(ChannelStats).
+
+vhost_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(vhost_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_vhost_data, [Ids, Ranges]}),
+ [begin
+ Id = id_lookup(vhost_stats, Obj),
+ VData = maps:get(Id, DataLookup),
+ Stats = format_range(VData, vhost_stats_coarse_conn_stats,
+ pick_range(coarse_conn_stats, Ranges), Interval) ++
+ format_range(VData, vhost_msg_stats,
+ pick_range(queue_msg_rates, Ranges), Interval),
+ StatsD = message_stats(format_range(VData, vhost_stats_fine_stats,
+ pick_range(fine_stats, Ranges), Interval) ++
+ format_range(VData, vhost_stats_deliver_stats,
+ pick_range(deliver_get, Ranges), Interval)),
+ Details = augment_details(Obj, []),
+ Obj ++ Details ++ Stats ++ StatsD
+ end || Obj <- Objs].
+
+node_stats(Ranges, Objs, Interval) ->
+ Ids = [id_lookup(node_stats, Obj) || Obj <- Objs],
+ DataLookup = get_data_from_nodes({rabbit_mgmt_data, all_node_data, [Ids, Ranges]}),
+ [begin
+ Id = id_lookup(node_stats, Obj),
+ NData = maps:get(Id, DataLookup),
+ Props = maps:get(node_stats, NData),
+ Stats = format_range(NData, node_coarse_stats,
+ pick_range(coarse_node_stats, Ranges), Interval) ++
+ format_range(NData, node_persister_stats,
+ pick_range(coarse_node_stats, Ranges), Interval) ++
+ format_range(NData, connection_churn_rates,
+ pick_range(churn_rates, Ranges), Interval),
+ NodeNodeStats = node_node_stats(NData, Id, Ranges, Interval),
+ StatsD = [{cluster_links, NodeNodeStats}],
+ MgmtStats = maps:get(mgmt_stats, NData),
+ Details = augment_details(Obj, []), % augmentation needs to be node local
+ combine(Props, Obj) ++ Details ++ Stats ++ StatsD ++ MgmtStats
+ end || Obj <- Objs].
+
+combine(New, Old) ->
+ case pget(state, Old) of
+ unknown -> New ++ Old;
+ live -> New ++ lists:keydelete(state, 1, Old);
+ _ -> lists:keydelete(state, 1, New) ++ Old
+ end.
+
+revert({'_', _}, {Id, _}) ->
+ Id;
+revert({_, '_'}, {_, Id}) ->
+ Id.
+
+%%----------------------------------------------------------------------------
+%% Internal, delegated operations
+%%----------------------------------------------------------------------------
+
+-spec get_data_from_nodes(mfargs()) -> #{atom() => any()}.
+get_data_from_nodes(MFA) ->
+ Data = delegate_invoke(MFA),
+ lists:foldl(fun(D, Agg) ->
+ maps_merge(fun merge_data/3, D, Agg)
+ end, #{}, Data).
+
+maps_merge(Fun, M1, M2) ->
+ JustM2 = maps:without(maps:keys(M1), M2),
+ maps:merge(JustM2,
+ maps:map(fun(K, V1) ->
+ case maps:find(K, M2) of
+ {ok, V2} -> Fun(K, V1, V2);
+ error -> V1
+ end
+ end,
+ M1)).
+
+-spec merge_data(atom(), any(), any()) -> any().
+merge_data(_, A, B) when is_integer(A), is_integer(B) -> A + B;
+merge_data(_, A, B) when is_list(A), is_list(B) ->
+ A ++ B;
+merge_data(_, {A1, B1}, {[_|_] = A2, [_|_] = B2}) ->
+ {[A1 | A2], [B1 | B2]};
+merge_data(_, {A1, B1}, {A2, B2}) -> % first slide
+ {[A1, A2], [B1, B2]};
+merge_data(_, D1, D2) -> % we assume if we get here both values a maps
+ try
+ maps_merge(fun merge_data/3, D1, D2)
+ catch
+ error:Err ->
+ rabbit_log:debug("merge_data err ~p got: ~p ~p ~n", [Err, D1, D2]),
+ case is_map(D1) of
+ true -> D1;
+ false -> D2
+ end
+ end.
+
+%% We do this when retrieving the queue record rather than when
+%% storing it since the memory use will drop *after* we find out about
+%% hibernation, so to do it when we receive a queue stats event would
+%% be fiddly and racy. This should be quite cheap though.
+adjust_hibernated_memory_use(Qs) ->
+ Pids = [Pid || {Pid, Q} <- Qs, pget(idle_since, Q, not_idle) =/= not_idle],
+ %% We use delegate here not for ordering reasons but because we
+ %% want to get the right amount of parallelism and minimise
+ %% cross-cluster communication.
+ {Mem, _BadNodes} = delegate:invoke(Pids, {erlang, process_info, [memory]}),
+ MemDict = maps:from_list([{P, M} || {P, M = {memory, _}} <- Mem]),
+ [case maps:find(Pid, MemDict) of
+ error -> Q;
+ {ok, Memory} -> [Memory | proplists:delete(memory, Q)]
+ end || {Pid, Q} <- Qs].
+
+-spec created_stats_delegated(any(), fun((any()) -> any()) | atom()) -> not_found | any().
+created_stats_delegated(Key, Type) ->
+ Data = delegate_invoke({rabbit_mgmt_data, augmented_created_stats, [Key, Type]}),
+ case [X || X <- Data, X =/= not_found] of
+ [] -> not_found;
+ [X] -> X
+ end.
+
+created_stats_delegated(Type) ->
+ lists:append(
+ delegate_invoke({rabbit_mgmt_data, augmented_created_stats, [Type]})).
+
+-spec delegate_invoke(mfargs()) -> [any()].
+delegate_invoke(FunOrMFA) ->
+ MemberPids = [P || P <- pg2:get_members(management_db)],
+ {Results, Errors} = delegate:invoke(MemberPids, ?DELEGATE_PREFIX, FunOrMFA),
+ case Errors of
+ [] -> ok;
+ _ -> rabbit_log:warning("Management delegate query returned errors:~n~p", [Errors])
+ end,
+ [R || {_, R} <- Results].
+
+submit(Fun) ->
+ {ok, Interval} = application:get_env(rabbit, collect_statistics_interval),
+ worker_pool:submit(management_worker_pool, fun() -> Fun(Interval) end, reuse).
+
+submit_cached(Key, Fun) ->
+ {ok, Interval} = application:get_env(rabbit, collect_statistics_interval),
+ {ok, Res} = rabbit_mgmt_db_cache:fetch(Key, fun() -> Fun(Interval) end),
+ Res.
+
+submit_cached(Key, Fun, Arg, Timeout) ->
+ {ok, Interval} = application:get_env(rabbit, collect_statistics_interval),
+ {ok, Res} = rabbit_mgmt_db_cache:fetch(Key,
+ fun(A) -> Fun(Interval, A) end,
+ [Arg], Timeout),
+ Res.
+
+%% Note: Assumes Key is a two-tuple.
+%% If not found at first, Key is reversed and tried again.
+%% Seems to be necessary based on node_node_metrics table keys
+%% and Key values in Lookup
+maybe_fetch_value(Key, Dict) ->
+ maybe_fetch_value(maps:is_key(Key, Dict), go, Key, Dict).
+
+maybe_fetch_value(true, _Cont, Key, Dict) ->
+ maps:get(Key, Dict);
+maybe_fetch_value(false, stop, _Key, _Dict) ->
+ [];
+maybe_fetch_value(false, go, Key, Dict) ->
+ Key2 = reverse_key(Key),
+ maybe_fetch_value(maps:is_key(Key2, Dict), stop, Key2, Dict).
+
+message_stats([]) ->
+ [];
+message_stats(Stats) ->
+ [{message_stats, Stats}].
+
+pick_range(Table, Ranges) ->
+ rabbit_mgmt_data:pick_range(Table, Ranges).
+
+first(Id) ->
+ {Id, '_'}.
+
+second(Id) ->
+ {'_', Id}.
+
+reverse_key({K1, K2}) ->
+ {K2, K1}.
+
+augment_details(Obj, Acc) ->
+ rabbit_mgmt_data:augment_details(Obj, Acc).
+
+format_detail_id(ChPid) when is_pid(ChPid) ->
+ augment_details([{channel, ChPid}], []);
+format_detail_id(#resource{name = Name, virtual_host = Vhost, kind = Kind}) ->
+ [{Kind, [{name, Name}, {vhost, Vhost}]}];
+format_detail_id(Node) when is_atom(Node) ->
+ [{name, Node}].
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_db_cache.erl b/deps/rabbitmq_management/src/rabbit_mgmt_db_cache.erl
new file mode 100644
index 0000000000..b4d92dd543
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_db_cache.erl
@@ -0,0 +1,143 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+-module(rabbit_mgmt_db_cache).
+
+-behaviour(gen_server).
+
+%% API functions
+-export([start_link/1]).
+-export([process_name/1,
+ fetch/2,
+ fetch/3,
+ fetch/4]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+-record(state, {data :: any() | none,
+ args :: [any()],
+ timer_ref :: undefined | reference(),
+ multiplier :: integer()}).
+
+-type error_desc() :: key_not_found | timeout | {throw, atom()}.
+-type fetch_fun() :: fun((_) -> any()) | fun(() -> any()).
+-type fetch_ret() :: {ok, any()} | {error, error_desc()}.
+
+-define(DEFAULT_MULT, 5).
+-define(DEFAULT_TIMEOUT, 60000).
+-define(CHILD(Key), {rabbit_mgmt_db_cache:process_name(Key),
+ {rabbit_mgmt_db_cache, start_link, [Key]},
+ permanent, 5000, worker,
+ [rabbit_mgmt_db_cache]}).
+-define(RESET_STATE(State), State#state{data = none, args = []}).
+
+%% Implements an adaptive cache that times the value generating fun
+%% and uses the return value as the cached value for the time it took
+%% to produce multiplied by some factor (defaults to 5).
+%% There is one cache process per key. New processes are started as
+%% required. The cache is invalidated if the arguments to the fetch
+%% fun have changed.
+
+
+%%%===================================================================
+%%% API functions
+%%%===================================================================
+
+-spec fetch(atom(), fetch_fun()) -> fetch_ret().
+fetch(Key, FetchFun) ->
+ fetch(Key, FetchFun, []).
+
+-spec fetch(atom(), fetch_fun(), [any()]) -> fetch_ret().
+fetch(Key, FetchFun, Args) when is_list(Args) ->
+ fetch(Key, FetchFun, Args, ?DEFAULT_TIMEOUT).
+
+-spec fetch(atom(), fetch_fun(), [any()], integer()) -> fetch_ret().
+fetch(Key, FetchFun, FunArgs, Timeout) ->
+ ProcName = process_name(Key),
+ Pid = case whereis(ProcName) of
+ undefined ->
+ {ok, P} = supervisor:start_child(rabbit_mgmt_db_cache_sup,
+ ?CHILD(Key)),
+ P;
+ P -> P
+ end,
+ gen_server:call(Pid, {fetch, FetchFun, FunArgs}, Timeout).
+
+
+-spec process_name(atom()) -> atom().
+process_name(Key) ->
+ list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(Key)).
+
+-spec start_link(atom()) -> {ok, pid()} | ignore | {error, any()}.
+start_link(Key) ->
+ gen_server:start_link({local, process_name(Key)}, ?MODULE, [], []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+init([]) ->
+ Mult = application:get_env(rabbitmq_management, management_db_cache_multiplier,
+ ?DEFAULT_MULT),
+ {ok, #state{data = none,
+ args = [],
+ multiplier = Mult}}.
+
+handle_call({fetch, _FetchFun, FunArgs} = Msg, From,
+ #state{data = CachedData, args = Args} = State) when
+ CachedData =/= none andalso Args =/= FunArgs ->
+ %% there is cached data that needs to be invalidated
+ handle_call(Msg, From, ?RESET_STATE(State));
+handle_call({fetch, FetchFun, FunArgs}, _From,
+ #state{data = none,
+ multiplier = Mult, timer_ref = Ref} = State) ->
+ %% force a gc here to clean up previously cleared data
+ garbage_collect(),
+ case Ref of
+ R when is_reference(R) ->
+ _ = erlang:cancel_timer(R);
+ _ -> ok
+ end,
+
+ try timer:tc(FetchFun, FunArgs) of
+ {Time, Data} ->
+ case trunc(Time / 1000 * Mult) of
+ 0 -> {reply, {ok, Data}, ?RESET_STATE(State)}; % no need to cache that
+ T ->
+ TimerRef = erlang:send_after(T, self(), purge_cache),
+ {reply, {ok, Data}, State#state{data = Data,
+ timer_ref = TimerRef,
+ args = FunArgs}}
+ end
+ catch
+ Throw -> {reply, {error, {throw, Throw}}, State}
+ end;
+handle_call({fetch, _FetchFun, _}, _From, #state{data = Data} = State) ->
+ Reply = {ok, Data},
+ {reply, Reply, State};
+handle_call(purge_cache, _From, State) ->
+ {reply, ok, ?RESET_STATE(State), hibernate}.
+
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(purge_cache, State) ->
+ {noreply, ?RESET_STATE(State), hibernate};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_db_cache_sup.erl b/deps/rabbitmq_management/src/rabbit_mgmt_db_cache_sup.erl
new file mode 100644
index 0000000000..1805f25872
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_db_cache_sup.erl
@@ -0,0 +1,29 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+-module(rabbit_mgmt_db_cache_sup).
+
+-behaviour(supervisor).
+
+%% API functions
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%%%===================================================================
+%%% API functions
+%%%===================================================================
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%%===================================================================
+%%% Supervisor callbacks
+%%%===================================================================
+
+init([]) ->
+ {ok, {{one_for_one, 5, 10}, []}}.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl b/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl
new file mode 100644
index 0000000000..e0de5c8bce
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl
@@ -0,0 +1,185 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_dispatcher).
+
+-export([modules/1, build_dispatcher/1]).
+
+-behaviour(rabbit_mgmt_extension).
+-export([dispatcher/0, web_ui/0]).
+
+build_dispatcher(Ignore) ->
+ Routes = build_routes(Ignore),
+ cowboy_router:compile(Routes).
+
+build_routes(Ignore) ->
+ ManagementApp = module_app(?MODULE),
+ Prefix = rabbit_mgmt_util:get_path_prefix(),
+ RootIdxRtes = build_root_index_routes(Prefix, ManagementApp),
+ ApiRdrRte = build_redirect_route("/api", Prefix ++ "/api/index.html"),
+ CliRdrRte = build_redirect_route("/cli", Prefix ++ "/cli/index.html"),
+ StatsRdrRte1 = build_redirect_route("/stats", Prefix ++ "/api/index.html"),
+ StatsRdrRte2 = build_redirect_route("/doc/stats.html", Prefix ++ "/api/index.html"),
+ MgmtRdrRte = {"/mgmt", rabbit_mgmt_wm_redirect, "/"},
+ LocalPaths = [{module_app(M), "www"} || M <- modules(Ignore)],
+ LocalStaticRte = {"/[...]", rabbit_mgmt_wm_static, LocalPaths},
+ % NB: order is significant in the routing list
+ Routes0 = build_module_routes(Ignore) ++
+ [ApiRdrRte, CliRdrRte, MgmtRdrRte, StatsRdrRte1, StatsRdrRte2, LocalStaticRte],
+ Routes1 = maybe_add_path_prefix(Routes0, Prefix),
+ % NB: ensure the root routes are first
+ Routes2 = RootIdxRtes ++ Routes1,
+ [{'_', Routes2}].
+
+build_root_index_routes("", ManagementApp) ->
+ [{"/", rabbit_mgmt_wm_static, root_idx_file(ManagementApp)}];
+build_root_index_routes(Prefix, ManagementApp) ->
+ [{"/", rabbit_mgmt_wm_redirect, Prefix ++ "/"},
+ {Prefix, rabbit_mgmt_wm_static, root_idx_file(ManagementApp)}].
+
+build_redirect_route(Path, Location) ->
+ {Path, rabbit_mgmt_wm_redirect, Location}.
+
+root_idx_file(ManagementApp) ->
+ {priv_file, ManagementApp, "www/index.html"}.
+
+maybe_add_path_prefix(Routes, "") ->
+ Routes;
+maybe_add_path_prefix(Routes, Prefix) ->
+ [{Prefix ++ Path, Mod, Args} || {Path, Mod, Args} <- Routes].
+
+build_module_routes(Ignore) ->
+ Routes = [Module:dispatcher() || Module <- modules(Ignore)],
+ [{"/api" ++ Path, Mod, Args} || {Path, Mod, Args} <- lists:append(Routes)].
+
+modules(IgnoreApps) ->
+ [Module || {App, Module, Behaviours} <-
+ %% Sort rabbitmq_management modules first. This is
+ %% a microoptimization because most files belong to
+ %% this application. Making it first avoids several
+ %% stats(2) which have a great chance of failing in other
+ %% applications.
+ lists:sort(
+ fun
+ ({rabbitmq_management, _, _}, _) -> true;
+ (_, {rabbitmq_management, _, _}) -> false;
+ ({A, _, _}, {B, _, _}) -> A =< B
+ end,
+ rabbit_misc:all_module_attributes(behaviour)),
+ not lists:member(App, IgnoreApps),
+ lists:member(rabbit_mgmt_extension, Behaviours)].
+
+module_app(Module) ->
+ {ok, App} = application:get_application(Module),
+ App.
+
+%%----------------------------------------------------------------------------
+
+web_ui() -> [{javascript, <<"dispatcher.js">>}].
+
+dispatcher() ->
+ [{"/overview", rabbit_mgmt_wm_overview, []},
+ {"/cluster-name", rabbit_mgmt_wm_cluster_name, []},
+ {"/nodes", rabbit_mgmt_wm_nodes, []},
+ {"/nodes/:node", rabbit_mgmt_wm_node, []},
+ {"/nodes/:node/memory", rabbit_mgmt_wm_node_memory, [absolute]},
+ {"/nodes/:node/memory/relative", rabbit_mgmt_wm_node_memory, [relative]},
+ {"/nodes/:node/memory/ets", rabbit_mgmt_wm_node_memory_ets, [absolute]},
+ {"/nodes/:node/memory/ets/relative", rabbit_mgmt_wm_node_memory_ets, [relative]},
+ {"/nodes/:node/memory/ets/:filter", rabbit_mgmt_wm_node_memory_ets, [absolute]},
+ {"/nodes/:node/memory/ets/:filter/relative", rabbit_mgmt_wm_node_memory_ets, [relative]},
+ {"/extensions", rabbit_mgmt_wm_extensions, []},
+ {"/all-configuration", rabbit_mgmt_wm_definitions, []}, %% This was the old name, let's not break things gratuitously.
+ {"/definitions", rabbit_mgmt_wm_definitions, []},
+ {"/definitions/:vhost", rabbit_mgmt_wm_definitions, []},
+ {"/parameters", rabbit_mgmt_wm_parameters, []},
+ {"/parameters/:component", rabbit_mgmt_wm_parameters, []},
+ {"/parameters/:component/:vhost", rabbit_mgmt_wm_parameters, []},
+ {"/parameters/:component/:vhost/:name", rabbit_mgmt_wm_parameter, []},
+ {"/global-parameters", rabbit_mgmt_wm_global_parameters, []},
+ {"/global-parameters/:name", rabbit_mgmt_wm_global_parameter, []},
+ {"/policies", rabbit_mgmt_wm_policies, []},
+ {"/policies/:vhost", rabbit_mgmt_wm_policies, []},
+ {"/policies/:vhost/:name", rabbit_mgmt_wm_policy, []},
+ {"/operator-policies", rabbit_mgmt_wm_operator_policies, []},
+ {"/operator-policies/:vhost", rabbit_mgmt_wm_operator_policies, []},
+ {"/operator-policies", rabbit_mgmt_wm_operator_policies, []},
+ {"/operator-policies/:vhost/:name", rabbit_mgmt_wm_operator_policy, []},
+ {"/vhost-limits/:vhost/:name", rabbit_mgmt_wm_limit, []},
+ {"/vhost-limits", rabbit_mgmt_wm_limits, []},
+ {"/vhost-limits/:vhost", rabbit_mgmt_wm_limits, []},
+ {"/connections", rabbit_mgmt_wm_connections, []},
+ {"/connections/:connection", rabbit_mgmt_wm_connection, []},
+ {"/connections/:connection/channels", rabbit_mgmt_wm_connection_channels, []},
+ {"/channels", rabbit_mgmt_wm_channels, []},
+ {"/channels/:channel", rabbit_mgmt_wm_channel, []},
+ {"/consumers", rabbit_mgmt_wm_consumers, []},
+ {"/consumers/:vhost", rabbit_mgmt_wm_consumers, []},
+ {"/exchanges", rabbit_mgmt_wm_exchanges, []},
+ {"/exchanges/:vhost", rabbit_mgmt_wm_exchanges, []},
+ {"/exchanges/:vhost/:exchange", rabbit_mgmt_wm_exchange, []},
+ {"/exchanges/:vhost/:exchange/publish", rabbit_mgmt_wm_exchange_publish, []},
+ {"/exchanges/:vhost/:exchange/bindings/source", rabbit_mgmt_wm_bindings, [exchange_source]},
+ {"/exchanges/:vhost/:exchange/bindings/destination", rabbit_mgmt_wm_bindings, [exchange_destination]},
+ {"/queues", rabbit_mgmt_wm_queues, []},
+ {"/queues/:vhost", rabbit_mgmt_wm_queues, []},
+ {"/queues/:vhost/:queue", rabbit_mgmt_wm_queue, []},
+ {"/queues/:vhost/:destination/bindings", rabbit_mgmt_wm_bindings, [queue]},
+ {"/queues/:vhost/:queue/contents", rabbit_mgmt_wm_queue_purge, []},
+ {"/queues/:vhost/:queue/get", rabbit_mgmt_wm_queue_get, []},
+ {"/queues/:vhost/:queue/actions", rabbit_mgmt_wm_queue_actions, []},
+ {"/bindings", rabbit_mgmt_wm_bindings, [all]},
+ {"/bindings/:vhost", rabbit_mgmt_wm_bindings, [all]},
+ {"/bindings/:vhost/e/:source/:dtype/:destination", rabbit_mgmt_wm_bindings, [source_destination]},
+ {"/bindings/:vhost/e/:source/:dtype/:destination/:props", rabbit_mgmt_wm_binding, []},
+ {"/vhosts", rabbit_mgmt_wm_vhosts, []},
+ {"/vhosts/:vhost", rabbit_mgmt_wm_vhost, []},
+ {"/vhosts/:vhost/start/:node", rabbit_mgmt_wm_vhost_restart, []},
+ {"/vhosts/:vhost/permissions", rabbit_mgmt_wm_permissions_vhost, []},
+ {"/vhosts/:vhost/topic-permissions", rabbit_mgmt_wm_topic_permissions_vhost, []},
+ %% /connections/:connection is already taken, we cannot use our standard scheme here
+ {"/vhosts/:vhost/connections", rabbit_mgmt_wm_connections_vhost, []},
+ %% /channels/:channel is already taken, we cannot use our standard scheme here
+ {"/vhosts/:vhost/channels", rabbit_mgmt_wm_channels_vhost, []},
+ {"/users/bulk-delete", rabbit_mgmt_wm_users_bulk_delete, []},
+ {"/users/without-permissions", rabbit_mgmt_wm_users, [without_permissions]},
+ {"/users", rabbit_mgmt_wm_users, [all]},
+ {"/users/:user", rabbit_mgmt_wm_user, []},
+ {"/users/:user/permissions", rabbit_mgmt_wm_permissions_user, []},
+ {"/users/:user/topic-permissions", rabbit_mgmt_wm_topic_permissions_user, []},
+ {"/user-limits/:user/:name", rabbit_mgmt_wm_user_limit, []},
+ {"/user-limits", rabbit_mgmt_wm_user_limits, []},
+ {"/user-limits/:user", rabbit_mgmt_wm_user_limits, []},
+ {"/feature-flags", rabbit_mgmt_wm_feature_flags, []},
+ {"/feature-flags/:name/enable", rabbit_mgmt_wm_feature_flag_enable, []},
+ {"/whoami", rabbit_mgmt_wm_whoami, []},
+ {"/permissions", rabbit_mgmt_wm_permissions, []},
+ {"/permissions/:vhost/:user", rabbit_mgmt_wm_permission, []},
+ {"/topic-permissions", rabbit_mgmt_wm_topic_permissions, []},
+ {"/topic-permissions/:vhost/:user", rabbit_mgmt_wm_topic_permission, []},
+ {"/topic-permissions/:vhost/:user/:exchange", rabbit_mgmt_wm_topic_permission, []},
+ {"/aliveness-test/:vhost", rabbit_mgmt_wm_aliveness_test, []},
+ %% now deprecated
+ {"/healthchecks/node", rabbit_mgmt_wm_healthchecks, []},
+ {"/healthchecks/node/:node", rabbit_mgmt_wm_healthchecks, []},
+ %% modern generation of fine-grained health checks
+ {"/health/checks/alarms", rabbit_mgmt_wm_health_check_alarms, []},
+ {"/health/checks/local-alarms", rabbit_mgmt_wm_health_check_local_alarms, []},
+ {"/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/virtual-hosts", rabbit_mgmt_wm_health_check_virtual_hosts, []},
+ {"/health/checks/node-is-mirror-sync-critical", rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical, []},
+ {"/health/checks/node-is-quorum-critical", rabbit_mgmt_wm_health_check_node_is_quorum_critical, []},
+ {"/reset", rabbit_mgmt_wm_reset, []},
+ {"/reset/:node", rabbit_mgmt_wm_reset, []},
+ {"/rebalance/queues", rabbit_mgmt_wm_rebalance_queues, [{queues, all}]},
+ {"/auth", rabbit_mgmt_wm_auth, []},
+ {"/auth/attempts/:node", rabbit_mgmt_wm_auth_attempts, [all]},
+ {"/auth/attempts/:node/source", rabbit_mgmt_wm_auth_attempts, [by_source]},
+ {"/login", rabbit_mgmt_wm_login, []}
+ ].
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_extension.erl b/deps/rabbitmq_management/src/rabbit_mgmt_extension.erl
new file mode 100644
index 0000000000..1ee72aaf36
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_extension.erl
@@ -0,0 +1,16 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_extension).
+
+%% Return a Cowboy dispatcher table to integrate
+-callback dispatcher() -> [{string(), atom(), [atom()]}].
+
+%% Return a proplist of information for the web UI to integrate
+%% this extension. Currently the proplist should have one key,
+%% 'javascript', the name of a javascript file to load and run.
+-callback web_ui() -> proplists:proplist().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_headers.erl b/deps/rabbitmq_management/src/rabbit_mgmt_headers.erl
new file mode 100644
index 0000000000..89bb464c01
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_headers.erl
@@ -0,0 +1,34 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% This module contains helper functions that control
+%% response headers related to CORS, CSP, HSTS and so on.
+-module(rabbit_mgmt_headers).
+
+-export([set_common_permission_headers/2]).
+-export([set_cors_headers/2, set_hsts_headers/2, set_csp_headers/2]).
+
+%%
+%% API
+%%
+
+set_cors_headers(ReqData, Module) ->
+ rabbit_mgmt_cors:set_headers(ReqData, Module).
+
+set_hsts_headers(ReqData, _Module) ->
+ rabbit_mgmt_hsts:set_headers(ReqData).
+
+set_csp_headers(ReqData, _Module) ->
+ rabbit_mgmt_csp:set_headers(ReqData).
+
+set_common_permission_headers(ReqData0, EndpointModule) ->
+ lists:foldl(fun(Fun, ReqData) ->
+ Fun(ReqData, EndpointModule)
+ end, ReqData0,
+ [fun set_csp_headers/2,
+ fun set_hsts_headers/2,
+ fun set_cors_headers/2]).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_hsts.erl b/deps/rabbitmq_management/src/rabbit_mgmt_hsts.erl
new file mode 100644
index 0000000000..004f3d4e7e
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_hsts.erl
@@ -0,0 +1,28 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% Sets HSTS header(s) on the response if configured,
+%% see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security.
+
+-module(rabbit_mgmt_hsts).
+
+-export([set_headers/1]).
+
+-define(HSTS_HEADER, <<"strict-transport-security">>).
+
+%%
+%% API
+%%
+
+set_headers(ReqData) ->
+ case application:get_env(rabbitmq_management, strict_transport_security) of
+ undefined -> ReqData;
+ {ok, Value} ->
+ cowboy_req:set_resp_header(?HSTS_HEADER,
+ rabbit_data_coercion:to_binary(Value),
+ ReqData)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_load_definitions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_load_definitions.erl
new file mode 100644
index 0000000000..ba9962d6cf
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_load_definitions.erl
@@ -0,0 +1,27 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_load_definitions).
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-export([boot/0, maybe_load_definitions/0, maybe_load_definitions_from/2]).
+
+%% This module exists for backwards compatibility only.
+%% Definition import functionality is now a core server feature.
+
+boot() ->
+ rabbit_log:debug("Will import definitions file from management.load_definitions"),
+ rabbit_definitions:maybe_load_definitions(rabbitmq_management, load_definitions).
+
+maybe_load_definitions() ->
+ rabbit_definitions:maybe_load_definitions().
+
+maybe_load_definitions_from(true, Dir) ->
+ rabbit_definitions:maybe_load_definitions_from(true, Dir);
+maybe_load_definitions_from(false, File) ->
+ rabbit_definitions:maybe_load_definitions_from(false, File).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_reset_handler.erl b/deps/rabbitmq_management/src/rabbit_mgmt_reset_handler.erl
new file mode 100644
index 0000000000..4eb0219e46
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_reset_handler.erl
@@ -0,0 +1,79 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% When management extensions are enabled and/or disabled at runtime, the
+%% management web dispatch mechanism needs to be reset. This event handler
+%% deals with responding to 'plugins_changed' events for management
+%% extensions, forcing a reset when necessary.
+
+-module(rabbit_mgmt_reset_handler).
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-rabbit_boot_step({?MODULE,
+ [{description, "management extension handling"},
+ {mfa, {gen_event, add_handler,
+ [rabbit_event, ?MODULE, []]}},
+ {cleanup, {gen_event, delete_handler,
+ [rabbit_event, ?MODULE, []]}},
+ {requires, rabbit_event},
+ {enables, recovery}]}).
+
+-import(rabbit_misc, [pget/2, pget/3]).
+
+%%----------------------------------------------------------------------------
+
+init([]) ->
+ {ok, []}.
+
+handle_call(_Request, State) ->
+ {ok, not_understood, State}.
+
+handle_event(#event{type = plugins_changed, props = Details}, State) ->
+ Enabled = pget(enabled, Details),
+ Disabled = pget(disabled, Details),
+ case extensions_changed(Enabled ++ Disabled) of
+ true ->
+ _ = rabbit_mgmt_app:reset_dispatcher(Disabled),
+ ok;
+ false -> ok
+ end,
+ {ok, State};
+
+handle_event(_Event, State) ->
+ {ok, State}.
+
+handle_info(_Info, State) ->
+ {ok, State}.
+
+terminate(_Arg, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%----------------------------------------------------------------------------
+
+%% We explicitly ignore the case where management has been
+%% started/stopped since the dispatcher is either freshly created or
+%% about to vanish.
+extensions_changed(Apps) ->
+ not lists:member(rabbitmq_management, Apps) andalso
+ lists:any(fun is_extension/1, [Mod || App <- Apps, Mod <- mods(App)]).
+
+is_extension(Mod) ->
+ lists:member(rabbit_mgmt_extension,
+ pget(behaviour, Mod:module_info(attributes), [])).
+
+mods(App) ->
+ {ok, Modules} = application:get_key(App, modules),
+ Modules.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_stats.erl b/deps/rabbitmq_management/src/rabbit_mgmt_stats.erl
new file mode 100644
index 0000000000..14b3432790
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_stats.erl
@@ -0,0 +1,739 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2010-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_stats).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_metrics.hrl").
+
+-export([format_range/6]).
+
+-define(MICRO_TO_MILLI, 1000).
+
+-type maybe_range() :: no_range | #range{}.
+-type maybe_slide() :: exometer_slide:slide() | not_found.
+-type maybe_slide_fun() :: fun(() -> [maybe_slide()]).
+
+-export_type([maybe_range/0, maybe_slide/0, maybe_slide_fun/0]).
+
+
+%%----------------------------------------------------------------------------
+%% Query-time
+%%----------------------------------------------------------------------------
+
+-spec format_range(maybe_range(), exometer_slide:timestamp(), atom(),
+ non_neg_integer(), maybe_slide_fun(), maybe_slide_fun()) ->
+ proplists:proplist().
+format_range(no_range, Now, Table, Interval, InstantRateFun, _SamplesFun) ->
+ format_no_range(Table, Now, Interval, InstantRateFun);
+format_range(#range{last = Last, first = First, incr = Incr}, _Now, Table,
+ Interval, _InstantRateFun, SamplesFun) ->
+ case SamplesFun() of
+ [] ->
+ [];
+ Slides ->
+ Empty = empty(Table, 0),
+ Slide = exometer_slide:sum(Last, First, Incr, Slides, Empty),
+ List = exometer_slide:to_list(Last, First, Slide),
+ Length = length(List),
+ RangePoint = Last - Interval,
+ LastTwo = get_last_two(List, Length),
+ {Total, Rate} = calculate_rate(LastTwo, Table, RangePoint),
+ {Samples, SampleTotals} = format_samples(List, empty(Table, []),
+ Empty),
+ format_rate(Table, Total, Rate, Samples, SampleTotals, Length)
+ end.
+
+-spec format_no_range(atom(), exometer_slide:timestamp(), non_neg_integer(),
+ maybe_slide_fun()) -> proplists:proplist().
+format_no_range(Table, Now, Interval, InstantRateFun) ->
+ RangePoint = ((Now div Interval) * Interval) - Interval,
+ case calculate_instant_rate(InstantRateFun, Table, RangePoint) of
+ {Total, Rate} -> format_rate(Table, Total, Rate);
+ not_found -> []
+ end.
+
+-spec calculate_instant_rate(maybe_slide_fun(), atom(), integer()) ->
+ 'not_found' | {integer(), number()} | {tuple(), tuple()}.
+calculate_instant_rate(Fun, Table, RangePoint) ->
+ case Fun() of
+ [] ->
+ not_found;
+ Slides ->
+ Slide = exometer_slide:sum(Slides),
+ case exometer_slide:last_two(Slide) of
+ [] -> {empty(Table, 0), empty(Table, 0.0)};
+ [Last | T] ->
+ Total = get_total(Slide, Table),
+ Rate = rate_from_last_increment(Table, Last, T, RangePoint),
+ {Total, Rate}
+ end
+ end.
+
+calculate_rate([], Table, _RangePoint) ->
+ {empty(Table, 0), empty(Table, 0.0)};
+calculate_rate([{_, Total} = Last | T], Table, RangePoint) ->
+ Rate = rate_from_last_increment(Table, Last, T, RangePoint),
+ {Total, Rate}.
+
+get_last_two(List, Length) when Length =< 2 ->
+ lists:reverse(List);
+get_last_two(List, Length) ->
+ lists:reverse(lists:nthtail(Length - 2, List)).
+
+get_total(Slide, Table) ->
+ case exometer_slide:last(Slide) of
+ undefined -> empty(Table, 0);
+ Other -> Other
+ end.
+
+format_samples(Samples, ESamples, ETotal) ->
+ lists:foldl(fun({TS, Sample}, {SamplesAcc, TotalsAcc}) ->
+ append_full_sample(TS, Sample, SamplesAcc, TotalsAcc)
+ end, {ESamples, ETotal}, Samples).
+
+%% Ts for "totals", Ss for "samples", Vs for "values"
+%%
+%% connection_stats_coarse_conn_stats, channel_stats_fine_stats,
+%% vhost_stats_fine_stats, queue_msg_stats, vhost_msg_stats
+append_full_sample(TS, {V1, V2, V3}, {S1, S2, S3}, {T1, T2, T3}) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2), append_sample(V3, TS, S3)},
+ {V1 + T1, V2 + T2, V3 + T3}};
+%% channel_exchange_stats_fine_stats
+append_full_sample(TS, {V1, V2, V3, V4}, {S1, S2, S3, S4}, {T1, T2, T3, T4}) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2), append_sample(V3, TS, S3),
+ append_sample(V4, TS, S4)},
+ {V1 + T1, V2 + T2, V3 + T3, V4 + T4}};
+%% connection_churn_rates
+append_full_sample(TS, {V1, V2, V3, V4, V5, V6, V7},
+ {S1, S2, S3, S4, S5, S6, S7},
+ {T1, T2, T3, T4, T5, T6, T7}) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2),
+ append_sample(V3, TS, S3), append_sample(V4, TS, S4),
+ append_sample(V5, TS, S5), append_sample(V6, TS, S6),
+ append_sample(V7, TS, S7)},
+ {V1 + T1, V2 + T2, V3 + T3, V4 + T4, V5 + T5, V6 + T6, V7 + T7}};
+%% channel_queue_stats_deliver_stats, queue_stats_deliver_stats,
+%% vhost_stats_deliver_stats, channel_stats_deliver_stats
+%% node_coarse_stats
+append_full_sample(TS, {V1, V2, V3, V4, V5, V6, V7, V8},
+ {S1, S2, S3, S4, S5, S6, S7, S8},
+ {T1, T2, T3, T4, T5, T6, T7, T8}) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2),
+ append_sample(V3, TS, S3), append_sample(V4, TS, S4),
+ append_sample(V5, TS, S5), append_sample(V6, TS, S6),
+ append_sample(V7, TS, S7), append_sample(V8, TS, S8)},
+ {V1 + T1, V2 + T2, V3 + T3, V4 + T4, V5 + T5, V6 + T6, V7 + T7, V8 + T8}};
+%% channel_process_stats, queue_stats_publish, queue_exchange_stats_publish,
+%% exchange_stats_publish_out, exchange_stats_publish_in, queue_process_stats
+append_full_sample(TS, {V1}, {S1}, {T1}) ->
+ {{append_sample(V1, TS, S1)}, {V1 + T1}};
+%% node_persister_stats
+append_full_sample(TS,
+ {V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14,
+ V15, V16, V17, V18, V19, V20},
+ {S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14,
+ S15, S16, S17, S18, S19, S20},
+ {T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
+ T15, T16, T17, T18, T19, T20}
+ ) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2),
+ append_sample(V3, TS, S3), append_sample(V4, TS, S4),
+ append_sample(V5, TS, S5), append_sample(V6, TS, S6),
+ append_sample(V7, TS, S7), append_sample(V8, TS, S8),
+ append_sample(V9, TS, S9), append_sample(V10, TS, S10),
+ append_sample(V11, TS, S11), append_sample(V12, TS, S12),
+ append_sample(V13, TS, S13), append_sample(V14, TS, S14),
+ append_sample(V15, TS, S15), append_sample(V16, TS, S16),
+ append_sample(V17, TS, S17), append_sample(V18, TS, S18),
+ append_sample(V19, TS, S19), append_sample(V20, TS, S20)},
+ {V1 + T1, V2 + T2, V3 + T3, V4 + T4, V5 + T5, V6 + T6, V7 + T7, V8 + T8,
+ V9 + T9, V10 + T10, V11 + T11, V12 + T12, V13 + T13, V14 + T14, V15 + T15,
+ V16 + T16, V17 + T17, V18 + T18, V19 + T19, V20 + T20}};
+%% node_node_coarse_stats, vhost_stats_coarse_connection_stats, queue_msg_rates,
+%% vhost_msg_rates
+append_full_sample(TS, {V1, V2}, {S1, S2}, {T1, T2}) ->
+ {{append_sample(V1, TS, S1), append_sample(V2, TS, S2)}, {V1 + T1, V2 + T2}}.
+
+
+format_rate(connection_stats_coarse_conn_stats, {TR, TS, TRe}, {RR, RS, RRe}) ->
+ [
+ {send_oct, TS},
+ {send_oct_details, [{rate, RS}]},
+ {recv_oct, TR},
+ {recv_oct_details, [{rate, RR}]},
+ {reductions, TRe},
+ {reductions_details, [{rate, RRe}]}
+ ];
+format_rate(vhost_stats_coarse_conn_stats, {TR, TS}, {RR, RS}) ->
+ [
+ {send_oct, TS},
+ {send_oct_details, [{rate, RS}]},
+ {recv_oct, TR},
+ {recv_oct_details, [{rate, RR}]}
+ ];
+format_rate(Type, {TR, TW}, {RR, RW}) when Type =:= vhost_msg_rates;
+ Type =:= queue_msg_rates ->
+ [
+ {disk_reads, TR},
+ {disk_reads_details, [{rate, RR}]},
+ {disk_writes, TW},
+ {disk_writes_details, [{rate, RW}]}
+ ];
+format_rate(Type, {TP, TC, TRe, TDrop}, {RP, RC, RRe, RDrop})
+ when Type =:= channel_stats_fine_stats;
+ Type =:= vhost_stats_fine_stats;
+ Type =:= channel_exchange_stats_fine_stats ->
+ [
+ {publish, TP},
+ {publish_details, [{rate, RP}]},
+ {confirm, TC},
+ {confirm_details, [{rate, RC}]},
+ {return_unroutable, TRe},
+ {return_unroutable_details, [{rate, RRe}]},
+ {drop_unroutable, TDrop},
+ {drop_unroutable_details, [{rate, RDrop}]}
+ ];
+format_rate(Type, {TG, TGN, TD, TDN, TR, TA, TDG, TGE},
+ {RG, RGN, RD, RDN, RR, RA, RDG, RGE})
+ when Type =:= channel_queue_stats_deliver_stats;
+ Type =:= channel_stats_deliver_stats;
+ Type =:= vhost_stats_deliver_stats;
+ Type =:= queue_stats_deliver_stats ->
+ [
+ {get, TG},
+ {get_details, [{rate, RG}]},
+ {get_no_ack, TGN},
+ {get_no_ack_details, [{rate, RGN}]},
+ {deliver, TD},
+ {deliver_details, [{rate, RD}]},
+ {deliver_no_ack, TDN},
+ {deliver_no_ack_details, [{rate, RDN}]},
+ {redeliver, TR},
+ {redeliver_details, [{rate, RR}]},
+ {ack, TA},
+ {ack_details, [{rate, RA}]},
+ {deliver_get, TDG},
+ {deliver_get_details, [{rate, RDG}]},
+ {get_empty, TGE},
+ {get_empty_details, [{rate, RGE}]}
+ ];
+format_rate(Type, {TR}, {RR}) when Type =:= channel_process_stats;
+ Type =:= queue_process_stats ->
+ [
+ {reductions, TR},
+ {reductions_details, [{rate, RR}]}
+ ];
+format_rate(exchange_stats_publish_out, {TP}, {RP}) ->
+ [
+ {publish_out, TP},
+ {publish_out_details, [{rate, RP}]}
+ ];
+format_rate(exchange_stats_publish_in, {TP}, {RP}) ->
+ [
+ {publish_in, TP},
+ {publish_in_details, [{rate, RP}]}
+ ];
+format_rate(Type, {TP}, {RP}) when Type =:= queue_stats_publish;
+ Type =:= queue_exchange_stats_publish ->
+ [
+ {publish, TP},
+ {publish_details, [{rate, RP}]}
+ ];
+format_rate(Type, {TR, TU, TM}, {RR, RU, RM}) when Type =:= queue_msg_stats;
+ Type =:= vhost_msg_stats ->
+ [
+ {messages_ready, TR},
+ {messages_ready_details, [{rate, RR}]},
+ {messages_unacknowledged, TU},
+ {messages_unacknowledged_details, [{rate, RU}]},
+ {messages, TM},
+ {messages_details, [{rate, RM}]}
+ ];
+format_rate(node_coarse_stats, {TF, TS, TM, TD, TP, TGC, TGCW, TCS},
+ {RF, RS, RM, RD, RP, RGC, RGCW, RCS}) ->
+ [
+ {mem_used, TM},
+ {mem_used_details, [{rate, RM}]},
+ {fd_used, TF},
+ {fd_used_details, [{rate, RF}]},
+ {sockets_used, TS},
+ {sockets_used_details, [{rate, RS}]},
+ {proc_used, TP},
+ {proc_used_details, [{rate, RP}]},
+ {disk_free, TD},
+ {disk_free_details, [{rate, RD}]},
+ {gc_num, TGC},
+ {gc_num_details, [{rate, RGC}]},
+ {gc_bytes_reclaimed, TGCW},
+ {gc_bytes_reclaimed_details, [{rate, RGCW}]},
+ {context_switches, TCS},
+ {context_switches_details, [{rate, RCS}]}
+ ];
+format_rate(node_persister_stats,
+ {TIR, TIB, TIA, TIWC, TIWB, TIWAT, TIS, TISAT, TISC,
+ TISEAT, TIRC, TMRTC, TMDTC, TMSRC, TMSWC, TQIJWC, TQIWC, TQIRC,
+ TIO, TIOAT},
+ {RIR, RIB, RIA, RIWC, RIWB, RIWAT, RIS, RISAT, RISC,
+ RISEAT, RIRC, RMRTC, RMDTC, RMSRC, RMSWC, RQIJWC, RQIWC, RQIRC,
+ RIO, RIOAT}) ->
+ %% Calculates average times for read/write/sync/seek from the
+ %% accumulated time and count
+ %% io_<op>_avg_time is the average operation time for the life of the node
+ %% io_<op>_avg_time_details/rate is the average operation time during the
+ %% last time unit calculated (thus similar to an instant rate)
+ [
+ {io_read_count, TIR},
+ {io_read_count_details, [{rate, RIR}]},
+ {io_read_bytes, TIB},
+ {io_read_bytes_details, [{rate, RIB}]},
+ {io_read_avg_time, avg_time(TIA, TIR)},
+ {io_read_avg_time_details, [{rate, avg_time(RIA, RIR)}]},
+ {io_write_count, TIWC},
+ {io_write_count_details, [{rate, RIWC}]},
+ {io_write_bytes, TIWB},
+ {io_write_bytes_details, [{rate, RIWB}]},
+ {io_write_avg_time, avg_time(TIWAT, TIWC)},
+ {io_write_avg_time_details, [{rate, avg_time(RIWAT, RIWC)}]},
+ {io_sync_count, TIS},
+ {io_sync_count_details, [{rate, RIS}]},
+ {io_sync_avg_time, avg_time(TISAT, TIS)},
+ {io_sync_avg_time_details, [{rate, avg_time(RISAT, RIS)}]},
+ {io_seek_count, TISC},
+ {io_seek_count_details, [{rate, RISC}]},
+ {io_seek_avg_time, avg_time(TISEAT, TISC)},
+ {io_seek_avg_time_details, [{rate, avg_time(RISEAT, RISC)}]},
+ {io_reopen_count, TIRC},
+ {io_reopen_count_details, [{rate, RIRC}]},
+ {mnesia_ram_tx_count, TMRTC},
+ {mnesia_ram_tx_count_details, [{rate, RMRTC}]},
+ {mnesia_disk_tx_count, TMDTC},
+ {mnesia_disk_tx_count_details, [{rate, RMDTC}]},
+ {msg_store_read_count, TMSRC},
+ {msg_store_read_count_details, [{rate, RMSRC}]},
+ {msg_store_write_count, TMSWC},
+ {msg_store_write_count_details, [{rate, RMSWC}]},
+ {queue_index_journal_write_count, TQIJWC},
+ {queue_index_journal_write_count_details, [{rate, RQIJWC}]},
+ {queue_index_write_count, TQIWC},
+ {queue_index_write_count_details, [{rate, RQIWC}]},
+ {queue_index_read_count, TQIRC},
+ {queue_index_read_count_details, [{rate, RQIRC}]},
+ {io_file_handle_open_attempt_count, TIO},
+ {io_file_handle_open_attempt_count_details, [{rate, RIO}]},
+ {io_file_handle_open_attempt_avg_time, avg_time(TIOAT, TIO)},
+ {io_file_handle_open_attempt_avg_time_details, [{rate, avg_time(RIOAT, RIO)}]}
+ ];
+format_rate(node_node_coarse_stats, {TS, TR}, {RS, RR}) ->
+ [
+ {send_bytes, TS},
+ {send_bytes_details, [{rate, RS}]},
+ {recv_bytes, TR},
+ {recv_bytes_details, [{rate, RR}]}
+ ];
+format_rate(connection_churn_rates, {TCCr, TCCo, TChCr, TChCo, TQD, TQCr, TQCo},
+ {RCCr, RCCo, RChCr, RChCo, RQD, RQCr, RQCo}) ->
+ [
+ {connection_created, TCCr},
+ {connection_created_details, [{rate, RCCr}]},
+ {connection_closed, TCCo},
+ {connection_closed_details, [{rate, RCCo}]},
+ {channel_created, TChCr},
+ {channel_created_details, [{rate, RChCr}]},
+ {channel_closed, TChCo},
+ {channel_closed_details, [{rate, RChCo}]},
+ {queue_declared, TQD},
+ {queue_declared_details, [{rate, RQD}]},
+ {queue_created, TQCr},
+ {queue_created_details, [{rate, RQCr}]},
+ {queue_deleted, TQCo},
+ {queue_deleted_details, [{rate, RQCo}]}
+ ].
+
+format_rate(connection_stats_coarse_conn_stats, {TR, TS, TRe}, {RR, RS, RRe},
+ {SR, SS, SRe}, {STR, STS, STRe}, Length) ->
+ [
+ {send_oct, TS},
+ {send_oct_details, [{rate, RS},
+ {samples, SS}] ++ average(SS, STS, Length)},
+ {recv_oct, TR},
+ {recv_oct_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)},
+ {reductions, TRe},
+ {reductions_details, [{rate, RRe},
+ {samples, SRe}] ++ average(SRe, STRe, Length)}
+ ];
+format_rate(vhost_stats_coarse_conn_stats, {TR, TS}, {RR, RS}, {SR, SS},
+ {STR, STS}, Length) ->
+ [
+ {send_oct, TS},
+ {send_oct_details, [{rate, RS},
+ {samples, SS}] ++ average(SS, STS, Length)},
+ {recv_oct, TR},
+ {recv_oct_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)}
+ ];
+format_rate(Type, {TR, TW}, {RR, RW}, {SR, SW}, {STR, STW}, Length)
+ when Type =:= vhost_msg_rates;
+ Type =:= queue_msg_rates ->
+ [
+ {disk_reads, TR},
+ {disk_reads_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)},
+ {disk_writes, TW},
+ {disk_writes_details, [{rate, RW},
+ {samples, SW}] ++ average(SW, STW, Length)}
+ ];
+format_rate(Type, {TP, TC, TRe, TDrop}, {RP, RC, RRe, RDrop},
+ {SP, SC, SRe, SDrop}, {STP, STC, STRe, STDrop}, Length)
+ when Type =:= channel_stats_fine_stats;
+ Type =:= vhost_stats_fine_stats;
+ Type =:= channel_exchange_stats_fine_stats ->
+ [
+ {publish, TP},
+ {publish_details, [{rate, RP},
+ {samples, SP}] ++ average(SP, STP, Length)},
+ {confirm, TC},
+ {confirm_details, [{rate, RC},
+ {samples, SC}] ++ average(SC, STC, Length)},
+ {return_unroutable, TRe},
+ {return_unroutable_details, [{rate, RRe},
+ {samples, SRe}] ++ average(SRe, STRe, Length)},
+ {drop_unroutable, TDrop},
+ {drop_unroutable_details, [{rate, RDrop},
+ {samples, SDrop}] ++ average(SDrop, STDrop, Length)}
+ ];
+format_rate(Type, {TG, TGN, TD, TDN, TR, TA, TDG, TGE},
+ {RG, RGN, RD, RDN, RR, RA, RDG, RGE},
+ {SG, SGN, SD, SDN, SR, SA, SDG, SGE},
+ {STG, STGN, STD, STDN, STR, STA, STDG, STGE},
+ Length)
+ when Type =:= channel_queue_stats_deliver_stats;
+ Type =:= channel_stats_deliver_stats;
+ Type =:= vhost_stats_deliver_stats;
+ Type =:= queue_stats_deliver_stats ->
+ [
+ {get, TG},
+ {get_details, [{rate, RG},
+ {samples, SG}] ++ average(SG, STG, Length)},
+ {get_no_ack, TGN},
+ {get_no_ack_details, [{rate, RGN},
+ {samples, SGN}] ++ average(SGN, STGN, Length)},
+ {deliver, TD},
+ {deliver_details, [{rate, RD},
+ {samples, SD}] ++ average(SD, STD, Length)},
+ {deliver_no_ack, TDN},
+ {deliver_no_ack_details, [{rate, RDN},
+ {samples, SDN}] ++ average(SDN, STDN, Length)},
+ {redeliver, TR},
+ {redeliver_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)},
+ {ack, TA},
+ {ack_details, [{rate, RA},
+ {samples, SA}] ++ average(SA, STA, Length)},
+ {deliver_get, TDG},
+ {deliver_get_details, [{rate, RDG},
+ {samples, SDG}] ++ average(SDG, STDG, Length)},
+ {get_empty, TGE},
+ {get_empty_details, [{rate, RGE},
+ {samples, SGE}] ++ average(SGE, STGE, Length)}
+ ];
+format_rate(Type, {TR}, {RR}, {SR}, {STR}, Length)
+ when Type =:= channel_process_stats;
+ Type =:= queue_process_stats ->
+ [
+ {reductions, TR},
+ {reductions_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)}
+ ];
+format_rate(exchange_stats_publish_out, {TP}, {RP}, {SP}, {STP}, Length) ->
+ [
+ {publish_out, TP},
+ {publish_out_details, [{rate, RP},
+ {samples, SP}] ++ average(SP, STP, Length)}
+ ];
+format_rate(exchange_stats_publish_in, {TP}, {RP}, {SP}, {STP}, Length) ->
+ [
+ {publish_in, TP},
+ {publish_in_details, [{rate, RP},
+ {samples, SP}] ++ average(SP, STP, Length)}
+ ];
+format_rate(Type, {TP}, {RP}, {SP}, {STP}, Length)
+ when Type =:= queue_stats_publish;
+ Type =:= queue_exchange_stats_publish ->
+ [
+ {publish, TP},
+ {publish_details, [{rate, RP},
+ {samples, SP}] ++ average(SP, STP, Length)}
+ ];
+format_rate(Type, {TR, TU, TM}, {RR, RU, RM}, {SR, SU, SM}, {STR, STU, STM},
+ Length) when Type =:= queue_msg_stats;
+ Type =:= vhost_msg_stats ->
+ [
+ {messages_ready, TR},
+ {messages_ready_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)},
+ {messages_unacknowledged, TU},
+ {messages_unacknowledged_details, [{rate, RU},
+ {samples, SU}] ++ average(SU, STU, Length)},
+ {messages, TM},
+ {messages_details, [{rate, RM},
+ {samples, SM}] ++ average(SM, STM, Length)}
+ ];
+format_rate(node_coarse_stats, {TF, TS, TM, TD, TP, TGC, TGCW, TCS},
+ {RF, RS, RM, RD, RP, RGC, RGCW, RCS},
+ {SF, SS, SM, SD, SP, SGC, SGCW, SCS},
+ {STF, STS, STM, STD, STP, STGC, STGCW, STCS}, Length) ->
+ [
+ {mem_used, TM},
+ {mem_used_details, [{rate, RM},
+ {samples, SM}] ++ average(SM, STM, Length)},
+ {fd_used, TF},
+ {fd_used_details, [{rate, RF},
+ {samples, SF}] ++ average(SF, STF, Length)},
+ {sockets_used, TS},
+ {sockets_used_details, [{rate, RS},
+ {samples, SS}] ++ average(SS, STS, Length)},
+ {proc_used, TP},
+ {proc_used_details, [{rate, RP},
+ {samples, SP}] ++ average(SP, STP, Length)},
+ {disk_free, TD},
+ {disk_free_details, [{rate, RD},
+ {samples, SD}] ++ average(SD, STD, Length)},
+ {gc_num, TGC},
+ {gc_num_details, [{rate, RGC},
+ {samples, SGC}] ++ average(SGC, STGC, Length)},
+ {gc_bytes_reclaimed, TGCW},
+ {gc_bytes_reclaimed_details, [{rate, RGCW},
+ {samples, SGCW}] ++ average(SGCW, STGCW, Length)},
+ {context_switches, TCS},
+ {context_switches_details, [{rate, RCS},
+ {samples, SCS}] ++ average(SCS, STCS, Length)}
+ ];
+format_rate(node_persister_stats,
+ {TIR, TIB, TIA, TIWC, TIWB, TIWAT, TIS, TISAT, TISC,
+ TISEAT, TIRC, TMRTC, TMDTC, TMSRC, TMSWC, TQIJWC, TQIWC, TQIRC,
+ TIO, TIOAT},
+ {RIR, RIB, _RIA, RIWC, RIWB, _RIWAT, RIS, _RISAT, RISC,
+ _RISEAT, RIRC, RMRTC, RMDTC, RMSRC, RMSWC, RQIJWC, RQIWC, RQIRC,
+ RIO, _RIOAT},
+ {SIR, SIB, SIA, SIWC, SIWB, SIWAT, SIS, SISAT, SISC,
+ SISEAT, SIRC, SMRTC, SMDTC, SMSRC, SMSWC, SQIJWC, SQIWC, SQIRC,
+ SIO, SIOAT},
+ {STIR, STIB, _STIA, STIWC, STIWB, _STIWAT, STIS, _STISAT, STISC,
+ _STISEAT, STIRC, STMRTC, STMDTC, STMSRC, STMSWC, STQIJWC, STQIWC, STQIRC,
+ STIO, _STIOAT}, Length) ->
+ %% Calculates average times for read/write/sync/seek from the
+ %% accumulated time and count
+ %% io_<op>_avg_time is the average operation time for the life of the node
+ %% io_<op>_avg_time_details/rate is the average operation time during the
+ %% last time unit calculated (thus similar to an instant rate)
+
+ [
+ {io_read_count, TIR},
+ {io_read_count_details, [{rate, RIR},
+ {samples, SIR}] ++ average(SIR, STIR, Length)},
+ {io_read_bytes, TIB},
+ {io_read_bytes_details, [{rate, RIB},
+ {samples, SIB}] ++ average(SIB, STIB, Length)},
+ {io_read_avg_time, avg_time(TIA, TIR)},
+ {io_read_avg_time_details, [{samples, unit_samples(SIA, SIR)}] ++
+ avg_time_details(avg_time(TIA, TIR))},
+ {io_write_count, TIWC},
+ {io_write_count_details, [{rate, RIWC},
+ {samples, SIWC}] ++ average(SIWC, STIWC, Length)},
+ {io_write_bytes, TIWB},
+ {io_write_bytes_details, [{rate, RIWB},
+ {samples, SIWB}] ++ average(SIWB, STIWB, Length)},
+ {io_write_avg_time, avg_time(TIWAT, TIWC)},
+ {io_write_avg_time_details, [{samples, unit_samples(SIWAT, SIWC)}] ++
+ avg_time_details(avg_time(TIWAT, TIWC))},
+ {io_sync_count, TIS},
+ {io_sync_count_details, [{rate, RIS},
+ {samples, SIS}] ++ average(SIS, STIS, Length)},
+ {io_sync_avg_time, avg_time(TISAT, TIS)},
+ {io_sync_avg_time_details, [{samples, unit_samples(SISAT, SIS)}] ++
+ avg_time_details(avg_time(TISAT, TIS))},
+ {io_seek_count, TISC},
+ {io_seek_count_details, [{rate, RISC},
+ {samples, SISC}] ++ average(SISC, STISC, Length)},
+ {io_seek_avg_time, avg_time(TISEAT, TISC)},
+ {io_seek_avg_time_details, [{samples, unit_samples(SISEAT, SISC)}] ++
+ avg_time_details(avg_time(TISEAT, TISC))},
+ {io_reopen_count, TIRC},
+ {io_reopen_count_details, [{rate, RIRC},
+ {samples, SIRC}] ++ average(SIRC, STIRC, Length)},
+ {mnesia_ram_tx_count, TMRTC},
+ {mnesia_ram_tx_count_details, [{rate, RMRTC},
+ {samples, SMRTC}] ++ average(SMRTC, STMRTC, Length)},
+ {mnesia_disk_tx_count, TMDTC},
+ {mnesia_disk_tx_count_details, [{rate, RMDTC},
+ {samples, SMDTC}] ++ average(SMDTC, STMDTC, Length)},
+ {msg_store_read_count, TMSRC},
+ {msg_store_read_count_details, [{rate, RMSRC},
+ {samples, SMSRC}] ++ average(SMSRC, STMSRC, Length)},
+ {msg_store_write_count, TMSWC},
+ {msg_store_write_count_details, [{rate, RMSWC},
+ {samples, SMSWC}] ++ average(SMSWC, STMSWC, Length)},
+ {queue_index_journal_write_count, TQIJWC},
+ {queue_index_journal_write_count_details, [{rate, RQIJWC},
+ {samples, SQIJWC}] ++ average(SQIJWC, STQIJWC, Length)},
+ {queue_index_write_count, TQIWC},
+ {queue_index_write_count_details, [{rate, RQIWC},
+ {samples, SQIWC}] ++ average(SQIWC, STQIWC, Length)},
+ {queue_index_read_count, TQIRC},
+ {queue_index_read_count_details, [{rate, RQIRC},
+ {samples, SQIRC}] ++ average(SQIRC, STQIRC, Length)},
+ {io_file_handle_open_attempt_count, TIO},
+ {io_file_handle_open_attempt_count_details, [{rate, RIO},
+ {samples, SIO}] ++ average(SIO, STIO, Length)},
+ {io_file_handle_open_attempt_avg_time, avg_time(TIOAT, TIO)},
+ {io_file_handle_open_attempt_avg_time_details,
+ [{samples, unit_samples(SIOAT, SIO)}] ++ avg_time_details(avg_time(TIOAT, TIO))}
+ ];
+format_rate(node_node_coarse_stats, {TS, TR}, {RS, RR}, {SS, SR}, {STS, STR}, Length) ->
+ [
+ {send_bytes, TS},
+ {send_bytes_details, [{rate, RS},
+ {samples, SS}] ++ average(SS, STS, Length)},
+ {recv_bytes, TR},
+ {recv_bytes_details, [{rate, RR},
+ {samples, SR}] ++ average(SR, STR, Length)}
+ ];
+format_rate(connection_churn_rates, {TCCr, TCCo, TChCr, TChCo, TQD, TQCr, TQCo},
+ {RCCr, RCCo, RChCr, RChCo, RQD, RQCr, RQCo},
+ {SCCr, SCCo, SChCr, SChCo, SQD, SQCr, SQCo},
+ {STCCr, STCCo, STChCr, STChCo, STQD, STQCr, STQCo}, Length) ->
+ [
+ {connection_created, TCCr},
+ {connection_created_details, [{rate, RCCr},
+ {samples, SCCr}] ++ average(SCCr, STCCr, Length)},
+ {connection_closed, TCCo},
+ {connection_closed_details, [{rate, RCCo},
+ {samples, SCCo}] ++ average(SCCo, STCCo, Length)},
+ {channel_created, TChCr},
+ {channel_created_details, [{rate, RChCr},
+ {samples, SChCr}] ++ average(SChCr, STChCr, Length)},
+ {channel_closed, TChCo},
+ {channel_closed_details, [{rate, RChCo},
+ {samples, SChCo}] ++ average(SChCo, STChCo, Length)},
+ {queue_declared, TQD},
+ {queue_declared_details, [{rate, RQD},
+ {samples, SQD}] ++ average(SQD, STQD, Length)},
+ {queue_created, TQCr},
+ {queue_created_details, [{rate, RQCr},
+ {samples, SQCr}] ++ average(SQCr, STQCr, Length)},
+ {queue_deleted, TQCo},
+ {queue_deleted_details, [{rate, RQCo},
+ {samples, SQCo}] ++ average(SQCo, STQCo, Length)}
+ ].
+
+avg_time_details(Avg) ->
+ %% Rates don't make sense here, populate it with the average.
+ [{rate, Avg}, {avg_rate, Avg}, {avg, Avg}].
+
+average(_Samples, _Total, Length) when Length =< 1->
+ [];
+average(Samples, Total, Length) ->
+ [{sample, S2}, {timestamp, T2}] = hd(Samples),
+ [{sample, S1}, {timestamp, T1}] = lists:last(Samples),
+ [{avg_rate, (S2 - S1) * 1000 / (T2 - T1)},
+ {avg, Total / Length}].
+
+rate_from_last_increment(Table, {TS, _} = Last, T, RangePoint) ->
+ case TS - RangePoint of % [0]
+ D when D >= 0 ->
+ case rate_from_last_increment(Last, T) of
+ unknown ->
+ empty(Table, 0.0);
+ Rate ->
+ Rate
+ end;
+ _ ->
+ empty(Table, 0.0)
+ end.
+
+%% [0] Only display the rate if it's live - i.e. ((the end of the
+%% range) - interval) corresponds to the last data point we have
+rate_from_last_increment(_Total, []) ->
+ unknown;
+rate_from_last_increment(Total, [H | _T]) ->
+ rate_from_difference(Total, H).
+
+rate_from_difference({TS0, {A0, A1}}, {TS1, {B0, B1}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval)};
+rate_from_difference({TS0, {A0, A1, A2}}, {TS1, {B0, B1, B2}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3}}, {TS1, {B0, B1, B2, B3}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval), rate(A3, B3, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3, A4}}, {TS1, {B0, B1, B2, B3, B4}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval), rate(A3, B3, Interval),
+ rate(A4, B4, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3, A4, A5}}, {TS1, {B0, B1, B2, B3, B4, B5}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval), rate(A3, B3, Interval),
+ rate(A4, B4, Interval), rate(A5, B5, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3, A4, A5, A6}},
+ {TS1, {B0, B1, B2, B3, B4, B5, B6}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval),
+ rate(A3, B3, Interval), rate(A4, B4, Interval), rate(A5, B5, Interval),
+ rate(A6, B6, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3, A4, A5, A6, A7}},
+ {TS1, {B0, B1, B2, B3, B4, B5, B6, B7}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval),
+ rate(A3, B3, Interval), rate(A4, B4, Interval), rate(A5, B5, Interval),
+ rate(A6, B6, Interval), rate(A7, B7, Interval)};
+rate_from_difference({TS0, {A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13,
+ A14, A15, A16, A17, A18, A19}},
+ {TS1, {B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13,
+ B14, B15, B16, B17, B18, B19}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval), rate(A1, B1, Interval), rate(A2, B2, Interval),
+ rate(A3, B3, Interval), rate(A4, B4, Interval), rate(A5, B5, Interval),
+ rate(A6, B6, Interval), rate(A7, B7, Interval), rate(A8, B8, Interval),
+ rate(A9, B9, Interval), rate(A10, B10, Interval), rate(A11, B11, Interval),
+ rate(A12, B12, Interval), rate(A13, B13, Interval), rate(A14, B14, Interval),
+ rate(A15, B15, Interval), rate(A16, B16, Interval), rate(A17, B17, Interval),
+ rate(A18, B18, Interval), rate(A19, B19, Interval)};
+rate_from_difference({TS0, {A0}}, {TS1, {B0}}) ->
+ Interval = TS0 - TS1,
+ {rate(A0, B0, Interval)}.
+
+rate(V1, V2, Interval) when is_number(V1), is_number(V2) ->
+ (V1 - V2) * 1000 / Interval;
+rate(_, _, _) ->
+ 0.
+
+append_sample(S, TS, List) ->
+ [[{sample, S}, {timestamp, TS}] | List].
+
+%%----------------------------------------------------------------------------
+%% Match specs to select from the ETS tables
+%%----------------------------------------------------------------------------
+
+avg_time(_Total, Count) when Count == 0;
+ Count == 0.0 ->
+ 0.0;
+avg_time(Total, Count) ->
+ (Total / Count) / ?MICRO_TO_MILLI.
+
+unit_samples(Total, Count) ->
+ lists:zipwith(fun(T, C) ->
+ TS = proplists:get_value(timestamp, T),
+ Sample = avg_time(proplists:get_value(sample, T),
+ proplists:get_value(sample, C)),
+ [{sample, Sample}, {timestamp, TS}]
+ end, Total, Count).
+
+empty(Table, Def) ->
+ rabbit_mgmt_data:empty(Table, Def).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_sup.erl b/deps/rabbitmq_management/src/rabbit_mgmt_sup.erl
new file mode 100644
index 0000000000..5f34f0d160
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_sup.erl
@@ -0,0 +1,42 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_sup).
+
+-behaviour(supervisor).
+
+-export([init/1]).
+-export([start_link/0]).
+-export([setup_wm_logging/0]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_metrics.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit_core_metrics.hrl").
+
+init([]) ->
+ DB = {rabbit_mgmt_db, {rabbit_mgmt_db, start_link, []},
+ permanent, ?WORKER_WAIT, worker, [rabbit_mgmt_db]},
+ WP = {management_worker_pool_sup, {worker_pool_sup, start_link, [3, management_worker_pool]},
+ permanent, ?SUPERVISOR_WAIT, supervisor, [management_worker_pool_sup]},
+ DBC = {rabbit_mgmt_db_cache_sup, {rabbit_mgmt_db_cache_sup, start_link, []},
+ permanent, ?SUPERVISOR_WAIT, supervisor, [rabbit_mgmt_db_cache_sup]},
+ {ok, {{one_for_one, 100, 1}, [DB, WP, DBC]}}.
+
+start_link() ->
+ Res = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
+ setup_wm_logging(),
+ Res.
+
+%% While the project has switched to Cowboy for HTTP handling, we still use
+%% the logger from Webmachine; at least until RabbitMQ switches to Lager or
+%% similar.
+setup_wm_logging() ->
+ {ok, LogDir} = application:get_env(rabbitmq_management, http_log_dir),
+ case LogDir of
+ none -> ok;
+ _ -> webmachine_log:add_handler(webmachine_log_handler, [LogDir])
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_sup_sup.erl b/deps/rabbitmq_management/src/rabbit_mgmt_sup_sup.erl
new file mode 100644
index 0000000000..e8dd164869
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_sup_sup.erl
@@ -0,0 +1,28 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_sup_sup).
+
+-behaviour(supervisor2).
+
+-export([init/1]).
+-export([start_link/0, start_child/0]).
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+start_child() ->
+ supervisor2:start_child(?MODULE, sup()).
+
+sup() ->
+ {rabbit_mgmt_sup, {rabbit_mgmt_sup, start_link, []},
+ temporary, ?SUPERVISOR_WAIT, supervisor, [rabbit_mgmt_sup]}.
+
+init([]) ->
+ {ok, {{one_for_one, 0, 1}, [sup()]}}.
+
+start_link() ->
+ supervisor2:start_link({local, ?MODULE}, ?MODULE, []).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_util.erl b/deps/rabbitmq_management/src/rabbit_mgmt_util.erl
new file mode 100644
index 0000000000..93f167e480
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_util.erl
@@ -0,0 +1,1220 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_util).
+
+%% TODO sort all this out; maybe there's scope for rabbit_mgmt_request?
+
+-export([is_authorized/2, is_authorized_admin/2, is_authorized_admin/4,
+ is_authorized_admin/3, vhost/1, vhost_from_headers/1]).
+-export([is_authorized_vhost/2, is_authorized_user/3,
+ is_authorized_user/4,
+ is_authorized_monitor/2, is_authorized_policies/2,
+ is_authorized_vhost_visible/2,
+ is_authorized_global_parameters/2]).
+
+-export([bad_request/3, bad_request_exception/4, internal_server_error/4,
+ id/2, parse_bool/1, parse_int/1]).
+-export([with_decode/4, not_found/3]).
+-export([with_channel/4, with_channel/5]).
+-export([props_to_method/2, props_to_method/4]).
+-export([all_or_one_vhost/2, reply/3, responder_map/1,
+ filter_vhost/3]).
+-export([filter_conn_ch_list/3, filter_user/2, list_login_vhosts/2,
+ list_login_vhosts_names/2]).
+-export([filter_tracked_conn_list/3]).
+-export([with_decode/5, decode/1, decode/2, set_resp_header/3,
+ args/1, read_complete_body/1]).
+-export([reply_list/3, reply_list/5, reply_list/4,
+ sort_list/2, destination_type/1, reply_list_or_paginate/3
+ ]).
+-export([post_respond/1, columns/1, is_monitor/1]).
+-export([list_visible_vhosts/1, list_visible_vhosts_names/1,
+ b64decode_or_throw/1, no_range/0, range/1,
+ range_ceil/1, floor/2, ceil/1, ceil/2]).
+-export([pagination_params/1,
+ maybe_filter_by_keyword/4,
+ get_value_param/2,
+ augment_resources/6
+ ]).
+-export([direct_request/6]).
+-export([qs_val/2]).
+-export([get_path_prefix/0]).
+-export([catch_no_such_user_or_vhost/2]).
+
+-export([disable_stats/1, enable_queue_totals/1]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+-define(FRAMING, rabbit_framing_amqp_0_9_1).
+-define(DEFAULT_PAGE_SIZE, 100).
+-define(MAX_PAGE_SIZE, 500).
+
+-define(MAX_RANGE, 500).
+
+-record(pagination, {page = undefined, page_size = undefined,
+ name = undefined, use_regex = undefined}).
+
+-record(aug_ctx, {req_data :: cowboy_req:req(),
+ pagination :: #pagination{},
+ sort = [] :: [atom()],
+ columns = [] :: [atom()],
+ data :: term()}).
+
+%%--------------------------------------------------------------------
+
+is_authorized(ReqData, Context) ->
+ is_authorized(ReqData, Context, '', fun(_) -> true end).
+
+is_authorized_admin(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"Not administrator user">>,
+ fun(#user{tags = Tags}) -> is_admin(Tags) end).
+
+is_authorized_admin(ReqData, Context, Token) ->
+ is_authorized(ReqData, Context,
+ rabbit_data_coercion:to_binary(
+ application:get_env(rabbitmq_management, uaa_client_id, "")),
+ Token, <<"Not administrator user">>,
+ fun(#user{tags = Tags}) -> is_admin(Tags) end).
+
+is_authorized_admin(ReqData, Context, Username, Password) ->
+ case is_basic_auth_disabled() of
+ true ->
+ Msg = "HTTP access denied: basic auth disabled",
+ rabbit_log:warning(Msg),
+ not_authorised(Msg, ReqData, Context);
+ false ->
+ is_authorized(ReqData, Context, Username, Password,
+ <<"Not administrator user">>,
+ fun(#user{tags = Tags}) -> is_admin(Tags) end)
+ end.
+
+is_authorized_monitor(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"Not monitor user">>,
+ fun(#user{tags = Tags}) -> is_monitor(Tags) end).
+
+is_authorized_vhost(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"User not authorised to access virtual host">>,
+ fun(#user{tags = Tags} = User) ->
+ is_admin(Tags) orelse user_matches_vhost(ReqData, User)
+ end).
+
+is_authorized_vhost_visible(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"User not authorised to access virtual host">>,
+ fun(#user{tags = Tags} = User) ->
+ is_admin(Tags) orelse user_matches_vhost_visible(ReqData, User)
+ end).
+
+disable_stats(ReqData) ->
+ MgmtOnly = case qs_val(<<"disable_stats">>, ReqData) of
+ <<"true">> -> true;
+ _ -> false
+ end,
+ MgmtOnly orelse get_bool_env(rabbitmq_management, disable_management_stats, false)
+ orelse get_bool_env(rabbitmq_management_agent, disable_metrics_collector, false).
+
+enable_queue_totals(ReqData) ->
+ EnableTotals = case qs_val(<<"enable_queue_totals">>, ReqData) of
+ <<"true">> -> true;
+ _ -> false
+ end,
+ EnableTotals orelse get_bool_env(rabbitmq_management, enable_queue_totals, false).
+
+get_bool_env(Application, Par, Default) ->
+ case application:get_env(Application, Par, Default) of
+ true -> true;
+ false -> false;
+ Other ->
+ rabbit_log:warning("Invalid configuration for application ~p: ~p set to ~p",
+ [Application, Par, Other]),
+ Default
+ end.
+
+user_matches_vhost(ReqData, User) ->
+ case vhost(ReqData) of
+ not_found -> true;
+ none -> true;
+ V ->
+ AuthzData = get_authz_data(ReqData),
+ lists:member(V, list_login_vhosts_names(User, AuthzData))
+ end.
+
+user_matches_vhost_visible(ReqData, User) ->
+ case vhost(ReqData) of
+ not_found -> true;
+ none -> true;
+ V ->
+ AuthzData = get_authz_data(ReqData),
+ lists:member(V, list_visible_vhosts_names(User, AuthzData))
+ end.
+
+get_authz_data(ReqData) ->
+ {PeerAddress, _PeerPort} = cowboy_req:peer(ReqData),
+ {ip, PeerAddress}.
+
+%% Used for connections / channels. A normal user can only see / delete
+%% their own stuff. Monitors can see other users' and delete their
+%% own. Admins can do it all.
+is_authorized_user(ReqData, Context, Item) ->
+ is_authorized(ReqData, Context,
+ <<"User not authorised to access object">>,
+ fun(#user{username = Username, tags = Tags}) ->
+ case cowboy_req:method(ReqData) of
+ <<"DELETE">> -> is_admin(Tags);
+ _ -> is_monitor(Tags)
+ end orelse Username == pget(user, Item)
+ end).
+
+%% For policies / parameters. Like is_authorized_vhost but you have to
+%% be a policymaker.
+is_authorized_policies(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"User not authorised to access object">>,
+ fun(User = #user{tags = Tags}) ->
+ is_admin(Tags) orelse
+ (is_policymaker(Tags) andalso
+ user_matches_vhost(ReqData, User))
+ end).
+
+%% For global parameters. Must be policymaker.
+is_authorized_global_parameters(ReqData, Context) ->
+ is_authorized(ReqData, Context,
+ <<"User not authorised to access object">>,
+ fun(#user{tags = Tags}) ->
+ is_policymaker(Tags)
+ end).
+
+is_basic_auth_disabled() ->
+ get_bool_env(rabbitmq_management, disable_basic_auth, false).
+
+is_authorized(ReqData, Context, ErrorMsg, Fun) ->
+ case cowboy_req:method(ReqData) of
+ <<"OPTIONS">> -> {true, ReqData, Context};
+ _ -> is_authorized1(ReqData, Context, ErrorMsg, Fun)
+ end.
+
+is_authorized1(ReqData, Context, ErrorMsg, Fun) ->
+ case cowboy_req:parse_header(<<"authorization">>, ReqData) of
+ {basic, Username, Password} ->
+ case is_basic_auth_disabled() of
+ true ->
+ Msg = "HTTP access denied: basic auth disabled",
+ rabbit_log:warning(Msg),
+ not_authorised(Msg, ReqData, Context);
+ false ->
+ is_authorized(ReqData, Context,
+ Username, Password,
+ ErrorMsg, Fun)
+ end;
+ {bearer, Token} ->
+ Username = rabbit_data_coercion:to_binary(
+ application:get_env(rabbitmq_management, uaa_client_id, "")),
+ is_authorized(ReqData, Context, Username, Token, ErrorMsg, Fun);
+ _ ->
+ case is_basic_auth_disabled() of
+ true ->
+ Msg = "HTTP access denied: basic auth disabled",
+ rabbit_log:warning(Msg),
+ not_authorised(Msg, ReqData, Context);
+ false ->
+ {{false, ?AUTH_REALM}, ReqData, Context}
+ end
+ end.
+
+is_authorized_user(ReqData, Context, Username, Password) ->
+ Msg = <<"User not authorized">>,
+ Fun = fun(_) -> true end,
+ is_authorized(ReqData, Context, Username, Password, Msg, Fun).
+
+is_authorized(ReqData, Context, Username, Password, ErrorMsg, Fun) ->
+ ErrFun = fun (Msg) ->
+ rabbit_log:warning("HTTP access denied: user '~s' - ~s",
+ [Username, Msg]),
+ not_authorised(Msg, ReqData, Context)
+ end,
+ AuthProps = [{password, Password}] ++ case vhost(ReqData) of
+ VHost when is_binary(VHost) -> [{vhost, VHost}];
+ _ -> []
+ end,
+ {IP, _} = cowboy_req:peer(ReqData),
+ RemoteAddress = list_to_binary(inet:ntoa(IP)),
+ case rabbit_access_control:check_user_login(Username, AuthProps) of
+ {ok, User = #user{tags = Tags}} ->
+ case rabbit_access_control:check_user_loopback(Username, IP) of
+ ok ->
+ case is_mgmt_user(Tags) of
+ true ->
+ case Fun(User) of
+ true ->
+ rabbit_core_metrics:auth_attempt_succeeded(RemoteAddress,
+ Username, http),
+ {true, ReqData,
+ Context#context{user = User,
+ password = Password}};
+ false ->
+ rabbit_core_metrics:auth_attempt_failed(RemoteAddress,
+ Username, http),
+ ErrFun(ErrorMsg)
+ end;
+ false ->
+ rabbit_core_metrics:auth_attempt_failed(RemoteAddress, Username, http),
+ ErrFun(<<"Not management user">>)
+ end;
+ not_allowed ->
+ rabbit_core_metrics:auth_attempt_failed(RemoteAddress, Username, http),
+ ErrFun(<<"User can only log in via localhost">>)
+ end;
+ {refused, _Username, Msg, Args} ->
+ rabbit_core_metrics:auth_attempt_failed(RemoteAddress, Username, http),
+ rabbit_log:warning("HTTP access denied: ~s",
+ [rabbit_misc:format(Msg, Args)]),
+ not_authorised(<<"Login failed">>, ReqData, Context)
+ end.
+
+vhost_from_headers(ReqData) ->
+ case cowboy_req:header(<<"x-vhost">>, ReqData) of
+ undefined -> none;
+ %% blank x-vhost means "All hosts" is selected in the UI
+ <<>> -> none;
+ VHost -> VHost
+ end.
+
+vhost(ReqData) ->
+ case id(vhost, ReqData) of
+ none -> vhost_from_headers(ReqData);
+ VHost -> case rabbit_vhost:exists(VHost) of
+ true -> VHost;
+ false -> not_found
+ end
+ end.
+
+destination_type(ReqData) ->
+ case id(dtype, ReqData) of
+ <<"e">> -> exchange;
+ <<"q">> -> queue
+ end.
+
+%% Provides a map of content type-to-responder that are supported by
+%% reply/3. The map can be used in the content_types_provided/2 callback
+%% used by cowboy_rest. Responder functions must be
+%% exported from the caller module and must use reply/3
+%% under the hood.
+responder_map(FunctionName) ->
+ [
+ {<<"application/json">>, FunctionName},
+ {<<"application/bert">>, FunctionName}
+ ].
+
+reply({stop, _, _} = Reply, _ReqData, _Context) ->
+ Reply;
+reply(Facts, ReqData, Context) ->
+ reply0(extract_columns(Facts, ReqData), ReqData, Context).
+
+reply0(Facts, ReqData, Context) ->
+ ReqData1 = set_resp_header(<<"cache-control">>, "no-cache", ReqData),
+ try
+ case maps:get(media_type, ReqData1, undefined) of
+ {<<"application">>, <<"bert">>, _} ->
+ {term_to_binary(Facts), ReqData1, Context};
+ _ ->
+ {rabbit_json:encode(rabbit_mgmt_format:format_nulls(Facts)),
+ ReqData1, Context}
+ end
+ catch exit:{json_encode, E} ->
+ Error = iolist_to_binary(
+ io_lib:format("JSON encode error: ~p", [E])),
+ Reason = iolist_to_binary(
+ io_lib:format("While encoding: ~n~p", [Facts])),
+ internal_server_error(Error, Reason, ReqData1, Context)
+ end.
+
+reply_list(Facts, ReqData, Context) ->
+ reply_list(Facts, ["vhost", "name"], ReqData, Context, undefined).
+
+reply_list(Facts, DefaultSorts, ReqData, Context) ->
+ reply_list(Facts, DefaultSorts, ReqData, Context, undefined).
+
+get_value_param(Name, ReqData) ->
+ case qs_val(Name, ReqData) of
+ undefined -> undefined;
+ Bin -> binary_to_list(Bin)
+ end.
+
+reply_list(Facts, DefaultSorts, ReqData, Context, Pagination) ->
+ SortList =
+ sort_list_and_paginate(
+ extract_columns_list(Facts, ReqData),
+ DefaultSorts,
+ get_value_param(<<"sort">>, ReqData),
+ get_sort_reverse(ReqData), Pagination),
+
+ reply(SortList, ReqData, Context).
+
+-spec get_sort_reverse(cowboy_req:req()) -> atom().
+get_sort_reverse(ReqData) ->
+ case get_value_param(<<"sort_reverse">>, ReqData) of
+ undefined -> false;
+ V -> list_to_atom(V)
+ end.
+
+
+-spec is_pagination_requested(#pagination{} | undefined) -> boolean().
+is_pagination_requested(undefined) ->
+ false;
+is_pagination_requested(#pagination{}) ->
+ true.
+
+
+with_valid_pagination(ReqData, Context, Fun) ->
+ try
+ Pagination = pagination_params(ReqData),
+ Fun(Pagination)
+ catch error:badarg ->
+ Reason = iolist_to_binary(
+ io_lib:format("Pagination parameters are invalid", [])),
+ invalid_pagination(bad_request, Reason, ReqData, Context);
+ {error, ErrorType, S} ->
+ Reason = iolist_to_binary(S),
+ invalid_pagination(ErrorType, Reason, ReqData, Context)
+ end.
+
+reply_list_or_paginate(Facts, ReqData, Context) ->
+ with_valid_pagination(
+ ReqData, Context,
+ fun(Pagination) ->
+ reply_list(Facts, ["vhost", "name"], ReqData, Context, Pagination)
+ end).
+
+merge_sorts(DefaultSorts, Extra) ->
+ case Extra of
+ undefined -> DefaultSorts;
+ Extra -> [Extra | DefaultSorts]
+ end.
+
+%% Resource augmentation. Works out the most optimal configuration of the operations:
+%% sort, page, augment and executes it returning the result.
+
+column_strategy(all, _) -> extended;
+column_strategy(Cols, BasicColumns) ->
+ case Cols -- BasicColumns of
+ [] -> basic;
+ _ -> extended
+ end.
+
+% columns are [[binary()]] - this takes the first item
+columns_as_strings(all) -> all;
+columns_as_strings(Columns0) ->
+ [rabbit_data_coercion:to_list(C) || [C | _] <- Columns0].
+
+augment_resources(Resources, DefaultSort, BasicColumns, ReqData, Context,
+ AugmentFun) ->
+ with_valid_pagination(ReqData, Context,
+ fun (Pagination) ->
+ augment_resources0(Resources, DefaultSort,
+ BasicColumns, Pagination,
+ ReqData, AugmentFun)
+ end).
+
+augment_resources0(Resources, DefaultSort, BasicColumns, Pagination, ReqData,
+ AugmentFun) ->
+ SortFun = fun (AugCtx) -> sort(DefaultSort, AugCtx) end,
+ AugFun = fun (AugCtx) -> augment(AugmentFun, AugCtx) end,
+ PageFun = fun page/1,
+ Pagination = pagination_params(ReqData),
+ Sort = def(get_value_param(<<"sort">>, ReqData), DefaultSort),
+ Columns = def(columns(ReqData), all),
+ ColumnsAsStrings = columns_as_strings(Columns),
+ Pipeline =
+ case {Pagination =/= undefined,
+ column_strategy(Sort, BasicColumns),
+ column_strategy(ColumnsAsStrings, BasicColumns)} of
+ {false, basic, basic} -> % no pagination, no extended fields
+ [SortFun];
+ {false, _, _} ->
+ % no pagination, extended columns means we need to augment all - SLOW
+ [AugFun, SortFun];
+ {true, basic, basic} ->
+ [SortFun, PageFun];
+ {true, extended, _} ->
+ % pagination with extended sort columns - SLOW
+ [AugFun, SortFun, PageFun];
+ {true, basic, extended} ->
+ % pagination with extended columns and sorting on basic
+ % here we can reduce the augmentation set before
+ % augmenting
+ [SortFun, PageFun, AugFun]
+ end,
+ #aug_ctx{data = {_, Reply}} = run_augmentation(
+ #aug_ctx{req_data = ReqData,
+ pagination = Pagination,
+ sort = Sort,
+ columns = Columns,
+ data = {loaded, Resources}},
+ Pipeline),
+ rabbit_mgmt_format:strip_pids(Reply).
+
+run_augmentation(C, []) -> C;
+run_augmentation(C, [Next | Rem]) ->
+ C2 = Next(C),
+ run_augmentation(C2, Rem).
+
+sort(DefaultSort, Ctx = #aug_ctx{data = Data0,
+ req_data = ReqData,
+ sort = Sort}) ->
+ Data1 = get_data(Data0),
+ Data = sort_list(Data1, DefaultSort, Sort,
+ get_sort_reverse(ReqData)),
+ Ctx#aug_ctx{data = update_data(Data0, {sorted, Data})}.
+
+page(Ctx = #aug_ctx{data = Data0,
+ pagination = Pagination}) ->
+ Data = filter_and_paginate(get_data(Data0), Pagination),
+ Ctx#aug_ctx{data = update_data(Data0, {paged, Data})}.
+
+update_data({paged, Old}, New) ->
+ {paged, rabbit_misc:pset(items, get_data(New), Old)};
+update_data(_, New) ->
+ New.
+
+augment(AugmentFun, Ctx = #aug_ctx{data = Data0, req_data = ReqData}) ->
+ Data1 = get_data(Data0),
+ Data = AugmentFun(Data1, ReqData),
+ Ctx#aug_ctx{data = update_data(Data0, {augmented, Data})}.
+
+get_data({paged, Data}) ->
+ rabbit_misc:pget(items, Data);
+get_data({_, Data}) ->
+ Data.
+
+get_path_prefix() ->
+ EnvPrefix = rabbit_misc:get_env(rabbitmq_management, path_prefix, ""),
+ fixup_prefix(EnvPrefix).
+
+fixup_prefix("") ->
+ "";
+fixup_prefix([Char|_Rest]=EnvPrefix) when is_list(EnvPrefix), Char =:= $/ ->
+ EnvPrefix;
+fixup_prefix(EnvPrefix) when is_list(EnvPrefix) ->
+ "/" ++ EnvPrefix;
+fixup_prefix(EnvPrefix) when is_binary(EnvPrefix) ->
+ fixup_prefix(rabbit_data_coercion:to_list(EnvPrefix)).
+
+%% XXX sort_list_and_paginate/2 is a more proper name for this function, keeping it
+%% with this name for backwards compatibility
+-spec sort_list([Fact], [string()]) -> [Fact] when
+ Fact :: [{atom(), term()}].
+sort_list(Facts, Sorts) -> sort_list_and_paginate(Facts, Sorts, undefined, false,
+ undefined).
+
+-spec sort_list([Fact], [SortColumn], [SortColumn] | undefined, boolean()) -> [Fact] when
+ Fact :: [{atom(), term()}],
+ SortColumn :: string().
+sort_list(Facts, _, [], _) ->
+ %% Do not sort when we are explicitly requested to sort with an
+ %% empty sort columns list. Note that this clause won't match when
+ %% 'sort' parameter is not provided in a HTTP request at all.
+ Facts;
+sort_list(Facts, DefaultSorts, Sort, Reverse) ->
+ SortList = merge_sorts(DefaultSorts, Sort),
+ %% lists:sort/2 is much more expensive than lists:sort/1
+ Sorted = [V || {_K, V} <- lists:sort(
+ [{sort_key(F, SortList), F} || F <- Facts])],
+ maybe_reverse(Sorted, Reverse).
+
+sort_list_and_paginate(Facts, DefaultSorts, Sort, Reverse, Pagination) ->
+ filter_and_paginate(sort_list(Facts, DefaultSorts, Sort, Reverse), Pagination).
+
+filter_and_paginate(Sorted, Pagination) ->
+ ContextList = maybe_filter_context(Sorted, Pagination),
+ range_filter(ContextList, Pagination, Sorted).
+
+%%
+%% Filtering functions
+%%
+maybe_filter_context(List, #pagination{name = Name, use_regex = UseRegex}) when
+ is_list(Name) ->
+ lists:filter(fun(ListF) ->
+ maybe_filter_by_keyword(name, Name, ListF, UseRegex)
+ end,
+ List);
+%% Here it is backward with the other API(s), that don't filter the data
+maybe_filter_context(List, _) ->
+ List.
+
+
+match_value({_, Value}, ValueTag, UseRegex) when UseRegex =:= "true" ->
+ case re:run(Value, ValueTag, [caseless]) of
+ {match, _} -> true;
+ nomatch -> false
+ end;
+match_value({_, Value}, ValueTag, _) ->
+ Pos = string:str(string:to_lower(binary_to_list(Value)),
+ string:to_lower(ValueTag)),
+ case Pos of
+ Pos when Pos > 0 -> true;
+ _ -> false
+ end.
+
+maybe_filter_by_keyword(KeyTag, ValueTag, List, UseRegex) when
+ is_list(ValueTag), length(ValueTag) > 0 ->
+ match_value(lists:keyfind(KeyTag, 1, List), ValueTag, UseRegex);
+maybe_filter_by_keyword(_, _, _, _) ->
+ true.
+
+check_request_param(V, ReqData) ->
+ case qs_val(V, ReqData) of
+ undefined -> undefined;
+ Str -> list_to_integer(binary_to_list(Str))
+ end.
+
+%% Validates and returns pagination parameters:
+%% Page is assumed to be > 0, PageSize > 0 PageSize <= ?MAX_PAGE_SIZE
+pagination_params(ReqData) ->
+ PageNum = check_request_param(<<"page">>, ReqData),
+ PageSize = check_request_param(<<"page_size">>, ReqData),
+ Name = get_value_param(<<"name">>, ReqData),
+ UseRegex = get_value_param(<<"use_regex">>, ReqData),
+ case {PageNum, PageSize} of
+ {undefined, _} ->
+ undefined;
+ {PageNum, undefined} when is_integer(PageNum) andalso PageNum > 0 ->
+ #pagination{page = PageNum, page_size = ?DEFAULT_PAGE_SIZE,
+ name = Name, use_regex = UseRegex};
+ {PageNum, PageSize} when is_integer(PageNum)
+ andalso is_integer(PageSize)
+ andalso (PageNum > 0)
+ andalso (PageSize > 0)
+ andalso (PageSize =< ?MAX_PAGE_SIZE) ->
+ #pagination{page = PageNum, page_size = PageSize,
+ name = Name, use_regex = UseRegex};
+ _ -> throw({error, invalid_pagination_parameters,
+ io_lib:format("Invalid pagination parameters: page number ~p, page size ~p",
+ [PageNum, PageSize])})
+ end.
+
+-spec maybe_reverse([any()], string() | true | false) -> [any()].
+maybe_reverse([], _) ->
+ [];
+maybe_reverse(RangeList, true) when is_list(RangeList) ->
+ lists:reverse(RangeList);
+maybe_reverse(RangeList, false) ->
+ RangeList.
+
+%% for backwards compatibility, does not filter the list
+range_filter(List, undefined, _)
+ -> List;
+
+range_filter(List, RP = #pagination{page = PageNum, page_size = PageSize},
+ TotalElements) ->
+ Offset = (PageNum - 1) * PageSize + 1,
+ try
+ range_response(sublist(List, Offset, PageSize), RP, List,
+ TotalElements)
+ catch
+ error:function_clause ->
+ Reason = io_lib:format(
+ "Page out of range, page: ~p page size: ~p, len: ~p",
+ [PageNum, PageSize, length(List)]),
+ throw({error, page_out_of_range, Reason})
+ end.
+
+%% Injects pagination information into
+range_response([], #pagination{page = PageNum, page_size = PageSize},
+ TotalFiltered, TotalElements) ->
+ TotalPages = trunc((length(TotalFiltered) + PageSize - 1) / PageSize),
+ [{total_count, length(TotalElements)},
+ {item_count, 0},
+ {filtered_count, length(TotalFiltered)},
+ {page, PageNum},
+ {page_size, PageSize},
+ {page_count, TotalPages},
+ {items, []}
+ ];
+range_response(List, #pagination{page = PageNum, page_size = PageSize},
+ TotalFiltered, TotalElements) ->
+ TotalPages = trunc((length(TotalFiltered) + PageSize - 1) / PageSize),
+ [{total_count, length(TotalElements)},
+ {item_count, length(List)},
+ {filtered_count, length(TotalFiltered)},
+ {page, PageNum},
+ {page_size, PageSize},
+ {page_count, TotalPages},
+ {items, List}
+ ].
+
+sort_key(_Item, []) ->
+ [];
+sort_key(Item, [Sort | Sorts]) ->
+ [get_dotted_value(Sort, Item) | sort_key(Item, Sorts)].
+
+get_dotted_value(Key, Item) ->
+ Keys = string:tokens(Key, "."),
+ get_dotted_value0(Keys, Item).
+
+get_dotted_value0([Key], Item) ->
+ %% Put "nothing" before everything else, in number terms it usually
+ %% means 0.
+ pget_bin(list_to_binary(Key), Item, 0);
+get_dotted_value0([Key | Keys], Item) ->
+ get_dotted_value0(Keys, pget_bin(list_to_binary(Key), Item, [])).
+
+pget_bin(Key, Map, Default) when is_map(Map) ->
+ maps:get(Key, Map, Default);
+pget_bin(Key, List, Default) when is_list(List) ->
+ case lists:partition(fun ({K, _V}) -> a2b(K) =:= Key end, List) of
+ {[{_K, V}], _} -> V;
+ {[], _} -> Default
+ end.
+maybe_pagination(Item, false, ReqData) ->
+ extract_column_items(Item, columns(ReqData));
+maybe_pagination([{items, Item} | T], true, ReqData) ->
+ [{items, extract_column_items(Item, columns(ReqData))} |
+ maybe_pagination(T, true, ReqData)];
+maybe_pagination([H | T], true, ReqData) ->
+ [H | maybe_pagination(T,true, ReqData)];
+maybe_pagination(Item, true, ReqData) ->
+ [maybe_pagination(X, true, ReqData) || X <- Item].
+
+extract_columns(Item, ReqData) ->
+ maybe_pagination(Item, is_pagination_requested(pagination_params(ReqData)),
+ ReqData).
+
+extract_columns_list(Items, ReqData) ->
+ Cols = columns(ReqData),
+ [extract_column_items(Item, Cols) || Item <- Items].
+
+columns(ReqData) ->
+ case qs_val(<<"columns">>, ReqData) of
+ undefined -> all;
+ Bin -> [[list_to_binary(T) || T <- string:tokens(C, ".")]
+ || C <- string:tokens(binary_to_list(Bin), ",")]
+ end.
+
+extract_column_items(Item, all) ->
+ Item;
+extract_column_items(Item = [T | _], Cols) when is_tuple(T) ->
+ [{K, extract_column_items(V, descend_columns(a2b(K), Cols))} ||
+ {K, V} <- Item, want_column(a2b(K), Cols)];
+extract_column_items(L, Cols) when is_list(L) ->
+ [extract_column_items(I, Cols) || I <- L];
+extract_column_items(O, _Cols) ->
+ O.
+
+% want_column(_Col, all) -> true;
+want_column(Col, Cols) -> lists:any(fun([C|_]) -> C == Col end, Cols).
+
+descend_columns(_K, []) -> [];
+descend_columns( K, [[K] | _Rest]) -> all;
+descend_columns( K, [[K | K2] | Rest]) -> [K2 | descend_columns(K, Rest)];
+descend_columns( K, [[_K2 | _ ] | Rest]) -> descend_columns(K, Rest).
+
+a2b(A) when is_atom(A) -> list_to_binary(atom_to_list(A));
+a2b(B) -> B.
+
+bad_request(Reason, ReqData, Context) ->
+ halt_response(400, bad_request, Reason, ReqData, Context).
+
+not_authorised(Reason, ReqData, Context) ->
+ halt_response(401, not_authorised, Reason, ReqData, Context).
+
+not_found(Reason, ReqData, Context) ->
+ halt_response(404, not_found, Reason, ReqData, Context).
+
+internal_server_error(Error, Reason, ReqData, Context) ->
+ rabbit_log:error("~s~n~s", [Error, Reason]),
+ halt_response(500, Error, Reason, ReqData, Context).
+
+invalid_pagination(Type,Reason, ReqData, Context) ->
+ halt_response(400, Type, Reason, ReqData, Context).
+
+halt_response(Code, Type, Reason, ReqData, Context) ->
+ ReasonFormatted = format_reason(Reason),
+ Json = #{<<"error">> => Type,
+ <<"reason">> => ReasonFormatted},
+ ReqData1 = cowboy_req:reply(Code,
+ #{<<"content-type">> => <<"application/json">>},
+ rabbit_json:encode(Json), ReqData),
+ {stop, ReqData1, Context}.
+
+format_reason(Tuple) when is_tuple(Tuple) ->
+ rabbit_mgmt_format:tuple(Tuple);
+format_reason(Binary) when is_binary(Binary) ->
+ Binary;
+format_reason(Other) ->
+ case is_string(Other) of
+ true -> rabbit_mgmt_format:print("~ts", [Other]);
+ false -> rabbit_mgmt_format:print("~p", [Other])
+ end.
+
+is_string(List) when is_list(List) ->
+ lists:all(
+ fun(El) -> is_integer(El) andalso El > 0 andalso El < 16#10ffff end,
+ List);
+is_string(_) -> false.
+
+id(Key, ReqData) when Key =:= exchange;
+ Key =:= source;
+ Key =:= destination ->
+ case id0(Key, ReqData) of
+ <<"amq.default">> -> <<"">>;
+ Name -> Name
+ end;
+id(Key, ReqData) ->
+ id0(Key, ReqData).
+
+id0(Key, ReqData) ->
+ case cowboy_req:binding(Key, ReqData) of
+ undefined -> none;
+ Id -> Id
+ end.
+
+read_complete_body(Req) ->
+ read_complete_body(Req, <<"">>).
+read_complete_body(Req0, Acc) ->
+ case cowboy_req:read_body(Req0) of
+ {ok, Data, Req} -> {ok, <<Acc/binary, Data/binary>>, Req};
+ {more, Data, Req} -> read_complete_body(Req, <<Acc/binary, Data/binary>>)
+ end.
+
+with_decode(Keys, ReqData, Context, Fun) ->
+ {ok, Body, ReqData1} = read_complete_body(ReqData),
+ with_decode(Keys, Body, ReqData1, Context, Fun).
+
+with_decode(Keys, Body, ReqData, Context, Fun) ->
+ case decode(Keys, Body) of
+ {error, Reason} -> bad_request(Reason, ReqData, Context);
+ {ok, Values, JSON} -> try
+ Fun(Values, JSON, ReqData)
+ catch {error, Error} ->
+ bad_request(Error, ReqData, Context)
+ end
+ end.
+
+decode(Keys, Body) ->
+ case decode(Body) of
+ {ok, J0} ->
+ J = maps:fold(fun(K, V, Acc) ->
+ Acc#{binary_to_atom(K, utf8) => V}
+ end, J0, J0),
+ Results = [get_or_missing(K, J) || K <- Keys],
+ case [E || E = {key_missing, _} <- Results] of
+ [] -> {ok, Results, J};
+ Errors -> {error, Errors}
+ end;
+ Else -> Else
+ end.
+
+-type parsed_json() :: map() | atom() | binary().
+-spec decode(binary()) -> {ok, parsed_json()} | {error, term()}.
+
+decode(<<"">>) ->
+ {ok, #{}};
+%% some HTTP API clients include "null" for payload in certain scenarios
+decode(<<"null">>) ->
+ {ok, #{}};
+decode(<<"undefined">>) ->
+ {ok, #{}};
+decode(Body) ->
+ try
+ case rabbit_json:decode(Body) of
+ Val when is_map(Val) ->
+ {ok, Val};
+ Val when is_atom(Val) ->
+ {ok, #{}};
+ %% handle double encoded JSON, see rabbitmq/rabbitmq-management#839
+ Bin when is_binary(Bin) ->
+ {error, "invalid payload: the request body JSON-decoded to a string. "
+ "Is the input doubly-JSON-encoded?"};
+ _ ->
+ {error, not_json}
+ end
+ catch error:_ -> {error, not_json}
+ end.
+
+get_or_missing(K, L) ->
+ case maps:get(K, L, undefined) of
+ undefined -> {key_missing, K};
+ V -> V
+ end.
+
+get_node(Props) ->
+ case maps:get(<<"node">>, Props, undefined) of
+ undefined -> node();
+ N -> rabbit_nodes:make(
+ binary_to_list(N))
+ end.
+
+direct_request(MethodName, Transformers, Extra, ErrorMsg, ReqData,
+ Context = #context{user = User}) ->
+ with_vhost_and_props(
+ fun(VHost, Props, ReqData1) ->
+ Method = props_to_method(MethodName, Props, Transformers, Extra),
+ Node = get_node(Props),
+ case rabbit_misc:rpc_call(Node, rabbit_channel, handle_method,
+ [Method, none, #{}, none,
+ VHost, User]) of
+ {badrpc, nodedown} ->
+ Msg = io_lib:format("Node ~p could not be contacted", [Node]),
+ rabbit_log:warning(ErrorMsg, [Msg]),
+ bad_request(list_to_binary(Msg), ReqData1, Context);
+ {badrpc, {'EXIT', #amqp_error{name = not_found, explanation = Explanation}}} ->
+ rabbit_log:warning(ErrorMsg, [Explanation]),
+ not_found(Explanation, ReqData1, Context);
+ {badrpc, {'EXIT', #amqp_error{name = access_refused, explanation = Explanation}}} ->
+ rabbit_log:warning(ErrorMsg, [Explanation]),
+ not_authorised(<<"Access refused.">>, ReqData1, Context);
+ {badrpc, {'EXIT', #amqp_error{name = not_allowed, explanation = Explanation}}} ->
+ rabbit_log:warning(ErrorMsg, [Explanation]),
+ not_authorised(<<"Access refused.">>, ReqData1, Context);
+ {badrpc, {'EXIT', #amqp_error{explanation = Explanation}}} ->
+ rabbit_log:warning(ErrorMsg, [Explanation]),
+ bad_request(list_to_binary(Explanation), ReqData1, Context);
+ {badrpc, Reason} ->
+ rabbit_log:warning(ErrorMsg, [Reason]),
+ bad_request(
+ list_to_binary(
+ io_lib:format("Request to node ~s failed with ~p",
+ [Node, Reason])),
+ ReqData1, Context);
+ _ -> {true, ReqData1, Context}
+ end
+ end, ReqData, Context).
+
+with_vhost_and_props(Fun, ReqData, Context) ->
+ case vhost(ReqData) of
+ not_found ->
+ not_found(rabbit_data_coercion:to_binary("vhost_not_found"),
+ ReqData, Context);
+ VHost ->
+ {ok, Body, ReqData1} = read_complete_body(ReqData),
+ case decode(Body) of
+ {ok, Props} ->
+ try
+ Fun(VHost, Props, ReqData1)
+ catch {error, Error} ->
+ bad_request(Error, ReqData1, Context)
+ end;
+ {error, Reason} ->
+ bad_request(rabbit_mgmt_format:escape_html_tags(Reason),
+ ReqData1, Context)
+ end
+ end.
+
+props_to_method(MethodName, Props, Transformers, Extra) when Props =:= null orelse
+ Props =:= undefined ->
+ props_to_method(MethodName, #{}, Transformers, Extra);
+props_to_method(MethodName, Props, Transformers, Extra) ->
+ Props1 = [{list_to_atom(binary_to_list(K)), V} || {K, V} <- maps:to_list(Props)],
+ props_to_method(
+ MethodName, rabbit_mgmt_format:format(Props1 ++ Extra, {Transformers, true})).
+
+props_to_method(MethodName, Props) ->
+ Props1 = rabbit_mgmt_format:format(
+ Props,
+ {fun rabbit_mgmt_format:format_args/1, true}),
+ FieldNames = ?FRAMING:method_fieldnames(MethodName),
+ {Res, _Idx} = lists:foldl(
+ fun (K, {R, Idx}) ->
+ NewR = case pget(K, Props1) of
+ undefined -> R;
+ V -> setelement(Idx, R, V)
+ end,
+ {NewR, Idx + 1}
+ end, {?FRAMING:method_record(MethodName), 2},
+ FieldNames),
+ Res.
+
+parse_bool(V) -> rabbit_misc:parse_bool(V).
+
+parse_int(V) -> rabbit_misc:parse_int(V).
+
+with_channel(VHost, ReqData, Context, Fun) ->
+ with_channel(VHost, ReqData, Context, node(), Fun).
+
+with_channel(VHost, ReqData,
+ Context = #context{user = #user {username = Username},
+ password = Password},
+ Node, Fun) ->
+ Params = #amqp_params_direct{username = Username,
+ password = Password,
+ node = Node,
+ virtual_host = VHost},
+ case amqp_connection:start(Params) of
+ {ok, Conn} ->
+ {ok, Ch} = amqp_connection:open_channel(Conn),
+ try
+ Fun(Ch)
+ catch
+ exit:{{shutdown,
+ {server_initiated_close, ?NOT_FOUND, Reason}}, _} ->
+ not_found(Reason, ReqData, Context);
+ exit:{{shutdown,
+ {server_initiated_close, ?ACCESS_REFUSED, Reason}}, _} ->
+ not_authorised(Reason, ReqData, Context);
+ exit:{{shutdown, {ServerClose, Code, Reason}}, _}
+ when ServerClose =:= server_initiated_close;
+ ServerClose =:= server_initiated_hard_close ->
+ bad_request_exception(Code, Reason, ReqData, Context);
+ exit:{{shutdown, {connection_closing,
+ {ServerClose, Code, Reason}}}, _}
+ when ServerClose =:= server_initiated_close;
+ ServerClose =:= server_initiated_hard_close ->
+ bad_request_exception(Code, Reason, ReqData, Context)
+ after
+ catch amqp_channel:close(Ch),
+ catch amqp_connection:close(Conn)
+ end;
+ {error, {auth_failure, Msg}} ->
+ not_authorised(Msg, ReqData, Context);
+ {error, not_allowed} ->
+ not_authorised(<<"Access refused.">>, ReqData, Context);
+ {error, access_refused} ->
+ not_authorised(<<"Access refused.">>, ReqData, Context);
+ {error, {nodedown, N}} ->
+ bad_request(
+ list_to_binary(
+ io_lib:format("Node ~s could not be contacted", [N])),
+ ReqData, Context)
+ end.
+
+bad_request_exception(Code, Reason, ReqData, Context) ->
+ bad_request(list_to_binary(io_lib:format("~p ~s", [Code, Reason])),
+ ReqData, Context).
+
+all_or_one_vhost(ReqData, Fun) ->
+ case vhost(ReqData) of
+ none -> lists:append([Fun(V) || V <- rabbit_vhost:list_names()]);
+ not_found -> vhost_not_found;
+ VHost -> Fun(VHost)
+ end.
+
+filter_vhost(List, ReqData, Context) ->
+ User = #user{tags = Tags} = Context#context.user,
+ Fn = case is_admin(Tags) of
+ true -> fun list_visible_vhosts_names/2;
+ false -> fun list_login_vhosts_names/2
+ end,
+ AuthzData = get_authz_data(ReqData),
+ VHosts = Fn(User, AuthzData),
+ [I || I <- List, lists:member(pget(vhost, I), VHosts)].
+
+filter_user(List, _ReqData, #context{user = User}) ->
+ filter_user(List, User).
+
+filter_user(List, #user{username = Username, tags = Tags}) ->
+ case is_monitor(Tags) of
+ true -> List;
+ false -> [I || I <- List, pget(user, I) == Username]
+ end.
+
+filter_conn_ch_list(List, ReqData, Context) ->
+ rabbit_mgmt_format:strip_pids(
+ filter_user(
+ case vhost(ReqData) of
+ none -> List;
+ VHost -> [I || I <- List, pget(vhost, I) =:= VHost]
+ end, ReqData, Context)).
+
+filter_tracked_conn_list(List, ReqData, #context{user = #user{username = FilterUsername, tags = Tags}}) ->
+ %% A bit of duplicated code, but we only go through the list once
+ case {vhost(ReqData), is_monitor(Tags)} of
+ {none, true} ->
+ [[{name, Name},
+ {vhost, VHost},
+ {user, Username},
+ {node, Node}] || #tracked_connection{name = Name, vhost = VHost, username = Username, node = Node} <- List];
+ {FilterVHost, true} ->
+ [[{name, Name},
+ {vhost, VHost},
+ {user, Username},
+ {node, Node}] || #tracked_connection{name = Name, vhost = VHost, username = Username, node = Node} <- List, VHost == FilterVHost];
+ {none, false} ->
+ [[{name, Name},
+ {vhost, VHost},
+ {user, Username},
+ {node, Node}] || #tracked_connection{name = Name, vhost = VHost, username = Username, node = Node} <- List, Username == FilterUsername];
+ {FilterVHost, false} ->
+ [[{name, Name},
+ {vhost, VHost},
+ {user, Username},
+ {node, Node}] || #tracked_connection{name = Name, vhost = VHost, username = Username, node = Node} <- List, VHost == FilterVHost, Username == FilterUsername]
+ end.
+
+set_resp_header(K, V, ReqData) ->
+ cowboy_req:set_resp_header(K, strip_crlf(V), ReqData).
+
+strip_crlf(Str) -> lists:append(string:tokens(Str, "\r\n")).
+
+args([]) -> args(#{});
+args(L) -> rabbit_mgmt_format:to_amqp_table(L).
+
+%% Make replying to a post look like anything else...
+post_respond({true, ReqData, Context}) ->
+ {true, ReqData, Context};
+post_respond({stop, ReqData, Context}) ->
+ {stop, ReqData, Context};
+post_respond({JSON, ReqData, Context}) ->
+ {true, cowboy_req:set_resp_body(JSON, ReqData), Context}.
+
+is_admin(T) -> intersects(T, [administrator]).
+is_policymaker(T) -> intersects(T, [administrator, policymaker]).
+is_monitor(T) -> intersects(T, [administrator, monitoring]).
+is_mgmt_user(T) -> intersects(T, [administrator, monitoring, policymaker,
+ management]).
+
+intersects(A, B) -> lists:any(fun(I) -> lists:member(I, B) end, A).
+
+%% The distinction between list_visible_vhosts and list_login_vhosts
+%% is there to ensure that monitors can always learn of the
+%% existence of all vhosts, and can always see their contribution to
+%% global stats.
+
+list_visible_vhosts_names(User) ->
+ list_visible_vhosts(User, undefined).
+
+list_visible_vhosts_names(User, AuthzData) ->
+ list_visible_vhosts(User, AuthzData).
+
+list_visible_vhosts(User) ->
+ list_visible_vhosts(User, undefined).
+
+list_visible_vhosts(User = #user{tags = Tags}, AuthzData) ->
+ case is_monitor(Tags) of
+ true -> rabbit_vhost:list_names();
+ false -> list_login_vhosts_names(User, AuthzData)
+ end.
+
+list_login_vhosts_names(User, AuthzData) ->
+ [V || V <- rabbit_vhost:list_names(),
+ case catch rabbit_access_control:check_vhost_access(User, V, AuthzData, #{}) of
+ ok -> true;
+ NotOK ->
+ log_access_control_result(NotOK),
+ false
+ end].
+
+list_login_vhosts(User, AuthzData) ->
+ [V || V <- rabbit_vhost:all(),
+ case catch rabbit_access_control:check_vhost_access(User, vhost:get_name(V), AuthzData, #{}) of
+ ok -> true;
+ NotOK ->
+ log_access_control_result(NotOK),
+ false
+ end].
+
+% rabbitmq/rabbitmq-auth-backend-http#100
+log_access_control_result(NotOK) ->
+ rabbit_log:debug("rabbit_access_control:check_vhost_access result: ~p", [NotOK]).
+
+%% base64:decode throws lots of weird errors. Catch and convert to one
+%% that will cause a bad_request.
+b64decode_or_throw(B64) ->
+ rabbit_misc:b64decode_or_throw(B64).
+
+no_range() -> {no_range, no_range, no_range, no_range}.
+
+%% Take floor on queries so we make sure we only return samples
+%% for which we've finished receiving events. Fixes the "drop at
+%% the end" problem.
+range(ReqData) -> {range("lengths", fun floor/2, ReqData),
+ range("msg_rates", fun floor/2, ReqData),
+ range("data_rates", fun floor/2, ReqData),
+ range("node_stats", fun floor/2, ReqData)}.
+
+%% ...but if we know only one event could have contributed towards
+%% what we are interested in, then let's take the ceiling instead and
+%% get slightly fresher data that will match up with any
+%% non-historical data we have (e.g. queue length vs queue messages in
+%% RAM, they should both come from the same snapshot or we might
+%% report more messages in RAM than total).
+%%
+%% However, we only do this for queue lengths since a) it's the only
+%% thing where this ends up being really glaring and b) for other
+%% numbers we care more about the rate than the absolute value, and if
+%% we use ceil() we stand a 50:50 chance of looking up the last sample
+%% in the range before we get it, and thus deriving an instantaneous
+%% rate of 0.0.
+%%
+%% Age is assumed to be > 0, Incr > 0 and (Age div Incr) <= ?MAX_RANGE.
+%% The latter condition allows us to limit the number of samples that
+%% will be sent to the client.
+range_ceil(ReqData) -> {range("lengths", fun ceil/2, ReqData),
+ range("msg_rates", fun floor/2, ReqData),
+ range("data_rates", fun floor/2, ReqData),
+ range("node_stats", fun floor/2, ReqData)}.
+
+range(Prefix, Round, ReqData) ->
+ Age0 = int(Prefix ++ "_age", ReqData),
+ Incr0 = int(Prefix ++ "_incr", ReqData),
+ if
+ is_atom(Age0) orelse is_atom(Incr0) -> no_range;
+ (Age0 > 0) andalso (Incr0 > 0) andalso ((Age0 div Incr0) =< ?MAX_RANGE) ->
+ Age = Age0 * 1000,
+ Incr = Incr0 * 1000,
+ Now = os:system_time(milli_seconds),
+ Last = Round(Now, Incr),
+ #range{first = (Last - Age),
+ last = Last,
+ incr = Incr};
+ true -> throw({error, invalid_range_parameters,
+ io_lib:format("Invalid range parameters: age ~p, incr ~p",
+ [Age0, Incr0])})
+ end.
+
+floor(TS, Interval) -> (TS div Interval) * Interval.
+
+ceil(TS, Interval) -> case floor(TS, Interval) of
+ TS -> TS;
+ Floor -> Floor + Interval
+ end.
+
+ceil(X) when X < 0 ->
+ trunc(X);
+ceil(X) ->
+ T = trunc(X),
+ case X - T == 0 of
+ true -> T;
+ false -> T + 1
+ end.
+
+int(Name, ReqData) ->
+ case qs_val(list_to_binary(Name), ReqData) of
+ undefined -> undefined;
+ Bin -> case catch list_to_integer(binary_to_list(Bin)) of
+ {'EXIT', _} -> undefined;
+ Integer -> Integer
+ end
+ end.
+
+def(undefined, Def) -> Def;
+def(V, _) -> V.
+
+-spec qs_val(binary(), cowboy:req()) -> any() | undefined.
+qs_val(Name, ReqData) ->
+ Qs = cowboy_req:parse_qs(ReqData),
+ proplists:get_value(Name, Qs, undefined).
+
+-spec catch_no_such_user_or_vhost(fun(() -> Result), Replacement) -> Result | Replacement.
+catch_no_such_user_or_vhost(Fun, Replacement) ->
+ try
+ Fun()
+ catch throw:{error, {E, _}} when E =:= no_such_user; E =:= no_such_vhost ->
+ Replacement()
+ end.
+
+%% this retains the old, buggy, pre 23.1 behavour of lists:sublist/3 where an
+%% error is thrown when the request is out of range
+sublist(List, S, L) when is_integer(L), L >= 0 ->
+ lists:sublist(lists:nthtail(S-1, List), L).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_aliveness_test.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_aliveness_test.erl
new file mode 100644
index 0000000000..992ec954e2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_aliveness_test.erl
@@ -0,0 +1,71 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_aliveness_test).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+-define(QUEUE, <<"aliveness-test">>).
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:with_channel(
+ rabbit_mgmt_util:vhost(ReqData),
+ ReqData,
+ Context,
+ fun(Ch) ->
+ #'queue.declare_ok'{queue = ?QUEUE} = amqp_channel:call(Ch, #'queue.declare'{
+ queue = ?QUEUE
+ }),
+ ok = amqp_channel:call(Ch, #'basic.publish'{routing_key = ?QUEUE}, #amqp_msg{
+ payload = <<"test_message">>
+ }),
+ case amqp_channel:call(Ch, #'basic.get'{queue = ?QUEUE, no_ack = true}) of
+ {#'basic.get_ok'{}, _} ->
+ %% Don't delete the queue. If this is pinged every few
+ %% seconds we don't want to create a mnesia transaction
+ %% each time.
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ #'basic.get_empty'{} ->
+ Reason = <<"aliveness-test queue is empty">>,
+ failure(Reason, ReqData, Context);
+ Error ->
+ Reason = rabbit_data_coercion:to_binary(Error),
+ failure(Reason, ReqData, Context)
+ end
+ end
+ ).
+
+failure(Reason, ReqData0, Context0) ->
+ Body = #{status => failed, reason => Reason},
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply(Body, ReqData0, Context0),
+ {stop, cowboy_req:reply(?HEALTH_CHECK_FAILURE_STATUS, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl
new file mode 100644
index 0000000000..6899fc54ee
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl
@@ -0,0 +1,51 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_auth).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ EnableUAA = application:get_env(rabbitmq_management, enable_uaa, false),
+ Data = case EnableUAA of
+ true ->
+ UAAClientId = application:get_env(rabbitmq_management, uaa_client_id, ""),
+ UAALocation = application:get_env(rabbitmq_management, uaa_location, ""),
+ case is_invalid([UAAClientId, UAALocation]) of
+ true ->
+ rabbit_log:warning("Disabling OAuth 2 authorization, relevant configuration settings are missing", []),
+ [{enable_uaa, false}, {uaa_client_id, <<>>}, {uaa_location, <<>>}];
+ false ->
+ [{enable_uaa, true},
+ {uaa_client_id, rabbit_data_coercion:to_binary(UAAClientId)},
+ {uaa_location, rabbit_data_coercion:to_binary(UAALocation)}]
+ end;
+ false ->
+ [{enable_uaa, false}, {uaa_client_id, <<>>}, {uaa_location, <<>>}]
+ end,
+ rabbit_mgmt_util:reply(Data, ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+is_invalid(List) ->
+ lists:any(fun(V) -> V == "" end, List).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth_attempts.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth_attempts.erl
new file mode 100644
index 0000000000..cdd11826ea
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth_attempts.erl
@@ -0,0 +1,79 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_auth_attempts).
+
+-export([init/2, to_json/2, content_types_provided/2, allowed_methods/2, is_authorized/2,
+ delete_resource/2, resource_exists/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+init(Req, [Mode]) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE),
+ {Mode, #context{}}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {node_exists(ReqData, get_node(ReqData)), ReqData, Context}.
+
+to_json(ReqData, {Mode, Context}) ->
+ rabbit_mgmt_util:reply(augment(Mode, ReqData), ReqData, Context).
+
+is_authorized(ReqData, {Mode, Context}) ->
+ {Res, Req2, Context2} = rabbit_mgmt_util:is_authorized_monitor(ReqData, Context),
+ {Res, Req2, {Mode, Context2}}.
+
+delete_resource(ReqData, Context) ->
+ Node = get_node(ReqData),
+ case node_exists(ReqData, Node) of
+ false ->
+ {false, ReqData, Context};
+ true ->
+ case rpc:call(Node, rabbit_core_metrics, reset_auth_attempt_metrics, [], infinity) of
+ {badrpc, _} -> {false, ReqData, Context};
+ ok -> {true, ReqData, Context}
+ end
+ end.
+%%--------------------------------------------------------------------
+get_node(ReqData) ->
+ list_to_atom(binary_to_list(rabbit_mgmt_util:id(node, ReqData))).
+
+node_exists(ReqData, Node) ->
+ case [N || N <- rabbit_mgmt_wm_nodes:all_nodes(ReqData),
+ proplists:get_value(name, N) == Node] of
+ [] -> false;
+ [_] -> true
+ end.
+
+augment(Mode, ReqData) ->
+ Node = get_node(ReqData),
+ case node_exists(ReqData, Node) of
+ false ->
+ not_found;
+ true ->
+ Fun = case Mode of
+ all -> get_auth_attempts;
+ by_source -> get_auth_attempts_by_source
+ end,
+ case rpc:call(Node, rabbit_core_metrics, Fun, [], infinity) of
+ {badrpc, _} -> not_available;
+ Result -> Result
+ end
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_binding.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_binding.erl
new file mode 100644
index 0000000000..4396edc53b
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_binding.erl
@@ -0,0 +1,141 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_binding).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ Binding = binding(ReqData),
+ {case Binding of
+ not_found -> false;
+ {bad_request, _} -> false;
+ _ -> case rabbit_binding:exists(Binding) of
+ true -> true;
+ _ -> false
+ end
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ with_binding(ReqData, Context,
+ fun(Binding) ->
+ rabbit_mgmt_util:reply(
+ rabbit_mgmt_format:binding(Binding),
+ ReqData, Context)
+ end).
+
+delete_resource(ReqData, Context) ->
+ MethodName = case rabbit_mgmt_util:destination_type(ReqData) of
+ exchange -> 'exchange.unbind';
+ queue -> 'queue.unbind'
+ end,
+ with_binding(
+ ReqData, Context,
+ fun(#binding{ source = #resource{name = S},
+ destination = #resource{name = D},
+ key = Key,
+ args = Args }) ->
+ rabbit_mgmt_util:direct_request(
+ MethodName,
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{queue, D},
+ {exchange, S},
+ {destination, D},
+ {source, S},
+ {routing_key, Key},
+ {arguments, Args}],
+ "Unbinding error: ~s", ReqData, Context)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+binding(ReqData) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> not_found;
+ VHost -> Source = rabbit_mgmt_util:id(source, ReqData),
+ Dest = rabbit_mgmt_util:id(destination, ReqData),
+ DestType = rabbit_mgmt_util:destination_type(ReqData),
+ Props = rabbit_mgmt_util:id(props, ReqData),
+ SName = rabbit_misc:r(VHost, exchange, Source),
+ DName = rabbit_misc:r(VHost, DestType, Dest),
+ case unpack(SName, DName, Props) of
+ {bad_request, Str} ->
+ {bad_request, Str};
+ {Key, Args} ->
+ #binding{ source = SName,
+ destination = DName,
+ key = Key,
+ args = Args }
+ end
+ end.
+
+unpack(Src, Dst, Props) ->
+ case rabbit_mgmt_format:tokenise(binary_to_list(Props)) of
+ ["\~"] -> {<<>>, []};
+ %% when routing_key is explicitly set to `null` in JSON payload,
+ %% the value would be stored as null the atom. See rabbitmq/rabbitmq-management#723 for details.
+ ["null"] -> {null, []};
+ ["undefined"] -> {undefined, []};
+ [Key] -> {unquote(Key), []};
+ ["\~", ArgsEnc] -> lookup(<<>>, ArgsEnc, Src, Dst);
+ %% see above
+ ["null", ArgsEnc] -> lookup(null, ArgsEnc, Src, Dst);
+ ["undefined", ArgsEnc] -> lookup(undefined, ArgsEnc, Src, Dst);
+ [Key, ArgsEnc] -> lookup(unquote(Key), ArgsEnc, Src, Dst);
+ _ -> {bad_request, {too_many_tokens, Props}}
+ end.
+
+lookup(RoutingKey, ArgsEnc, Src, Dst) ->
+ lookup(RoutingKey, unquote(ArgsEnc),
+ rabbit_binding:list_for_source_and_destination(Src, Dst)).
+
+lookup(_RoutingKey, _Hash, []) ->
+ {bad_request, "binding not found"};
+lookup(RoutingKey, Hash, [#binding{args = Args} | Rest]) ->
+ case args_hash(Args) =:= Hash of
+ true -> {RoutingKey, Args};
+ false -> lookup(RoutingKey, Hash, Rest)
+ end.
+
+args_hash(Args) ->
+ rabbit_mgmt_format:args_hash(Args).
+
+unquote(Name) ->
+ list_to_binary(rabbit_http_util:unquote(Name)).
+
+with_binding(ReqData, Context, Fun) ->
+ case binding(ReqData) of
+ {bad_request, Reason} ->
+ rabbit_mgmt_util:bad_request(Reason, ReqData, Context);
+ Binding ->
+ Fun(Binding)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_bindings.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_bindings.erl
new file mode 100644
index 0000000000..5ed0a82f8d
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_bindings.erl
@@ -0,0 +1,149 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_bindings).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([allowed_methods/2]).
+-export([content_types_accepted/2, accept_content/2, resource_exists/2]).
+-export([basic/1, augmented/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, [Mode]) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), {Mode, #context{}}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+%% The current version of Cowboy forces us to report the resource doesn't
+%% exist here in order to get a 201 response. It seems Cowboy confuses the
+%% resource from the request and the resource that will be created by POST.
+%% https://github.com/ninenines/cowboy/issues/723#issuecomment-161319576
+resource_exists(ReqData, {Mode, Context}) ->
+ case cowboy_req:method(ReqData) of
+ <<"POST">> ->
+ {false, ReqData, {Mode, Context}};
+ _ ->
+ {case list_bindings(Mode, ReqData) of
+ vhost_not_found -> false;
+ _ -> true
+ end, ReqData, {Mode, Context}}
+ end.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+%% Methods to add to the CORS header.
+%% This clause is called by rabbit_mgmt_cors:handle_options/2
+allowed_methods(undefined, undefined) ->
+ {[<<"HEAD">>, <<"GET">>, <<"POST">>, <<"OPTIONS">>], undefined, undefined};
+allowed_methods(ReqData, {Mode, Context}) ->
+ {case Mode of
+ source_destination -> [<<"HEAD">>, <<"GET">>, <<"POST">>, <<"OPTIONS">>];
+ _ -> [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]
+ end, ReqData, {Mode, Context}}.
+
+to_json(ReqData, {Mode, Context}) ->
+ Bs = [rabbit_mgmt_format:binding(B) || B <- list_bindings(Mode, ReqData)],
+ rabbit_mgmt_util:reply_list(
+ rabbit_mgmt_util:filter_vhost(Bs, ReqData, Context),
+ ["vhost", "source", "type", "destination",
+ "routing_key", "properties_key"],
+ ReqData, {Mode, Context}).
+
+accept_content(ReqData0, {_Mode, Context}) ->
+ {ok, Body, ReqData} = rabbit_mgmt_util:read_complete_body(ReqData0),
+ Source = rabbit_mgmt_util:id(source, ReqData),
+ Dest = rabbit_mgmt_util:id(destination, ReqData),
+ DestType = rabbit_mgmt_util:id(dtype, ReqData),
+ VHost = rabbit_mgmt_util:vhost(ReqData),
+ {ok, Props} = rabbit_mgmt_util:decode(Body),
+ MethodName = case rabbit_mgmt_util:destination_type(ReqData) of
+ exchange -> 'exchange.bind';
+ queue -> 'queue.bind'
+ end,
+ {Key, Args} = key_args(DestType, Props),
+ case rabbit_mgmt_util:direct_request(
+ MethodName,
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{queue, Dest},
+ {exchange, Source},
+ {destination, Dest},
+ {source, Source},
+ {routing_key, Key},
+ {arguments, Args}],
+ "Binding error: ~s", ReqData, Context) of
+ {stop, _, _} = Res ->
+ Res;
+ {true, ReqData, Context2} ->
+ From = binary_to_list(cowboy_req:path(ReqData)),
+ Prefix = rabbit_mgmt_util:get_path_prefix(),
+ BindingProps = rabbit_mgmt_format:pack_binding_props(Key, Args),
+ UrlWithBindings = rabbit_mgmt_format:url("/api/bindings/~s/e/~s/~s/~s/~s",
+ [VHost, Source, DestType,
+ Dest, BindingProps]),
+ To = Prefix ++ binary_to_list(UrlWithBindings),
+ Loc = rabbit_web_dispatch_util:relativise(From, To),
+ {{true, Loc}, ReqData, Context2}
+ end.
+
+is_authorized(ReqData, {Mode, Context}) ->
+ {Res, RD2, C2} = rabbit_mgmt_util:is_authorized_vhost(ReqData, Context),
+ {Res, RD2, {Mode, C2}}.
+
+%%--------------------------------------------------------------------
+
+basic(ReqData) ->
+ [rabbit_mgmt_format:binding(B) ||
+ B <- list_bindings(all, ReqData)].
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context).
+
+key_args(<<"q">>, Props) ->
+ #'queue.bind'{routing_key = K, arguments = A} =
+ rabbit_mgmt_util:props_to_method(
+ 'queue.bind', Props, [], []),
+ {K, A};
+
+key_args(<<"e">>, Props) ->
+ #'exchange.bind'{routing_key = K, arguments = A} =
+ rabbit_mgmt_util:props_to_method(
+ 'exchange.bind', Props,
+ [], []),
+ {K, A}.
+
+%%--------------------------------------------------------------------
+
+list_bindings(all, ReqData) ->
+ rabbit_mgmt_util:all_or_one_vhost(ReqData,
+ fun (VHost) ->
+ rabbit_binding:list(VHost)
+ end);
+list_bindings(exchange_source, ReqData) ->
+ rabbit_binding:list_for_source(r(exchange, exchange, ReqData));
+list_bindings(exchange_destination, ReqData) ->
+ rabbit_binding:list_for_destination(r(exchange, exchange, ReqData));
+list_bindings(queue, ReqData) ->
+ rabbit_binding:list_for_destination(r(queue, destination, ReqData));
+list_bindings(source_destination, ReqData) ->
+ DestType = rabbit_mgmt_util:destination_type(ReqData),
+ rabbit_binding:list_for_source_and_destination(
+ r(exchange, source, ReqData),
+ r(DestType, destination, ReqData)).
+
+r(Type, Name, ReqData) ->
+ rabbit_misc:r(rabbit_mgmt_util:vhost(ReqData), Type,
+ rabbit_mgmt_util:id(Name, ReqData)).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_channel.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channel.erl
new file mode 100644
index 0000000000..0b4aae6c13
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channel.erl
@@ -0,0 +1,68 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_channel).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ case channel(ReqData) of
+ not_found -> {false, ReqData, Context};
+ _Conn -> {true, ReqData, Context}
+ end;
+ true ->
+ {false, ReqData, Context}
+ end.
+
+to_json(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ Payload = rabbit_mgmt_format:clean_consumer_details(
+ rabbit_mgmt_format:strip_pids(channel(ReqData))),
+ rabbit_mgmt_util:reply(
+ maps:from_list(Payload),
+ ReqData, Context);
+ true ->
+ rabbit_mgmt_util:bad_request(<<"Stats in management UI are disabled on this node">>, ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ try
+ rabbit_mgmt_util:is_authorized_user(ReqData, Context, channel(ReqData))
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end;
+ true ->
+ rabbit_mgmt_util:bad_request(<<"Stats in management UI are disabled on this node">>, ReqData, Context)
+ end.
+
+%%--------------------------------------------------------------------
+
+channel(ReqData) ->
+ rabbit_mgmt_db:get_channel(rabbit_mgmt_util:id(channel, ReqData),
+ rabbit_mgmt_util:range(ReqData)).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels.erl
new file mode 100644
index 0000000000..a064b2f9f9
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels.erl
@@ -0,0 +1,50 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_channels).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ augmented/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ try
+ rabbit_mgmt_util:reply_list_or_paginate(augmented(ReqData, Context),
+ ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end;
+ true ->
+ rabbit_mgmt_util:bad_request(<<"Stats in management UI are disabled on this node">>, ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_util:filter_conn_ch_list(
+ rabbit_mgmt_db:get_all_channels(
+ rabbit_mgmt_util:range(ReqData)), ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels_vhost.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels_vhost.erl
new file mode 100644
index 0000000000..454a29e0bb
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_channels_vhost.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2010-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_channels_vhost).
+
+%% Lists channels in a vhost
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ augmented/2, resource_exists/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {rabbit_vhost:exists(rabbit_mgmt_util:id(vhost, ReqData)), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ try
+ rabbit_mgmt_util:reply_list(augmented(ReqData, Context), ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end;
+ true ->
+ rabbit_mgmt_util:bad_request(<<"Stats in management UI are disabled on this node">>, ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_util:filter_conn_ch_list(
+ rabbit_mgmt_db:get_all_channels(
+ rabbit_mgmt_util:range(ReqData)), ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_cluster_name.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_cluster_name.erl
new file mode 100644
index 0000000000..132da93ad2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_cluster_name.erl
@@ -0,0 +1,59 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_cluster_name).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(
+ [{name, rabbit_nodes:cluster_name()}], ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ rabbit_mgmt_util:with_decode(
+ [name], ReqData0, Context, fun([Name], _, ReqData) ->
+ rabbit_nodes:set_cluster_name(
+ as_binary(Name), Username),
+ {true, ReqData, Context}
+ end).
+
+is_authorized(ReqData, Context) ->
+ case cowboy_req:method(ReqData) of
+ <<"PUT">> -> rabbit_mgmt_util:is_authorized_admin(ReqData, Context);
+ _ -> rabbit_mgmt_util:is_authorized(ReqData, Context)
+ end.
+
+as_binary(Val) when is_binary(Val) ->
+ Val;
+as_binary(Val) when is_list(Val) ->
+ list_to_binary(Val).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection.erl
new file mode 100644
index 0000000000..7de605e6a5
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection.erl
@@ -0,0 +1,98 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_connection).
+
+-export([init/2, resource_exists/2, to_json/2, content_types_provided/2,
+ is_authorized/2, allowed_methods/2, delete_resource/2, conn/1]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ case conn(ReqData) of
+ not_found -> {false, ReqData, Context};
+ _Conn -> {true, ReqData, Context}
+ end.
+
+to_json(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_util:reply(
+ maps:from_list(rabbit_mgmt_format:strip_pids(conn_stats(ReqData))), ReqData, Context);
+ true ->
+ rabbit_mgmt_util:reply([{name, rabbit_mgmt_util:id(connection, ReqData)}],
+ ReqData, Context)
+ end.
+
+delete_resource(ReqData, Context) ->
+ case conn(ReqData) of
+ not_found -> ok;
+ Conn ->
+ case proplists:get_value(pid, Conn) of
+ undefined -> ok;
+ Pid when is_pid(Pid) ->
+ force_close_connection(ReqData, Conn, Pid)
+ end
+ end,
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ try
+ rabbit_mgmt_util:is_authorized_user(ReqData, Context, conn(ReqData))
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+%%--------------------------------------------------------------------
+
+conn(ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ conn_stats(ReqData);
+ true ->
+ case rabbit_connection_tracking:lookup(rabbit_mgmt_util:id(connection, ReqData)) of
+ #tracked_connection{name = Name, pid = Pid, username = Username, type = Type} ->
+ [{name, Name}, {pid, Pid}, {user, Username}, {type, Type}];
+ not_found ->
+ not_found
+ end
+ end.
+
+conn_stats(ReqData) ->
+ rabbit_mgmt_db:get_connection(rabbit_mgmt_util:id(connection, ReqData),
+ rabbit_mgmt_util:range_ceil(ReqData)).
+
+force_close_connection(ReqData, Conn, Pid) ->
+ Reason = case cowboy_req:header(<<"x-reason">>, ReqData) of
+ undefined -> "Closed via management plugin";
+ V -> binary_to_list(V)
+ end,
+ case proplists:get_value(type, Conn) of
+ direct -> amqp_direct_connection:server_close(Pid, 320, Reason);
+ network -> rabbit_networking:close_connection(Pid, Reason);
+ _ ->
+ % best effort, this will work for connections to the stream plugin
+ gen_server:call(Pid, {shutdown, Reason}, infinity)
+ end,
+ ok.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_channels.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_channels.erl
new file mode 100644
index 0000000000..dfbcfb5ba1
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connection_channels.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_connection_channels).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ case rabbit_mgmt_wm_connection:conn(ReqData) of
+ error -> {false, ReqData, Context};
+ _Conn -> {true, ReqData, Context}
+ end.
+
+to_json(ReqData, Context) ->
+ Name = proplists:get_value(name, rabbit_mgmt_wm_connection:conn(ReqData)),
+ Chs = rabbit_mgmt_db:get_all_channels(rabbit_mgmt_util:range(ReqData)),
+ rabbit_mgmt_util:reply_list(
+ [Ch || Ch <- rabbit_mgmt_util:filter_conn_ch_list(Chs, ReqData, Context),
+ conn_name(Ch) =:= Name],
+ ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ try
+ rabbit_mgmt_util:is_authorized_user(
+ ReqData, Context, rabbit_mgmt_wm_connection:conn(ReqData))
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+%%--------------------------------------------------------------------
+
+conn_name(Ch) ->
+ proplists:get_value(name, proplists:get_value(connection_details, Ch)).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections.erl
new file mode 100644
index 0000000000..0e1345abca
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_connections).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ augmented/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ Connections = do_connections_query(ReqData, Context),
+ rabbit_mgmt_util:reply_list_or_paginate(Connections, ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_util:filter_conn_ch_list(
+ rabbit_mgmt_db:get_all_connections(
+ rabbit_mgmt_util:range_ceil(ReqData)), ReqData, Context).
+
+do_connections_query(ReqData, Context) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ augmented(ReqData, Context);
+ true ->
+ rabbit_mgmt_util:filter_tracked_conn_list(rabbit_connection_tracking:list(),
+ ReqData, Context)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections_vhost.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections_vhost.erl
new file mode 100644
index 0000000000..1840a7ae45
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_connections_vhost.erl
@@ -0,0 +1,49 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2010-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_connections_vhost).
+
+%% Lists connections in a vhost
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ augmented/2, resource_exists/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {rabbit_vhost:exists(rabbit_mgmt_util:id(vhost, ReqData)), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ rabbit_mgmt_util:reply_list(augmented(ReqData, Context), ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_util:filter_conn_ch_list(
+ rabbit_mgmt_db:get_all_connections(
+ rabbit_mgmt_util:range_ceil(ReqData)), ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_consumers.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_consumers.erl
new file mode 100644
index 0000000000..56945d3a5d
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_consumers.erl
@@ -0,0 +1,59 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+
+-module(rabbit_mgmt_wm_consumers).
+
+-export([init/2, to_json/2, content_types_provided/2, resource_exists/2,
+ is_authorized/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> false;
+ none -> true; % none means `all`
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context = #context{user = User}) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ Arg = case rabbit_mgmt_util:vhost(ReqData) of
+ none -> all;
+ VHost -> VHost
+ end,
+ Consumers = rabbit_mgmt_format:strip_pids(rabbit_mgmt_db:get_all_consumers(Arg)),
+ Formatted = [rabbit_mgmt_format:format_consumer_arguments(C) || C <- Consumers],
+ rabbit_mgmt_util:reply_list(
+ filter_user(Formatted, User), ReqData, Context);
+ true ->
+ rabbit_mgmt_util:bad_request(<<"Stats in management UI are disabled on this node">>, ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+filter_user(List, #user{username = Username, tags = Tags}) ->
+ case rabbit_mgmt_util:is_monitor(Tags) of
+ true -> List;
+ false -> [I || I <- List,
+ pget(user, pget(channel_details, I)) == Username]
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_definitions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_definitions.erl
new file mode 100644
index 0000000000..c0687993a9
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_definitions.erl
@@ -0,0 +1,298 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_definitions).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([content_types_accepted/2, allowed_methods/2, accept_json/2]).
+-export([accept_multipart/2]).
+-export([variances/2]).
+
+-export([apply_defs/3, apply_defs/5]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"json">>, '*'}, accept_json},
+ {{<<"multipart">>, <<"form-data">>, '*'}, accept_multipart}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ none ->
+ all_definitions(ReqData, Context);
+ not_found ->
+ rabbit_mgmt_util:bad_request(rabbit_data_coercion:to_binary("vhost_not_found"),
+ ReqData, Context);
+ VHost ->
+ vhost_definitions(ReqData, VHost, Context)
+ end.
+
+all_definitions(ReqData, Context) ->
+ Xs = [X || X <- rabbit_mgmt_wm_exchanges:basic(ReqData),
+ export_exchange(X)],
+ Qs = [Q || Q <- rabbit_mgmt_wm_queues:basic(ReqData),
+ export_queue(Q)],
+ QNames = [{pget(name, Q), pget(vhost, Q)} || Q <- Qs],
+ Bs = [B || B <- rabbit_mgmt_wm_bindings:basic(ReqData),
+ export_binding(B, QNames)],
+ Vsn = rabbit:base_product_version(),
+ ProductName = rabbit:product_name(),
+ ProductVersion = rabbit:product_version(),
+ rabbit_mgmt_util:reply(
+ [{rabbit_version, rabbit_data_coercion:to_binary(Vsn)},
+ {rabbitmq_version, rabbit_data_coercion:to_binary(Vsn)},
+ {product_name, rabbit_data_coercion:to_binary(ProductName)},
+ {product_version, rabbit_data_coercion:to_binary(ProductVersion)}] ++
+ filter(
+ [{users, rabbit_mgmt_wm_users:users(all)},
+ {vhosts, rabbit_mgmt_wm_vhosts:basic()},
+ {permissions, rabbit_mgmt_wm_permissions:permissions()},
+ {topic_permissions, rabbit_mgmt_wm_topic_permissions:topic_permissions()},
+ {parameters, rabbit_mgmt_wm_parameters:basic(ReqData)},
+ {global_parameters, rabbit_mgmt_wm_global_parameters:basic()},
+ {policies, rabbit_mgmt_wm_policies:basic(ReqData)},
+ {queues, Qs},
+ {exchanges, Xs},
+ {bindings, Bs}]),
+ case rabbit_mgmt_util:qs_val(<<"download">>, ReqData) of
+ undefined -> ReqData;
+ Filename -> rabbit_mgmt_util:set_resp_header(
+ <<"Content-Disposition">>,
+ "attachment; filename=" ++
+ binary_to_list(Filename), ReqData)
+ end,
+ Context).
+
+accept_json(ReqData0, Context) ->
+ {ok, Body, ReqData} = rabbit_mgmt_util:read_complete_body(ReqData0),
+ accept(Body, ReqData, Context).
+
+vhost_definitions(ReqData, VHost, Context) ->
+ %% rabbit_mgmt_wm_<>:basic/1 filters by VHost if it is available
+ Xs = [strip_vhost(X) || X <- rabbit_mgmt_wm_exchanges:basic(ReqData),
+ export_exchange(X)],
+ VQs = [Q || Q <- rabbit_mgmt_wm_queues:basic(ReqData), export_queue(Q)],
+ Qs = [strip_vhost(Q) || Q <- VQs],
+ QNames = [{pget(name, Q), pget(vhost, Q)} || Q <- VQs],
+ Bs = [strip_vhost(B) || B <- rabbit_mgmt_wm_bindings:basic(ReqData),
+ export_binding(B, QNames)],
+ {ok, Vsn} = application:get_key(rabbit, vsn),
+ Parameters = [rabbit_mgmt_format:parameter(
+ rabbit_mgmt_wm_parameters:fix_shovel_publish_properties(P))
+ || P <- rabbit_runtime_parameters:list(VHost)],
+ rabbit_mgmt_util:reply(
+ [{rabbit_version, rabbit_data_coercion:to_binary(Vsn)}] ++
+ filter(
+ [{parameters, Parameters},
+ {policies, rabbit_mgmt_wm_policies:basic(ReqData)},
+ {queues, Qs},
+ {exchanges, Xs},
+ {bindings, Bs}]),
+ case rabbit_mgmt_util:qs_val(<<"download">>, ReqData) of
+ undefined -> ReqData;
+ Filename ->
+ HeaderVal = "attachment; filename=" ++ binary_to_list(Filename),
+ rabbit_mgmt_util:set_resp_header(<<"Content-Disposition">>, HeaderVal, ReqData)
+ end,
+ Context).
+
+accept_multipart(ReqData0, Context) ->
+ {Parts, ReqData} = get_all_parts(ReqData0),
+ Redirect = get_part(<<"redirect">>, Parts),
+ Payload = get_part(<<"file">>, Parts),
+ Resp = {Res, _, _} = accept(Payload, ReqData, Context),
+ case {Res, Redirect} of
+ {true, unknown} -> {true, ReqData, Context};
+ {true, _} -> {{true, Redirect}, ReqData, Context};
+ _ -> Resp
+ end.
+
+is_authorized(ReqData, Context) ->
+ case rabbit_mgmt_util:qs_val(<<"auth">>, ReqData) of
+ undefined ->
+ case rabbit_mgmt_util:qs_val(<<"token">>, ReqData) of
+ undefined ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context);
+ Token ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context, Token)
+ end;
+ Auth ->
+ is_authorized_qs(ReqData, Context, Auth)
+ end.
+
+%% Support for the web UI - it can't add a normal "authorization"
+%% header for a file download.
+is_authorized_qs(ReqData, Context, Auth) ->
+ case rabbit_web_dispatch_util:parse_auth_header("Basic " ++ Auth) of
+ [Username, Password] -> rabbit_mgmt_util:is_authorized_admin(
+ ReqData, Context, Username, Password);
+ _ -> {?AUTH_REALM, ReqData, Context}
+ end.
+
+%%--------------------------------------------------------------------
+
+decode(<<"">>) ->
+ {ok, #{}};
+decode(Body) ->
+ try
+ Decoded = rabbit_json:decode(Body),
+ Normalised = maps:fold(fun(K, V, Acc) ->
+ Acc#{binary_to_atom(K, utf8) => V}
+ end, Decoded, Decoded),
+ {ok, Normalised}
+ catch error:_ -> {error, not_json}
+ end.
+
+accept(Body, ReqData, Context = #context{user = #user{username = Username}}) ->
+ %% At this point the request was fully received.
+ %% There is no point in the idle_timeout anymore.
+ disable_idle_timeout(ReqData),
+ case decode(Body) of
+ {error, E} ->
+ rabbit_log:error("Encountered an error when parsing definitions: ~p", [E]),
+ rabbit_mgmt_util:bad_request(rabbit_data_coercion:to_binary("failed_to_parse_json"),
+ ReqData, Context);
+ {ok, Map} ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ none ->
+ case apply_defs(Map, Username) of
+ {error, E} ->
+ rabbit_log:error("Encountered an error when importing definitions: ~p", [E]),
+ rabbit_mgmt_util:bad_request(E, ReqData, Context);
+ ok -> {true, ReqData, Context}
+ end;
+ not_found ->
+ rabbit_mgmt_util:not_found(rabbit_data_coercion:to_binary("vhost_not_found"),
+ ReqData, Context);
+ VHost when is_binary(VHost) ->
+ case apply_defs(Map, Username, VHost) of
+ {error, E} ->
+ rabbit_log:error("Encountered an error when importing definitions: ~p", [E]),
+ rabbit_mgmt_util:bad_request(E, ReqData, Context);
+ ok -> {true, ReqData, Context}
+ end
+ end
+ end.
+
+disable_idle_timeout(#{pid := Pid, streamid := StreamID}) ->
+ Pid ! {{Pid, StreamID}, {set_options, #{idle_timeout => infinity}}}.
+
+-spec apply_defs(Map :: #{atom() => any()}, ActingUser :: rabbit_types:username()) -> 'ok' | {error, term()}.
+
+apply_defs(Body, ActingUser) ->
+ rabbit_definitions:apply_defs(Body, ActingUser).
+
+-spec apply_defs(Map :: #{atom() => any()}, ActingUser :: rabbit_types:username(),
+ VHost :: vhost:name()) -> 'ok' | {error, term()}.
+
+apply_defs(Body, ActingUser, VHost) ->
+ rabbit_definitions:apply_defs(Body, ActingUser, VHost).
+
+-spec apply_defs(Map :: #{atom() => any()},
+ ActingUser :: rabbit_types:username(),
+ SuccessFun :: fun(() -> 'ok'),
+ ErrorFun :: fun((any()) -> 'ok'),
+ VHost :: vhost:name()) -> 'ok' | {error, term()}.
+
+apply_defs(Body, ActingUser, SuccessFun, ErrorFun, VHost) ->
+ rabbit_definitions:apply_defs(Body, ActingUser, SuccessFun, ErrorFun, VHost).
+
+get_all_parts(Req) ->
+ get_all_parts(Req, []).
+
+get_all_parts(Req0, Acc) ->
+ case cowboy_req:read_part(Req0) of
+ {done, Req1} ->
+ {Acc, Req1};
+ {ok, Headers, Req1} ->
+ Name = case cow_multipart:form_data(Headers) of
+ {data, N} -> N;
+ {file, N, _, _} -> N
+ end,
+ {ok, Body, Req2} = stream_part_body(Req1, <<>>),
+ get_all_parts(Req2, [{Name, Body}|Acc])
+ end.
+
+stream_part_body(Req0, Acc) ->
+ case cowboy_req:read_part_body(Req0) of
+ {more, Data, Req1} ->
+ stream_part_body(Req1, <<Acc/binary, Data/binary>>);
+ {ok, Data, Req1} ->
+ {ok, <<Acc/binary, Data/binary>>, Req1}
+ end.
+
+get_part(Name, Parts) ->
+ case lists:keyfind(Name, 1, Parts) of
+ false -> unknown;
+ {_, Value} -> Value
+ end.
+
+export_queue(Queue) ->
+ pget(owner_pid, Queue) == none.
+
+export_binding(Binding, Qs) ->
+ Src = pget(source, Binding),
+ Dest = pget(destination, Binding),
+ DestType = pget(destination_type, Binding),
+ VHost = pget(vhost, Binding),
+ Src =/= <<"">>
+ andalso
+ ( (DestType =:= queue andalso lists:member({Dest, VHost}, Qs))
+ orelse (DestType =:= exchange andalso Dest =/= <<"">>) ).
+
+export_exchange(Exchange) ->
+ export_name(pget(name, Exchange)).
+
+export_name(<<>>) -> false;
+export_name(<<"amq.", _/binary>>) -> false;
+export_name(_Name) -> true.
+
+%%--------------------------------------------------------------------
+
+rw_state() ->
+ [{users, [name, password_hash, hashing_algorithm, tags, limits]},
+ {vhosts, [name]},
+ {permissions, [user, vhost, configure, write, read]},
+ {topic_permissions, [user, vhost, exchange, write, read]},
+ {parameters, [vhost, component, name, value]},
+ {global_parameters, [name, value]},
+ {policies, [vhost, name, pattern, definition, priority, 'apply-to']},
+ {queues, [name, vhost, durable, auto_delete, arguments]},
+ {exchanges, [name, vhost, type, durable, auto_delete, internal,
+ arguments]},
+ {bindings, [source, vhost, destination, destination_type, routing_key,
+ arguments]}].
+
+filter(Items) ->
+ [filter_items(N, V, proplists:get_value(N, rw_state())) || {N, V} <- Items].
+
+filter_items(Name, List, Allowed) ->
+ {Name, [filter_item(I, Allowed) || I <- List]}.
+
+filter_item(Item, Allowed) ->
+ [{K, Fact} || {K, Fact} <- Item, lists:member(K, Allowed)].
+
+strip_vhost(Item) ->
+ lists:keydelete(vhost, 1, Item).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange.erl
new file mode 100644
index 0000000000..2d5930d1d0
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange.erl
@@ -0,0 +1,90 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_exchange).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2, exchange/1, exchange/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case exchange(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ [X] = rabbit_mgmt_db:augment_exchanges(
+ [exchange(ReqData)], rabbit_mgmt_util:range(ReqData), full),
+ rabbit_mgmt_util:reply(rabbit_mgmt_format:strip_pids(X), ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+accept_content(ReqData, Context) ->
+ Name = rabbit_mgmt_util:id(exchange, ReqData),
+ rabbit_mgmt_util:direct_request(
+ 'exchange.declare',
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{exchange, Name}], "Declare exchange error: ~s", ReqData, Context).
+
+delete_resource(ReqData, Context) ->
+ %% We need to retrieve manually if-unused, as the HTTP API uses '-'
+ %% while the record uses '_'
+ Name = id(ReqData),
+ IfUnused = <<"true">> =:= rabbit_mgmt_util:qs_val(<<"if-unused">>, ReqData),
+ rabbit_mgmt_util:direct_request(
+ 'exchange.delete',
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{exchange, Name},
+ {if_unused, IfUnused}], "Delete exchange error: ~s", ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+exchange(ReqData) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> not_found;
+ VHost -> exchange(VHost, id(ReqData))
+ end.
+
+exchange(VHost, XName) ->
+ Name = rabbit_misc:r(VHost, exchange, XName),
+ case rabbit_exchange:lookup(Name) of
+ {ok, X} -> rabbit_mgmt_format:exchange(
+ rabbit_exchange:info(X));
+ {error, not_found} -> not_found
+ end.
+
+id(ReqData) ->
+ rabbit_mgmt_util:id(exchange, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange_publish.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange_publish.erl
new file mode 100644
index 0000000000..7e90b54ba1
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchange_publish.erl
@@ -0,0 +1,105 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_exchange_publish).
+
+-export([init/2, resource_exists/2, is_authorized/2,
+ allowed_methods/2, content_types_provided/2, accept_content/2,
+ content_types_accepted/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_exchange:exchange(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+accept_content(ReqData, Context) ->
+ rabbit_mgmt_util:post_respond(do_it(ReqData, Context)).
+
+do_it(ReqData0, Context) ->
+ VHost = rabbit_mgmt_util:vhost(ReqData0),
+ X = rabbit_mgmt_util:id(exchange, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [routing_key, properties, payload, payload_encoding], ReqData0, Context,
+ fun ([RoutingKey, Props0, Payload0, Enc], _, ReqData) when is_binary(Payload0) ->
+ rabbit_mgmt_util:with_channel(
+ VHost, ReqData, Context,
+ fun (Ch) ->
+ MRef = erlang:monitor(process, Ch),
+ amqp_channel:register_confirm_handler(Ch, self()),
+ amqp_channel:register_return_handler(Ch, self()),
+ amqp_channel:call(Ch, #'confirm.select'{}),
+ Props = rabbit_mgmt_format:to_basic_properties(Props0),
+ Payload = decode(Payload0, Enc),
+ amqp_channel:cast(Ch, #'basic.publish'{
+ exchange = X,
+ routing_key = RoutingKey,
+ mandatory = true},
+ #amqp_msg{props = Props,
+ payload = Payload}),
+ receive
+ {#'basic.return'{}, _} ->
+ receive
+ #'basic.ack'{} -> ok
+ end,
+ good(MRef, false, ReqData, Context);
+ #'basic.ack'{} ->
+ good(MRef, true, ReqData, Context);
+ #'basic.nack'{} ->
+ erlang:demonitor(MRef),
+ bad(rejected, ReqData, Context);
+ {'DOWN', _, _, _, Err} ->
+ bad(Err, ReqData, Context)
+ end
+ end);
+ ([_RoutingKey, _Props, _Payload, _Enc], _, _ReqData) ->
+ throw({error, payload_not_string})
+ end).
+
+good(MRef, Routed, ReqData, Context) ->
+ erlang:demonitor(MRef),
+ rabbit_mgmt_util:reply([{routed, Routed}], ReqData, Context).
+
+bad({shutdown, {connection_closing,
+ {server_initiated_close, Code, Reason}}}, ReqData, Context) ->
+ rabbit_mgmt_util:bad_request_exception(Code, Reason, ReqData, Context);
+
+bad({shutdown, {server_initiated_close, Code, Reason}}, ReqData, Context) ->
+ rabbit_mgmt_util:bad_request_exception(Code, Reason, ReqData, Context);
+bad(rejected, ReqData, Context) ->
+ Msg = "Unable to publish message. Check queue limits.",
+ rabbit_mgmt_util:bad_request_exception(rejected, Msg, ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+decode(Payload, <<"string">>) -> Payload;
+decode(Payload, <<"base64">>) -> rabbit_mgmt_util:b64decode_or_throw(Payload);
+decode(_Payload, Enc) -> throw({error, {unsupported_encoding, Enc}}).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchanges.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchanges.erl
new file mode 100644
index 0000000000..9f7abab20f
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_exchanges.erl
@@ -0,0 +1,75 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_exchanges).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ resource_exists/2, basic/1, augmented/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-define(DEFAULT_SORT, ["vhost", "name"]).
+
+-define(BASIC_COLUMNS, ["vhost", "name", "type", "durable", "auto_delete",
+ "internal", "arguments", "pid"]).
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case exchanges0(ReqData) of
+ vhost_not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ Basic = rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData,
+ Context),
+ Data = rabbit_mgmt_util:augment_resources(Basic, ?DEFAULT_SORT,
+ ?BASIC_COLUMNS, ReqData,
+ Context, fun augment/2),
+ rabbit_mgmt_util:reply(Data, ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+augment(Basic, ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_exchanges(Basic, rabbit_mgmt_util:range(ReqData),
+ basic);
+ true ->
+ Basic
+ end.
+
+augmented(ReqData, Context) ->
+ rabbit_mgmt_db:augment_exchanges(
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context),
+ rabbit_mgmt_util:range(ReqData), basic).
+
+basic(ReqData) ->
+ [rabbit_mgmt_format:exchange(X) || X <- exchanges0(ReqData)].
+
+exchanges0(ReqData) ->
+ rabbit_mgmt_util:all_or_one_vhost(ReqData, fun rabbit_exchange:info_all/1).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_extensions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_extensions.erl
new file mode 100644
index 0000000000..26c3ccbee6
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_extensions.erl
@@ -0,0 +1,33 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_extensions).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ Modules = rabbit_mgmt_dispatcher:modules([]),
+ rabbit_mgmt_util:reply(
+ [Module:web_ui() || Module <- Modules], ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl
new file mode 100644
index 0000000000..fafb993951
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl
@@ -0,0 +1,55 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2019-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_feature_flag_enable).
+
+-export([init/2,
+ content_types_accepted/2, is_authorized/2,
+ allowed_methods/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _Args) ->
+ {cowboy_rest,
+ rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE),
+ #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"json">>, '*'}, accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"PUT">>, <<"OPTIONS">>], ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+accept_content(ReqData, #context{} = Context) ->
+ NameS = rabbit_mgmt_util:id(name, ReqData),
+ try
+ Name = list_to_existing_atom(binary_to_list(NameS)),
+ case rabbit_feature_flags:enable(Name) of
+ ok ->
+ {true, ReqData, Context};
+ {error, Reason1} ->
+ FormattedReason1 = rabbit_ff_extra:format_error(Reason1),
+ rabbit_mgmt_util:bad_request(
+ list_to_binary(FormattedReason1), ReqData, Context)
+ end
+ catch
+ _:badarg ->
+ Reason2 = unsupported,
+ FormattedReason2 = rabbit_ff_extra:format_error(Reason2),
+ rabbit_mgmt_util:bad_request(
+ list_to_binary(FormattedReason2), ReqData, Context)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flags.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flags.erl
new file mode 100644
index 0000000000..3f25045a36
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flags.erl
@@ -0,0 +1,45 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2019-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_feature_flags).
+
+-export([init/2, to_json/2,
+ content_types_provided/2,
+ is_authorized/2, allowed_methods/2]).
+-export([variances/2]).
+-export([feature_flags/0]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _Args) ->
+ {cowboy_rest,
+ rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE),
+ #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"OPTIONS">>], ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(feature_flags(), ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ {Res, Req2, Context2} = rabbit_mgmt_util:is_authorized_admin(ReqData, Context),
+ {Res, Req2, Context2}.
+
+%%--------------------------------------------------------------------
+
+feature_flags() ->
+ rabbit_ff_extra:cli_info().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameter.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameter.erl
new file mode 100644
index 0000000000..a81c5be8fa
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameter.erl
@@ -0,0 +1,71 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_global_parameter).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case parameter(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(rabbit_mgmt_format:parameter(parameter(ReqData)),
+ ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ rabbit_mgmt_util:with_decode(
+ [value], ReqData0, Context,
+ fun([Value], _, ReqData) ->
+ Val = if is_map(Value) -> maps:to_list(Value);
+ true -> Value
+ end,
+ rabbit_runtime_parameters:set_global(name(ReqData), Val, Username),
+ {true, ReqData, Context}
+ end).
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ ok = rabbit_runtime_parameters:clear_global(name(ReqData), Username),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_global_parameters(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+parameter(ReqData) ->
+ rabbit_runtime_parameters:lookup_global(name(ReqData)).
+
+name(ReqData) -> rabbit_data_coercion:to_atom(rabbit_mgmt_util:id(name, ReqData)).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameters.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameters.erl
new file mode 100644
index 0000000000..fa23283ad5
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_global_parameters.erl
@@ -0,0 +1,36 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_global_parameters).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+-export([basic/0]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(basic(), ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_global_parameters(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+basic() ->
+ rabbit_runtime_parameters:list_global().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_alarms.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_alarms.erl
new file mode 100644
index 0000000000..06db3bd79e
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_alarms.erl
@@ -0,0 +1,56 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_alarms'
+-module(rabbit_mgmt_wm_health_check_alarms).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ Timeout = case cowboy_req:header(<<"timeout">>, ReqData) of
+ undefined -> 70000;
+ Val -> list_to_integer(binary_to_list(Val))
+ end,
+ case rabbit_alarm:get_alarms(Timeout) of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ Xs when length(Xs) > 0 ->
+ Msg = "There are alarms in effect in the cluster",
+ failure(Msg, Xs, ReqData, Context)
+ end.
+
+failure(Message, Alarms0, ReqData, Context) ->
+ Alarms = rabbit_alarm:format_as_maps(Alarms0),
+ Body = #{
+ status => failed,
+ reason => rabbit_data_coercion:to_binary(Message),
+ alarms => Alarms
+ },
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply(Body, ReqData, Context),
+ {stop, cowboy_req:reply(?HEALTH_CHECK_FAILURE_STATUS, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_certificate_expiration.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_certificate_expiration.erl
new file mode 100644
index 0000000000..f0fd466b2a
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_certificate_expiration.erl
@@ -0,0 +1,178 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_certificate_expiration'
+-module(rabbit_mgmt_wm_health_check_certificate_expiration).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-define(DAYS_SECONDS, 86400).
+-define(WEEKS_SECONDS, ?DAYS_SECONDS * 7).
+-define(MONTHS_SECONDS, ?DAYS_SECONDS * (365 / 12)).
+-define(YEARS_SECONDS, ?DAYS_SECONDS * 365).
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ Listeners = rabbit_networking:active_listeners(),
+ Local = [L || #listener{node = N} = L <- Listeners, N == node()],
+ case convert(within(ReqData), unit(ReqData)) of
+ {error, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context);
+ Seconds ->
+ ExpiringListeners = lists:foldl(fun(L, Acc) ->
+ case listener_expiring_within(L, Seconds) of
+ false -> Acc;
+ Map -> [Map | Acc]
+ end
+ end, [], Local),
+ case ExpiringListeners of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ _ ->
+ Msg = <<"Certificates expiring">>,
+ failure(Msg, ExpiringListeners, ReqData, Context)
+ end
+ end.
+
+failure(Message, Listeners, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {expired, Listeners}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+within(ReqData) ->
+ rabbit_mgmt_util:id(within, ReqData).
+
+unit(ReqData) ->
+ rabbit_mgmt_util:id(unit, ReqData).
+
+convert(Time, Unit) ->
+ try
+ do_convert(binary_to_integer(Time), string:lowercase(binary_to_list(Unit)))
+ catch
+ error:badarg ->
+ {error, "Invalid expiration value."};
+ invalid_unit ->
+ {error, "Time unit not recognised. Use: days, seconds, months, years."}
+ end.
+
+do_convert(Time, "days") ->
+ Time * ?DAYS_SECONDS;
+do_convert(Time, "weeks") ->
+ Time * ?WEEKS_SECONDS;
+do_convert(Time, "months") ->
+ Time * ?MONTHS_SECONDS;
+do_convert(Time, "years") ->
+ Time * ?YEARS_SECONDS;
+do_convert(_, _) ->
+ throw(invalid_unit).
+
+listener_expiring_within(#listener{node = Node, protocol = Protocol, ip_address = Interface,
+ port = Port, opts = Opts}, Seconds) ->
+ Certfile = proplists:get_value(certfile, Opts),
+ Cacertfile = proplists:get_value(cacertfile, Opts),
+ Now = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
+ ExpiryDate = Now + Seconds,
+ CertfileExpiresOn = expired(cert_validity(read_cert(Certfile)), ExpiryDate),
+ CacertfileExpiresOn = expired(cert_validity(read_cert(Cacertfile)), ExpiryDate),
+ case {CertfileExpiresOn, CacertfileExpiresOn} of
+ {[], []} ->
+ false;
+ _ ->
+ #{node => Node,
+ protocol => Protocol,
+ interface => list_to_binary(inet:ntoa(Interface)),
+ port => Port,
+ certfile => list_to_binary(Certfile),
+ cacertfile => list_to_binary(Cacertfile),
+ certfile_expires_on => expires_on_list(CertfileExpiresOn),
+ cacertfile_expires_on => expires_on_list(CacertfileExpiresOn)
+ }
+ end.
+
+expires_on_list({error, Reason}) ->
+ {error, list_to_binary(Reason)};
+expires_on_list(ExpiresOn) ->
+ [seconds_to_bin(S) || S <- ExpiresOn].
+
+read_cert(undefined) ->
+ undefined;
+read_cert({pem, Pem}) ->
+ Pem;
+read_cert(Path) ->
+ case file:read_file(Path) of
+ {ok, Bin} ->
+ Bin;
+ Err ->
+ Err
+ end.
+
+cert_validity(undefined) ->
+ undefined;
+cert_validity(Cert) ->
+ DsaEntries = public_key:pem_decode(Cert),
+ case DsaEntries of
+ [] ->
+ {error, "The certificate file provided does not contain any PEM entry."};
+ _ ->
+ Now = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
+ lists:map(
+ fun({'Certificate', _, _} = DsaEntry) ->
+ #'Certificate'{tbsCertificate = TBSCertificate} = public_key:pem_entry_decode(DsaEntry),
+ #'TBSCertificate'{validity = Validity} = TBSCertificate,
+ #'Validity'{notAfter = NotAfter, notBefore = NotBefore} = Validity,
+ Start = pubkey_cert:time_str_2_gregorian_sec(NotBefore),
+ case Start > Now of
+ true ->
+ {error, "Certificate is not yet valid"};
+ false ->
+ pubkey_cert:time_str_2_gregorian_sec(NotAfter)
+ end;
+ ({Type, _, _}) ->
+ {error, io_lib:format("The certificate file provided contains a ~p entry",
+ [Type])}
+ end, DsaEntries)
+ end.
+
+expired(undefined, _ExpiryDate) ->
+ [];
+expired({error, _} = Error, _ExpiryDate) ->
+ Error;
+expired(Expires, ExpiryDate) ->
+ lists:filter(fun({error, _}) ->
+ true;
+ (Seconds) ->
+ Seconds < ExpiryDate
+ end, Expires).
+
+seconds_to_bin(Seconds) ->
+ {{Y, M, D}, {H, Min, S}} = calendar:gregorian_seconds_to_datetime(Seconds),
+ list_to_binary(lists:flatten(io_lib:format("~w-~2.2.0w-~2.2.0w ~w:~2.2.0w:~2.2.0w",
+ [Y, M, D, H, Min, S]))).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_local_alarms.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_local_alarms.erl
new file mode 100644
index 0000000000..4553efa3e2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_local_alarms.erl
@@ -0,0 +1,56 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-dignoastics check_local_alarms'
+-module(rabbit_mgmt_wm_health_check_local_alarms).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ Timeout = case cowboy_req:header(<<"timeout">>, ReqData) of
+ undefined -> 70000;
+ Val -> list_to_integer(binary_to_list(Val))
+ end,
+ case rabbit_alarm:get_local_alarms(Timeout) of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ Xs when length(Xs) > 0 ->
+ Msg = "There are alarms in effect on the node",
+ failure(Msg, Xs, ReqData, Context)
+ end.
+
+failure(Message, Alarms0, ReqData, Context) ->
+ Alarms = rabbit_alarm:format_as_maps(Alarms0),
+ Body = #{
+ status => failed,
+ reason => rabbit_data_coercion:to_binary(Message),
+ alarms => Alarms
+ },
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply(Body, ReqData, Context),
+ {stop, cowboy_req:reply(?HEALTH_CHECK_FAILURE_STATUS, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical.erl
new file mode 100644
index 0000000000..cef5512551
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_if_node_is_quorum_critical'
+-module(rabbit_mgmt_wm_health_check_node_is_mirror_sync_critical).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_nodes:is_single_node_cluster() of
+ true ->
+ rabbit_mgmt_util:reply([{status, ok},
+ {reason, <<"single node cluster">>}], ReqData, Context);
+ false ->
+ case rabbit_amqqueue:list_local_mirrored_classic_without_synchronised_mirrors_for_cli() of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ Qs when length(Qs) > 0 ->
+ Msg = <<"There are classic mirrored queues without online synchronised mirrors">>,
+ failure(Msg, Qs, ReqData, Context)
+ end
+ end.
+
+failure(Message, Qs, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {queues, Qs}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_quorum_critical.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_quorum_critical.erl
new file mode 100644
index 0000000000..857c72b30b
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_node_is_quorum_critical.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_if_node_is_quorum_critical'
+-module(rabbit_mgmt_wm_health_check_node_is_quorum_critical).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_nodes:is_single_node_cluster() of
+ true ->
+ rabbit_mgmt_util:reply([{status, ok},
+ {reason, <<"single node cluster">>}], ReqData, Context);
+ false ->
+ case rabbit_quorum_queue:list_with_minimum_quorum_for_cli() of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ Qs when length(Qs) > 0 ->
+ Msg = <<"There are quorum queues that would lose their quorum if the target node is shut down">>,
+ failure(Msg, Qs, ReqData, Context)
+ end
+ end.
+
+failure(Message, Qs, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {queues, Qs}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_port_listener.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_port_listener.erl
new file mode 100644
index 0000000000..1fcc00f613
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_port_listener.erl
@@ -0,0 +1,66 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_port_listener'
+-module(rabbit_mgmt_wm_health_check_port_listener).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case port(ReqData) of
+ none -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ Port = binary_to_integer(port(ReqData)),
+ Listeners = rabbit_networking:active_listeners(),
+ Local = [L || #listener{node = N} = L <- Listeners, N == node()],
+ PortListeners = [L || #listener{port = P} = L <- Local, P == Port],
+ case PortListeners of
+ [] ->
+ Msg = <<"No active listener">>,
+ failure(Msg, Port, [P || #listener{port = P} <- Local], ReqData, Context);
+ _ ->
+ rabbit_mgmt_util:reply([{status, ok},
+ {port, Port}], ReqData, Context)
+ end
+ catch
+ error:badarg ->
+ rabbit_mgmt_util:bad_request(<<"Invalid port">>, ReqData, Context)
+ end.
+
+failure(Message, Missing, Ports, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {missing, Missing},
+ {ports, Ports}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+port(ReqData) ->
+ rabbit_mgmt_util:id(port, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_protocol_listener.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_protocol_listener.erl
new file mode 100644
index 0000000000..a0b7e4e6dd
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_protocol_listener.erl
@@ -0,0 +1,127 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_protocol_listener'
+-module(rabbit_mgmt_wm_health_check_protocol_listener).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case protocol(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
+ [] ->
+ Msg = <<"No active listener">>,
+ failure(Msg, Protocol, [P || #listener{protocol = P} <- Local], ReqData, Context);
+ _ ->
+ rabbit_mgmt_util:reply([{status, ok},
+ {protocol, list_to_binary(Protocol)}], ReqData, Context)
+ end.
+
+failure(Message, Missing, Protocols, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {missing, list_to_binary(Missing)},
+ {protocols, Protocols}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+protocol(ReqData) ->
+ rabbit_mgmt_util:id(protocol, ReqData).
+
+normalize_protocol(Protocol) ->
+ case string:lowercase(binary_to_list(Protocol)) of
+ "amqp091" -> "amqp";
+ "amqp0.9.1" -> "amqp";
+ "amqp0-9-1" -> "amqp";
+ "amqp0_9_1" -> "amqp";
+ "amqp10" -> "amqp";
+ "amqp1.0" -> "amqp";
+ "amqp1-0" -> "amqp";
+ "amqp1_0" -> "amqp";
+ "amqps" -> "amqp/ssl";
+ "mqtt3.1" -> "mqtt";
+ "mqtt3.1.1" -> "mqtt";
+ "mqtt31" -> "mqtt";
+ "mqtt311" -> "mqtt";
+ "mqtt3_1" -> "mqtt";
+ "mqtt3_1_1" -> "mqtt";
+ "mqtts" -> "mqtt/ssl";
+ "mqtt+tls" -> "mqtt/ssl";
+ "mqtt+ssl" -> "mqtt/ssl";
+ "stomp1.0" -> "stomp";
+ "stomp1.1" -> "stomp";
+ "stomp1.2" -> "stomp";
+ "stomp10" -> "stomp";
+ "stomp11" -> "stomp";
+ "stomp12" -> "stomp";
+ "stomp1_0" -> "stomp";
+ "stomp1_1" -> "stomp";
+ "stomp1_2" -> "stomp";
+ "stomps" -> "stomp/ssl";
+ "stomp+tls" -> "stomp/ssl";
+ "stomp+ssl" -> "stomp/ssl";
+ "https" -> "https";
+ "http1" -> "http";
+ "http1.1" -> "http";
+ "http_api" -> "http";
+ "management" -> "http";
+ "management_ui" -> "http";
+ "ui" -> "http";
+ "cli" -> "clustering";
+ "distribution" -> "clustering";
+ "webmqtt" -> "http/web-mqtt";
+ "web-mqtt" -> "http/web-mqtt";
+ "web_mqtt" -> "http/web-mqtt";
+ "webmqtt/tls" -> "https/web-mqtt";
+ "web-mqtt/tls" -> "https/web-mqtt";
+ "webmqtt/ssl" -> "https/web-mqtt";
+ "web-mqtt/ssl" -> "https/web-mqtt";
+ "webmqtt+tls" -> "https/web-mqtt";
+ "web-mqtt+tls" -> "https/web-mqtt";
+ "webmqtt+ssl" -> "https/web-mqtt";
+ "web-mqtt+ssl" -> "https/web-mqtt";
+ "webstomp" -> "http/web-stomp";
+ "web-stomp" -> "http/web-stomp";
+ "web_stomp" -> "http/web-stomp";
+ "webstomp/tls" -> "https/web-stomp";
+ "web-stomp/tls" -> "https/web-stomp";
+ "webstomp/ssl" -> "https/web-stomp";
+ "web-stomp/ssl" -> "https/web-stomp";
+ "webstomp+tls" -> "https/web-stomp";
+ "web-stomp+tls" -> "https/web-stomp";
+ "webstomp+ssl" -> "https/web-stomp";
+ "web-stomp+ssl" -> "https/web-stomp";
+ Any -> Any
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_virtual_hosts.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_virtual_hosts.erl
new file mode 100644
index 0000000000..0bc9680adc
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_health_check_virtual_hosts.erl
@@ -0,0 +1,48 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% An HTTP API counterpart of 'rabbitmq-diagnostics check_virtual_hosts'
+-module(rabbit_mgmt_wm_health_check_virtual_hosts).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {true, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ case rabbit_vhost_sup_sup:check() of
+ [] ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ Vs when length(Vs) > 0 ->
+ Msg = <<"Some virtual hosts are down">>,
+ failure(Msg, Vs, ReqData, Context)
+ end.
+
+failure(Message, Vs, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message},
+ {'virtual-hosts', Vs}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(503, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_healthchecks.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_healthchecks.erl
new file mode 100644
index 0000000000..4fc61bd0a7
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_healthchecks.erl
@@ -0,0 +1,76 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% This original One True Health Checkâ„¢ has been deprecated as too coarse-grained,
+%% intrusive and prone to false positives under load.
+-module(rabbit_mgmt_wm_healthchecks).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include("rabbit_mgmt.hrl").
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case node0(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ Node = node0(ReqData),
+ Timeout = case cowboy_req:header(<<"timeout">>, ReqData) of
+ undefined -> 70000;
+ Val -> list_to_integer(binary_to_list(Val))
+ end,
+ case rabbit_health_check:node(Node, Timeout) of
+ ok ->
+ rabbit_mgmt_util:reply([{status, ok}], ReqData, Context);
+ {badrpc, timeout} ->
+ ErrMsg = rabbit_mgmt_format:print("node ~p health check timed out", [Node]),
+ failure(ErrMsg, ReqData, Context);
+ {badrpc, Err} ->
+ failure(rabbit_mgmt_format:print("~p", Err), ReqData, Context);
+ {error_string, Err} ->
+ S = rabbit_mgmt_format:escape_html_tags(
+ rabbit_data_coercion:to_list(rabbit_mgmt_format:print(Err))),
+ failure(S, ReqData, Context)
+ end.
+
+failure(Message, ReqData, Context) ->
+ {Response, ReqData1, Context1} = rabbit_mgmt_util:reply([{status, failed},
+ {reason, Message}],
+ ReqData, Context),
+ {stop, cowboy_req:reply(?HEALTH_CHECK_FAILURE_STATUS, #{}, Response, ReqData1), Context1}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+node0(ReqData) ->
+ Node = case rabbit_mgmt_util:id(node, ReqData) of
+ none ->
+ node();
+ Node0 ->
+ list_to_atom(binary_to_list(Node0))
+ end,
+ case [N || N <- rabbit_mgmt_wm_nodes:all_nodes(ReqData),
+ proplists:get_value(name, N) == Node] of
+ [] -> not_found;
+ [_] -> Node
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_limit.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_limit.erl
new file mode 100644
index 0000000000..5f0e27c4ba
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_limit.erl
@@ -0,0 +1,64 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_limit).
+
+-export([init/2,
+ content_types_accepted/2, is_authorized/2,
+ allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"json">>, '*'}, accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ case rabbit_mgmt_util:vhost(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:not_found(vhost_not_found, ReqData0, Context);
+ VHost ->
+ rabbit_mgmt_util:with_decode(
+ [value], ReqData0, Context,
+ fun([Value], _Body, ReqData) ->
+ Name = rabbit_mgmt_util:id(name, ReqData),
+ case rabbit_vhost_limit:update_limit(VHost, Name, Value,
+ Username) of
+ ok ->
+ {true, ReqData, Context};
+ {error_string, Reason} ->
+ rabbit_mgmt_util:bad_request(
+ list_to_binary(Reason), ReqData, Context)
+ end
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ ok = rabbit_vhost_limit:clear_limit(rabbit_mgmt_util:vhost(ReqData),
+ name(ReqData), Username),
+ {true, ReqData, Context}.
+
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+name(ReqData) -> rabbit_mgmt_util:id(name, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_limits.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_limits.erl
new file mode 100644
index 0000000000..96fbf64100
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_limits.erl
@@ -0,0 +1,59 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_limits).
+
+-export([init/2, to_json/2, content_types_provided/2,
+ resource_exists/2, is_authorized/2, allowed_methods/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"GET">>, <<"OPTIONS">>], ReqData, Context}.
+
+%% Admin user can see all vhosts
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost_visible(ReqData, Context).
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(limits(ReqData, Context), [], ReqData, Context).
+
+limits(ReqData, Context) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ none ->
+ User = Context#context.user,
+ VisibleVhosts = rabbit_mgmt_util:list_visible_vhosts_names(User),
+ [ [{vhost, VHost}, {value, Value}]
+ || {VHost, Value} <- rabbit_vhost_limit:list(),
+ lists:member(VHost, VisibleVhosts) ];
+ VHost when is_binary(VHost) ->
+ case rabbit_vhost_limit:list(VHost) of
+ [] -> [];
+ Value -> [[{vhost, VHost}, {value, Value}]]
+ end
+ end.
+%%--------------------------------------------------------------------
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_login.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_login.erl
new file mode 100644
index 0000000000..fec7c99643
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_login.erl
@@ -0,0 +1,53 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_login).
+
+-export([init/2, is_authorized/2,
+ allowed_methods/2, accept_content/2, content_types_provided/2,
+ content_types_accepted/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"x-www-form-urlencoded">>, '*'}, accept_content}], ReqData, Context}.
+
+is_authorized(#{method := <<"OPTIONS">>} = ReqData, Context) ->
+ {true, ReqData, Context};
+is_authorized(ReqData0, Context) ->
+ {ok, Body, ReqData} = cowboy_req:read_urlencoded_body(ReqData0),
+ Username = proplists:get_value(<<"username">>, Body),
+ Password = proplists:get_value(<<"password">>, Body),
+ case rabbit_mgmt_util:is_authorized_user(ReqData, Context, Username, Password) of
+ {true, ReqData1, Context1} ->
+ Value = base64:encode(<<Username/binary,":",Password/binary>>),
+ {true, cowboy_req:set_resp_cookie(<<"auth">>, Value, ReqData1), Context1};
+ Other ->
+ Other
+ end.
+
+accept_content(ReqData, Context) ->
+ rabbit_mgmt_util:post_respond(do_login(ReqData, Context)).
+
+do_login(ReqData, Context) ->
+ rabbit_mgmt_util:reply(ok, ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_node.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node.erl
new file mode 100644
index 0000000000..52c1d4d9bb
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node.erl
@@ -0,0 +1,78 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_node).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case node0(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(node0(ReqData), ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_monitor(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+node0(ReqData) ->
+ Node = list_to_atom(binary_to_list(rabbit_mgmt_util:id(node, ReqData))),
+ [Data] = node_data(Node, ReqData),
+ augment(ReqData, Node, Data).
+
+augment(ReqData, Node, Data) ->
+ lists:foldl(fun (Key, DataN) -> augment(Key, ReqData, Node, DataN) end,
+ Data, [memory, binary]).
+
+augment(Key, ReqData, Node, Data) ->
+ case rabbit_mgmt_util:qs_val(list_to_binary(atom_to_list(Key)), ReqData) of
+ <<"true">> -> Res = case rpc:call(Node, rabbit_vm, Key, [], infinity) of
+ {badrpc, _} -> not_available;
+ Result -> Result
+ end,
+ [{Key, Res} | Data];
+ _ -> Data
+ end.
+
+node_data(Node, ReqData) ->
+ S = rabbit_mnesia:status(),
+ Nodes = proplists:get_value(nodes, S),
+ Running = proplists:get_value(running_nodes, S),
+ Type = find_type(Node, Nodes),
+ Basic = [[{name, Node}, {running, lists:member(Node, Running)}, {type, Type}]],
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_nodes(Basic, rabbit_mgmt_util:range_ceil(ReqData));
+ true ->
+ Basic
+ end.
+
+find_type(Node, [{Type, Nodes} | Rest]) ->
+ case lists:member(Node, Nodes) of
+ true -> Type;
+ false -> find_type(Node, Rest)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory.erl
new file mode 100644
index 0000000000..19e4237b44
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory.erl
@@ -0,0 +1,75 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_node_memory).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, [Mode]) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), {Mode, #context{}}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {node_exists(ReqData, get_node(ReqData)), ReqData, Context}.
+
+to_json(ReqData, {Mode, Context}) ->
+ rabbit_mgmt_util:reply(augment(Mode, ReqData), ReqData, {Mode, Context}).
+
+is_authorized(ReqData, {Mode, Context}) ->
+ {Res, RD, C} = rabbit_mgmt_util:is_authorized_monitor(ReqData, Context),
+ {Res, RD, {Mode, C}}.
+
+%%--------------------------------------------------------------------
+get_node(ReqData) ->
+ list_to_atom(binary_to_list(rabbit_mgmt_util:id(node, ReqData))).
+
+node_exists(ReqData, Node) ->
+ case [N || N <- rabbit_mgmt_wm_nodes:all_nodes(ReqData),
+ proplists:get_value(name, N) == Node] of
+ [] -> false;
+ [_] -> true
+ end.
+
+augment(Mode, ReqData) ->
+ Node = get_node(ReqData),
+ case node_exists(ReqData, Node) of
+ false ->
+ not_found;
+ true ->
+ case rpc:call(Node, rabbit_vm, memory, [], infinity) of
+ {badrpc, _} -> [{memory, not_available}];
+ Result -> [{memory, format(Mode, Result)}]
+ end
+ end.
+
+format(absolute, Result) ->
+ Result;
+format(relative, Result) ->
+ {value, {total, Totals}, Rest} = lists:keytake(total, 1, Result),
+ Total = proplists:get_value(rss, Totals),
+ [{total, 100} | [{K, percentage(V, Total)} || {K, V} <- Rest,
+ K =/= strategy]].
+
+percentage(Part, Total) ->
+ case round((Part/Total) * 100) of
+ 0 when Part =/= 0 ->
+ 1;
+ Int ->
+ Int
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory_ets.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory_ets.erl
new file mode 100644
index 0000000000..2bc40d22ce
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_node_memory_ets.erl
@@ -0,0 +1,85 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_node_memory_ets).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([resource_exists/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, [Mode]) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), {Mode, #context{}}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {node_exists(ReqData, get_node(ReqData)), ReqData, Context}.
+
+to_json(ReqData, {Mode, Context}) ->
+ rabbit_mgmt_util:reply(augment(Mode, ReqData), ReqData, {Mode, Context}).
+
+is_authorized(ReqData, {Mode, Context}) ->
+ {Res, RD, C} = rabbit_mgmt_util:is_authorized_monitor(ReqData, Context),
+ {Res, RD, {Mode, C}}.
+
+%%--------------------------------------------------------------------
+get_node(ReqData) ->
+ list_to_atom(binary_to_list(rabbit_mgmt_util:id(node, ReqData))).
+
+get_filter(ReqData) ->
+ case rabbit_mgmt_util:id(filter, ReqData) of
+ none -> all;
+ <<"management">> -> rabbit_mgmt_storage;
+ Other when is_binary(Other) -> list_to_atom(binary_to_list(Other));
+ _ -> all
+ end.
+
+node_exists(ReqData, Node) ->
+ case [N || N <- rabbit_mgmt_wm_nodes:all_nodes(ReqData),
+ proplists:get_value(name, N) == Node] of
+ [] -> false;
+ [_] -> true
+ end.
+
+augment(Mode, ReqData) ->
+ Node = get_node(ReqData),
+ Filter = get_filter(ReqData),
+ case node_exists(ReqData, Node) of
+ false ->
+ not_found;
+ true ->
+ case rpc:call(Node, rabbit_vm, ets_tables_memory,
+ [Filter], infinity) of
+ {badrpc, _} -> [{ets_tables_memory, not_available}];
+ [] -> [{ets_tables_memory, no_tables}];
+ Result -> [{ets_tables_memory, format(Mode, Result)}]
+ end
+ end.
+
+format(absolute, Result) ->
+ Total = lists:sum([V || {_K,V} <- Result]),
+ [{total, Total} | Result];
+format(relative, Result) ->
+ Total = lists:sum([V || {_K,V} <- Result]),
+ [{total, 100} | [{K, percentage(V, Total)} || {K, V} <- Result]].
+
+percentage(Part, Total) ->
+ case round((Part/Total) * 100) of
+ 0 when Part =/= 0 ->
+ 1;
+ Int ->
+ Int
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_nodes.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_nodes.erl
new file mode 100644
index 0000000000..c3de2e0bd2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_nodes.erl
@@ -0,0 +1,56 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_nodes).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([all_nodes/1, all_nodes_raw/0]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ rabbit_mgmt_util:reply_list(all_nodes(ReqData), ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_monitor(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+all_nodes(ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_nodes(
+ all_nodes_raw(), rabbit_mgmt_util:range_ceil(ReqData));
+ true ->
+ all_nodes_raw()
+ end.
+
+all_nodes_raw() ->
+ S = rabbit_mnesia:status(),
+ Nodes = proplists:get_value(nodes, S),
+ Types = proplists:get_keys(Nodes),
+ Running = proplists:get_value(running_nodes, S),
+ [[{name, Node}, {type, Type}, {running, lists:member(Node, Running)}] ||
+ Type <- Types, Node <- proplists:get_value(Type, Nodes)].
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policies.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policies.erl
new file mode 100644
index 0000000000..7490c427dd
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policies.erl
@@ -0,0 +1,49 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_operator_policies).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ resource_exists/2, basic/1]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case basic(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context),
+ ["priority"], ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+basic(ReqData) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> not_found;
+ none -> rabbit_policy:list_op();
+ VHost -> rabbit_policy:list_op(VHost)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policy.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policy.erl
new file mode 100644
index 0000000000..85bec3631d
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_operator_policy.erl
@@ -0,0 +1,83 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_operator_policy).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"json">>, '*'}, accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case policy(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(policy(ReqData), ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ case rabbit_mgmt_util:vhost(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:not_found(vhost_not_found, ReqData0, Context);
+ VHost ->
+ rabbit_mgmt_util:with_decode(
+ [pattern, definition], ReqData0, Context,
+ fun([Pattern, Definition], Body, ReqData) ->
+ case rabbit_policy:set_op(
+ VHost, name(ReqData), Pattern,
+ maps:to_list(Definition),
+ maps:get(priority, Body, undefined),
+ maps:get('apply-to', Body, undefined),
+ Username) of
+ ok ->
+ {true, ReqData, Context};
+ {error_string, Reason} ->
+ rabbit_mgmt_util:bad_request(
+ list_to_binary(Reason), ReqData, Context)
+ end
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ ok = rabbit_policy:delete_op(
+ rabbit_mgmt_util:vhost(ReqData), name(ReqData), Username),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+policy(ReqData) ->
+ rabbit_policy:lookup_op(
+ rabbit_mgmt_util:vhost(ReqData), name(ReqData)).
+
+name(ReqData) -> rabbit_mgmt_util:id(name, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_overview.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_overview.erl
new file mode 100644
index 0000000000..3a7e821b7d
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_overview.erl
@@ -0,0 +1,182 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_overview).
+
+-export([init/2]).
+-export([to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2, pget/3]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context = #context{user = User = #user{tags = Tags}}) ->
+ RatesMode = rabbit_mgmt_agent_config:get_env(rates_mode),
+ SRP = get_sample_retention_policies(),
+ %% NB: this duplicates what's in /nodes but we want a global idea
+ %% of this. And /nodes is not accessible to non-monitor users.
+ ExchangeTypes = lists:sort(
+ fun(ET1, ET2) ->
+ proplists:get_value(name, ET1, none)
+ =<
+ proplists:get_value(name, ET2, none)
+ end,
+ rabbit_mgmt_external_stats:list_registry_plugins(exchange)),
+ Overview0 = [{management_version, version(rabbitmq_management)},
+ {rates_mode, RatesMode},
+ {sample_retention_policies, SRP},
+ {exchange_types, ExchangeTypes},
+ {product_version, list_to_binary(rabbit:product_version())},
+ {product_name, list_to_binary(rabbit:product_name())},
+ {rabbitmq_version, list_to_binary(rabbit:base_product_version())},
+ {cluster_name, rabbit_nodes:cluster_name()},
+ {erlang_version, erlang_version()},
+ {erlang_full_version, erlang_full_version()},
+ {disable_stats, rabbit_mgmt_util:disable_stats(ReqData)},
+ {enable_queue_totals, rabbit_mgmt_util:enable_queue_totals(ReqData)}],
+ try
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ Range = rabbit_mgmt_util:range(ReqData),
+ Overview =
+ case rabbit_mgmt_util:is_monitor(Tags) of
+ true ->
+ Overview0 ++
+ [{K, maybe_map(V)} ||
+ {K,V} <- rabbit_mgmt_db:get_overview(Range)] ++
+ [{node, node()},
+ {listeners, listeners()},
+ {contexts, web_contexts(ReqData)}];
+ _ ->
+ Overview0 ++
+ [{K, maybe_map(V)} ||
+ {K, V} <- rabbit_mgmt_db:get_overview(User, Range)]
+ end,
+ rabbit_mgmt_util:reply(Overview, ReqData, Context);
+ true ->
+ VHosts = case rabbit_mgmt_util:is_monitor(Tags) of
+ true -> rabbit_vhost:list_names();
+ _ -> rabbit_mgmt_util:list_visible_vhosts_names(User)
+ end,
+
+ ObjectTotals = case rabbit_mgmt_util:is_monitor(Tags) of
+ true ->
+ [{queues, rabbit_amqqueue:count()},
+ {exchanges, rabbit_exchange:count()},
+ {connections, rabbit_connection_tracking:count()}];
+ _ ->
+ [{queues, length([Q || V <- VHosts, Q <- rabbit_amqqueue:list(V)])},
+ {exchanges, length([X || V <- VHosts, X <- rabbit_exchange:list(V)])}]
+ end,
+ Overview = Overview0 ++
+ [{node, node()},
+ {object_totals, ObjectTotals}],
+ rabbit_mgmt_util:reply(Overview, ReqData, Context)
+ end
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+version(App) ->
+ {ok, V} = application:get_key(App, vsn),
+ list_to_binary(V).
+
+listeners() ->
+ rabbit_mgmt_util:sort_list(
+ [rabbit_mgmt_format:listener(L)
+ || L <- rabbit_networking:active_listeners()],
+ ["protocol", "port", "node"] ).
+
+maybe_map(L) when is_list(L) -> maps:from_list(L);
+maybe_map(V) -> V.
+
+%%--------------------------------------------------------------------
+
+web_contexts(ReqData) ->
+ rabbit_mgmt_util:sort_list(
+ lists:append(
+ [fmt_contexts(N) || N <- rabbit_mgmt_wm_nodes:all_nodes(ReqData)]),
+ ["description", "port", "node"]).
+
+fmt_contexts(Node) ->
+ [fmt_context(Node, C) || C <- pget(contexts, Node, [])].
+
+fmt_context(Node, C) ->
+ rabbit_mgmt_format:web_context([{node, pget(name, Node)} | C]).
+
+erlang_version() -> list_to_binary(rabbit_misc:otp_release()).
+
+erlang_full_version() ->
+ list_to_binary(rabbit_misc:otp_system_version()).
+
+get_sample_retention_policies() ->
+ P = rabbit_mgmt_agent_config:get_env(sample_retention_policies),
+ get_sample_retention_policies(P).
+
+get_sample_retention_policies(undefined) ->
+ [{global, []}, {basic, []}, {detailed, []}];
+get_sample_retention_policies(Policies) ->
+ [transform_retention_policy(Pol, Policies) || Pol <- [global, basic, detailed]].
+
+transform_retention_policy(Pol, Policies) ->
+ case proplists:lookup(Pol, Policies) of
+ none ->
+ {Pol, []};
+ {Pol, Intervals} ->
+ {Pol, transform_retention_intervals(Intervals, [])}
+ end.
+
+transform_retention_intervals([], Acc) ->
+ lists:sort(Acc);
+transform_retention_intervals([{MaxAgeInSeconds, _}|Rest], Acc) ->
+ %
+ % Seconds | Interval
+ % 60 | last minute
+ % 600 | last 10 minutes
+ % 3600 | last hour
+ % 28800 | last 8 hours
+ % 86400 | last day
+ %
+ % rabbitmq/rabbitmq-management#635
+ %
+ % We check for the max age in seconds to be within 10% of the value above.
+ % The reason being that the default values are "bit higher" to accommodate
+ % edge cases (see deps/rabbitmq_management_agent/Makefile)
+ AccVal = if
+ MaxAgeInSeconds >= 0 andalso MaxAgeInSeconds =< 66 ->
+ 60;
+ MaxAgeInSeconds >= 540 andalso MaxAgeInSeconds =< 660 ->
+ 600;
+ MaxAgeInSeconds >= 3240 andalso MaxAgeInSeconds =< 3960 ->
+ 3600;
+ MaxAgeInSeconds >= 25920 andalso MaxAgeInSeconds =< 31681 ->
+ 28800;
+ MaxAgeInSeconds >= 77760 andalso MaxAgeInSeconds =< 95041 ->
+ 86400;
+ true ->
+ 0
+ end,
+ transform_retention_intervals(Rest, [AccVal|Acc]).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameter.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameter.erl
new file mode 100644
index 0000000000..17a97ca4bf
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameter.erl
@@ -0,0 +1,88 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_parameter).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case parameter(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(rabbit_mgmt_format:parameter(
+ rabbit_mgmt_wm_parameters:fix_shovel_publish_properties(parameter(ReqData))),
+ ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = User}) ->
+ case rabbit_mgmt_util:vhost(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:not_found(vhost_not_found, ReqData0, Context);
+ VHost ->
+ rabbit_mgmt_util:with_decode(
+ [value], ReqData0, Context,
+ fun([Value], _, ReqData) ->
+ case rabbit_runtime_parameters:set(
+ VHost, component(ReqData), name(ReqData),
+ if
+ is_map(Value) -> maps:to_list(Value);
+ true -> Value
+ end,
+ User) of
+ ok ->
+ {true, ReqData, Context};
+ {error_string, Reason} ->
+ S = rabbit_mgmt_format:escape_html_tags(
+ rabbit_data_coercion:to_list(Reason)),
+ rabbit_mgmt_util:bad_request(
+ rabbit_data_coercion:to_binary(S), ReqData, Context)
+ end
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ ok = rabbit_runtime_parameters:clear(
+ rabbit_mgmt_util:vhost(ReqData), component(ReqData), name(ReqData), Username),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_policies(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+parameter(ReqData) ->
+ rabbit_runtime_parameters:lookup(
+ rabbit_mgmt_util:vhost(ReqData), component(ReqData), name(ReqData)).
+
+component(ReqData) -> rabbit_mgmt_util:id(component, ReqData).
+name(ReqData) -> rabbit_mgmt_util:id(name, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameters.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameters.erl
new file mode 100644
index 0000000000..6acd7a608f
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_parameters.erl
@@ -0,0 +1,78 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_parameters).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ resource_exists/2, basic/1]).
+-export([fix_shovel_publish_properties/1]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case basic(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context),
+ ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_policies(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+%% Hackish fix to make sure we return a JSON object instead of an empty list
+%% when the publish-properties value is empty. Should be removed in 3.7.0
+%% when we switch to a new JSON library.
+fix_shovel_publish_properties(P) ->
+ case lists:keyfind(component, 1, P) of
+ {_, <<"shovel">>} ->
+ case lists:keytake(value, 1, P) of
+ {value, {_, Values}, P2} ->
+ case lists:keytake(<<"publish-properties">>, 1, Values) of
+ {_, {_, []}, Values2} ->
+ P2 ++ [{value, Values2 ++ [{<<"publish-properties">>, empty_struct}]}];
+ _ ->
+ P
+ end;
+ _ -> P
+ end;
+ _ -> P
+ end.
+
+basic(ReqData) ->
+ Raw = case rabbit_mgmt_util:id(component, ReqData) of
+ none -> rabbit_runtime_parameters:list();
+ Name -> case rabbit_mgmt_util:vhost(ReqData) of
+ none -> rabbit_runtime_parameters:list_component(
+ Name);
+ not_found -> not_found;
+ VHost -> rabbit_runtime_parameters:list(
+ VHost, Name)
+ end
+ end,
+ case Raw of
+ not_found -> not_found;
+ _ -> [rabbit_mgmt_format:parameter(fix_shovel_publish_properties(P)) || P <- Raw]
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_permission.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permission.erl
new file mode 100644
index 0000000000..74df5ab960
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permission.erl
@@ -0,0 +1,102 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_permission).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case perms(ReqData) of
+ none -> false;
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(perms(ReqData), ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ case perms(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found,
+ ReqData0, Context);
+ _ ->
+ User = rabbit_mgmt_util:id(user, ReqData0),
+ VHost = rabbit_mgmt_util:id(vhost, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [configure, write, read], ReqData0, Context,
+ fun([Conf, Write, Read], _, ReqData) ->
+ rabbit_auth_backend_internal:set_permissions(
+ User, VHost, Conf, Write, Read, Username),
+ {true, ReqData, Context}
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ VHost = rabbit_mgmt_util:id(vhost, ReqData),
+ rabbit_auth_backend_internal:clear_permissions(User, VHost, Username),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+perms(ReqData) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ case rabbit_auth_backend_internal:lookup_user(User) of
+ {ok, _} ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found ->
+ not_found;
+ VHost ->
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms =
+ rabbit_auth_backend_internal:list_user_vhost_permissions(
+ User, VHost),
+ case Perms of
+ [Rest] -> [{user, User},
+ {vhost, VHost} | Rest];
+ [] -> none
+ end
+ end,
+ fun() -> not_found end)
+ end;
+ {error, _} ->
+ not_found
+ end.
+
+
+
+
+
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions.erl
new file mode 100644
index 0000000000..ad1799b84b
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions.erl
@@ -0,0 +1,38 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_permissions).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([permissions/0]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(permissions(), ["vhost", "user"],
+ ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+permissions() ->
+ rabbit_auth_backend_internal:list_permissions().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_user.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_user.erl
new file mode 100644
index 0000000000..ad1a2e5f96
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_user.erl
@@ -0,0 +1,47 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_permissions_user).
+
+-export([init/2, to_json/2, content_types_provided/2, resource_exists/2,
+ is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_user:user(ReqData) of
+ {ok, _} -> true;
+ {error, _} -> false
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms = rabbit_auth_backend_internal:list_user_permissions(User),
+ rabbit_mgmt_util:reply_list([[{user, User} | Rest] || Rest <- Perms],
+ ["vhost", "user"], ReqData, Context)
+ end,
+ fun() ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found, ReqData, Context)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_vhost.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_vhost.erl
new file mode 100644
index 0000000000..435c15faa4
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_permissions_vhost.erl
@@ -0,0 +1,44 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_permissions_vhost).
+
+-export([init/2, to_json/2, content_types_provided/2, resource_exists/2,
+ is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {rabbit_vhost:exists(rabbit_mgmt_wm_vhost:id(ReqData)), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ VHost = rabbit_mgmt_util:id(vhost, ReqData),
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms = rabbit_auth_backend_internal:list_vhost_permissions(VHost),
+ rabbit_mgmt_util:reply_list([[{vhost, VHost} | Rest] || Rest <- Perms],
+ ["vhost", "user"], ReqData, Context)
+ end,
+ fun() ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found, ReqData, Context)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_policies.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_policies.erl
new file mode 100644
index 0000000000..c2b1bf1a1a
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_policies.erl
@@ -0,0 +1,49 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_policies).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ resource_exists/2, basic/1]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case basic(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context),
+ ["priority"], ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+basic(ReqData) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> not_found;
+ none -> rabbit_policy:list();
+ VHost -> rabbit_policy:list(VHost)
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_policy.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_policy.erl
new file mode 100644
index 0000000000..5ab8033925
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_policy.erl
@@ -0,0 +1,82 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_policy).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case policy(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(policy(ReqData), ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ case rabbit_mgmt_util:vhost(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:not_found(vhost_not_found, ReqData0, Context);
+ VHost ->
+ rabbit_mgmt_util:with_decode(
+ [pattern, definition], ReqData0, Context,
+ fun([Pattern, Definition], Body, ReqData) ->
+ case rabbit_policy:set(
+ VHost, name(ReqData), Pattern,
+ maps:to_list(Definition),
+ maps:get(priority, Body, undefined),
+ maps:get('apply-to', Body, undefined),
+ Username) of
+ ok ->
+ {true, ReqData, Context};
+ {error_string, Reason} ->
+ rabbit_mgmt_util:bad_request(
+ rabbit_mgmt_format:escape_html_tags(Reason), ReqData, Context)
+ end
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ ok = rabbit_policy:delete(
+ rabbit_mgmt_util:vhost(ReqData), name(ReqData), Username),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_policies(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+policy(ReqData) ->
+ rabbit_policy:lookup(
+ rabbit_mgmt_util:vhost(ReqData), name(ReqData)).
+
+name(ReqData) -> rabbit_mgmt_util:id(name, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue.erl
new file mode 100644
index 0000000000..6560be1524
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue.erl
@@ -0,0 +1,113 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_queue).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2, queue/1, queue/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case queue(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ [Q] = rabbit_mgmt_db:augment_queues(
+ [queue(ReqData)], rabbit_mgmt_util:range_ceil(ReqData),
+ full),
+ Payload = rabbit_mgmt_format:clean_consumer_details(
+ rabbit_mgmt_format:strip_pids(Q)),
+ rabbit_mgmt_util:reply(ensure_defaults(Payload), ReqData, Context);
+ true ->
+ rabbit_mgmt_util:reply(rabbit_mgmt_format:strip_pids(queue(ReqData)),
+ ReqData, Context)
+ end
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+accept_content(ReqData, Context) ->
+ Name = rabbit_mgmt_util:id(queue, ReqData),
+ rabbit_mgmt_util:direct_request(
+ 'queue.declare',
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{queue, Name}], "Declare queue error: ~s", ReqData, Context).
+
+delete_resource(ReqData, Context) ->
+ %% We need to retrieve manually if-unused and if-empty, as the HTTP API uses '-'
+ %% while the record uses '_'
+ IfUnused = <<"true">> =:= rabbit_mgmt_util:qs_val(<<"if-unused">>, ReqData),
+ IfEmpty = <<"true">> =:= rabbit_mgmt_util:qs_val(<<"if-empty">>, ReqData),
+ Name = rabbit_mgmt_util:id(queue, ReqData),
+ rabbit_mgmt_util:direct_request(
+ 'queue.delete',
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{queue, Name},
+ {if_unused, IfUnused},
+ {if_empty, IfEmpty}], "Delete queue error: ~s", ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+%% this is here to ensure certain data points are always there. When a queue
+%% is moved there can be transient periods where certain advanced metrics aren't
+%% yet available on the new node.
+ensure_defaults(Payload0) ->
+ case lists:keyfind(garbage_collection, 1, Payload0) of
+ {_K, _V} -> Payload0;
+ false ->
+ [{garbage_collection,
+ [{max_heap_size,-1},
+ {min_bin_vheap_size,-1},
+ {min_heap_size,-1},
+ {fullsweep_after,-1},
+ {minor_gcs,-1}]} | Payload0]
+ end.
+
+queue(ReqData) ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found -> not_found;
+ VHost -> queue(VHost, rabbit_mgmt_util:id(queue, ReqData))
+ end.
+
+
+queue(VHost, QName) ->
+ Name = rabbit_misc:r(VHost, queue, QName),
+ case rabbit_amqqueue:lookup(Name) of
+ {ok, Q} -> rabbit_mgmt_format:queue(Q);
+ {error, not_found} -> not_found
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_actions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_actions.erl
new file mode 100644
index 0000000000..c3b13f40c2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_actions.erl
@@ -0,0 +1,68 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_queue_actions).
+
+-export([init/2, resource_exists/2, is_authorized/2,
+ allowed_methods/2, content_types_accepted/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+-include_lib("rabbit/include/amqqueue.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_queue:queue(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+accept_content(ReqData, Context) ->
+ rabbit_mgmt_util:post_respond(do_it(ReqData, Context)).
+
+do_it(ReqData0, Context) ->
+ VHost = rabbit_mgmt_util:vhost(ReqData0),
+ QName = rabbit_mgmt_util:id(queue, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [action], ReqData0, Context,
+ fun([Action], _Body, ReqData) ->
+ rabbit_amqqueue:with(
+ rabbit_misc:r(VHost, queue, QName),
+ fun(Q) -> action(Action, Q, ReqData, Context) end)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+action(<<"sync">>, Q, ReqData, Context) when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ spawn(fun() -> rabbit_amqqueue:sync_mirrors(QPid) end),
+ {true, ReqData, Context};
+
+action(<<"cancel_sync">>, Q, ReqData, Context) when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ _ = rabbit_amqqueue:cancel_sync_mirrors(QPid),
+ {true, ReqData, Context};
+
+action(Else, _Q, ReqData, Context) ->
+ rabbit_mgmt_util:bad_request({unknown, Else}, ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_get.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_get.erl
new file mode 100644
index 0000000000..88ad9a0034
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_get.erl
@@ -0,0 +1,166 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_queue_get).
+
+-export([init/2, resource_exists/2, is_authorized/2,
+ allowed_methods/2, accept_content/2, content_types_provided/2,
+ content_types_accepted/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_queue:queue(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+accept_content(ReqData, Context) ->
+ rabbit_mgmt_util:post_respond(do_it(ReqData, Context)).
+
+do_it(ReqData0, Context) ->
+ VHost = rabbit_mgmt_util:vhost(ReqData0),
+ Q = rabbit_mgmt_util:id(queue, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [ackmode, count, encoding], ReqData0, Context,
+ fun([AckModeBin, CountBin, EncBin], Body, ReqData) ->
+ rabbit_mgmt_util:with_channel(
+ VHost, ReqData, Context,
+ fun (Ch) ->
+ AckMode = list_to_atom(binary_to_list(AckModeBin)),
+ Count = rabbit_mgmt_util:parse_int(CountBin),
+ Enc = case EncBin of
+ <<"auto">> -> auto;
+ <<"base64">> -> base64;
+ _ -> throw({error, <<"Unsupported encoding. Please use auto or base64.">>})
+ end,
+ Trunc = case maps:get(truncate, Body, undefined) of
+ undefined -> none;
+ TruncBin -> rabbit_mgmt_util:parse_int(
+ TruncBin)
+ end,
+
+ Reply = basic_gets(Count, Ch, Q, AckMode, Enc, Trunc),
+ maybe_rejects(Reply, Ch, AckMode),
+ rabbit_mgmt_util:reply(remove_delivery_tag(Reply),
+ ReqData, Context)
+ end)
+ end).
+
+
+
+
+basic_gets(0, _, _, _, _, _) ->
+ [];
+
+basic_gets(Count, Ch, Q, AckMode, Enc, Trunc) ->
+ case basic_get(Ch, Q, AckMode, Enc, Trunc) of
+ none -> [];
+ M -> [M | basic_gets(Count - 1, Ch, Q, AckMode, Enc, Trunc)]
+ end.
+
+
+
+ackmode_to_requeue(reject_requeue_false) -> false;
+ackmode_to_requeue(reject_requeue_true) -> true.
+
+parse_ackmode(ack_requeue_false) -> true;
+parse_ackmode(ack_requeue_true) -> false;
+parse_ackmode(reject_requeue_false) -> false;
+parse_ackmode(reject_requeue_true) -> false.
+
+
+% the messages must rejects later,
+% because we get always the same message if the
+% messages are requeued inside basic_get/5
+maybe_rejects(R, Ch, AckMode) ->
+ lists:foreach(fun(X) ->
+ maybe_reject(Ch, AckMode,
+ proplists:get_value(delivery_tag, X))
+ end, R).
+
+% removes the delivery_tag from the reply.
+% it is not necessary
+remove_delivery_tag([]) -> [];
+remove_delivery_tag([H|T]) ->
+ [proplists:delete(delivery_tag, H) | [X || X <- remove_delivery_tag(T)]].
+
+
+maybe_reject(Ch, AckMode, DeliveryTag) when AckMode == reject_requeue_true;
+ AckMode == reject_requeue_false ->
+ amqp_channel:call(Ch,
+ #'basic.reject'{delivery_tag = DeliveryTag,
+ requeue = ackmode_to_requeue(AckMode)});
+maybe_reject(_Ch, _AckMode, _DeliveryTag) -> ok.
+
+
+basic_get(Ch, Q, AckMode, Enc, Trunc) ->
+ case amqp_channel:call(Ch,
+ #'basic.get'{queue = Q,
+ no_ack = parse_ackmode(AckMode)}) of
+ {#'basic.get_ok'{redelivered = Redelivered,
+ exchange = Exchange,
+ routing_key = RoutingKey,
+ message_count = MessageCount,
+ delivery_tag = DeliveryTag},
+ #amqp_msg{props = Props, payload = Payload}} ->
+ [{payload_bytes, size(Payload)},
+ {redelivered, Redelivered},
+ {exchange, Exchange},
+ {routing_key, RoutingKey},
+ {message_count, MessageCount},
+ {delivery_tag, DeliveryTag},
+ {properties, rabbit_mgmt_format:basic_properties(Props)}] ++
+ payload_part(maybe_truncate(Payload, Trunc), Enc);
+ #'basic.get_empty'{} ->
+ none
+ end.
+
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+maybe_truncate(Payload, none) -> Payload;
+maybe_truncate(Payload, Len) when size(Payload) < Len -> Payload;
+maybe_truncate(Payload, Len) ->
+ <<Start:Len/binary, _Rest/binary>> = Payload,
+ Start.
+
+payload_part(Payload, Enc) ->
+ {PL, E} = case Enc of
+ auto -> case is_utf8(Payload) of
+ true -> {Payload, string};
+ false -> {base64:encode(Payload), base64}
+ end;
+ _ -> {base64:encode(Payload), base64}
+ end,
+ [{payload, PL}, {payload_encoding, E}].
+
+is_utf8(<<>>) -> true;
+is_utf8(<<_/utf8, Rest/bits>>) -> is_utf8(Rest);
+is_utf8(_) -> false.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_purge.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_purge.erl
new file mode 100644
index 0000000000..b250984d11
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queue_purge.erl
@@ -0,0 +1,42 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_queue_purge).
+
+-export([init/2, resource_exists/2, is_authorized/2, allowed_methods/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_queue:queue(ReqData) of
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+delete_resource(ReqData, Context) ->
+ Name = rabbit_mgmt_util:id(queue, ReqData),
+ rabbit_mgmt_util:direct_request(
+ 'queue.purge',
+ fun rabbit_mgmt_format:format_accept_content/1,
+ [{queue, Name}], "Error purging queue: ~s", ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_queues.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queues.erl
new file mode 100644
index 0000000000..7df50f61d9
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_queues.erl
@@ -0,0 +1,113 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_queues).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2,
+ resource_exists/2, basic/1]).
+-export([variances/2,
+ augmented/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("rabbit/include/amqqueue.hrl").
+
+-define(BASIC_COLUMNS, ["vhost", "name", "durable", "auto_delete", "exclusive",
+ "owner_pid", "arguments", "pid", "state"]).
+
+-define(DEFAULT_SORT, ["vhost", "name"]).
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case queues0(ReqData) of
+ vhost_not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ Basic = basic_vhost_filtered(ReqData, Context),
+ Data = rabbit_mgmt_util:augment_resources(Basic, ?DEFAULT_SORT,
+ ?BASIC_COLUMNS, ReqData,
+ Context, fun augment/2),
+ rabbit_mgmt_util:reply(Data, ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData,
+ Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost(ReqData, Context).
+
+%%--------------------------------------------------------------------
+%% Exported functions
+
+basic(ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ [rabbit_mgmt_format:queue(Q) || Q <- queues0(ReqData)] ++
+ [rabbit_mgmt_format:queue(amqqueue:set_state(Q, down)) ||
+ Q <- down_queues(ReqData)];
+ true ->
+ case rabbit_mgmt_util:enable_queue_totals(ReqData) of
+ false ->
+ [rabbit_mgmt_format:queue(Q) ++ policy(Q) || Q <- queues0(ReqData)] ++
+ [rabbit_mgmt_format:queue(amqqueue:set_state(Q, down)) ||
+ Q <- down_queues(ReqData)];
+ true ->
+ [rabbit_mgmt_format:queue_info(Q) || Q <- queues_with_totals(ReqData)] ++
+ [rabbit_mgmt_format:queue(amqqueue:set_state(Q, down)) ||
+ Q <- down_queues(ReqData)]
+ end
+ end.
+
+augmented(ReqData, Context) ->
+ augment(rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context), ReqData).
+
+%%--------------------------------------------------------------------
+%% Private helpers
+
+augment(Basic, ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_queues(Basic, rabbit_mgmt_util:range_ceil(ReqData),
+ basic);
+ true ->
+ Basic
+ end.
+
+basic_vhost_filtered(ReqData, Context) ->
+ rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context).
+
+queues0(ReqData) ->
+ rabbit_mgmt_util:all_or_one_vhost(ReqData, fun rabbit_amqqueue:list/1).
+
+queues_with_totals(ReqData) ->
+ rabbit_mgmt_util:all_or_one_vhost(ReqData, fun collect_info_all/1).
+
+collect_info_all(VHostPath) ->
+ rabbit_amqqueue:collect_info_all(VHostPath, [name, durable, auto_delete, exclusive, owner_pid, arguments, type, state, policy, totals, type_specific]).
+
+down_queues(ReqData) ->
+ rabbit_mgmt_util:all_or_one_vhost(ReqData, fun rabbit_amqqueue:list_down/1).
+
+policy(Q) ->
+ case rabbit_policy:name(Q) of
+ none -> [];
+ Policy -> [{policy, Policy}]
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_rebalance_queues.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_rebalance_queues.erl
new file mode 100644
index 0000000000..982655493c
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_rebalance_queues.erl
@@ -0,0 +1,58 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_rebalance_queues).
+
+-export([init/2, service_available/2, resource_exists/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, [Mode]) ->
+ Headers = rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE),
+ {cowboy_rest, Headers, {Mode, #context{}}}.
+
+service_available(Req, {{queues, all}, _Context}=State) ->
+ {true, Req, State};
+service_available(Req, State) ->
+ {false, Req, State}.
+
+variances(Req, State) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, State}.
+
+content_types_provided(Req, State) ->
+ {[{{<<"text">>, <<"plain">>, '*'}, undefined}], Req, State}.
+
+content_types_accepted(Req, State) ->
+ {[{'*', accept_content}], Req, State}.
+
+allowed_methods(Req, State) ->
+ {[<<"POST">>, <<"OPTIONS">>], Req, State}.
+
+resource_exists(Req, State) ->
+ {true, Req, State}.
+
+accept_content(Req, {_Mode, #context{user = #user{username = Username}}}=State) ->
+ try
+ rabbit_log:info("User '~s' has initiated a queue rebalance", [Username]),
+ spawn(fun() ->
+ rabbit_amqqueue:rebalance(all, <<".*">>, <<".*">>)
+ end),
+ {true, Req, State}
+ catch
+ {error, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), Req, State)
+ end.
+
+is_authorized(Req0, {Mode, Context0}) ->
+ {Res, Req1, Context1} = rabbit_mgmt_util:is_authorized_admin(Req0, Context0),
+ {Res, Req1, {Mode, Context1}}.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_redirect.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_redirect.erl
new file mode 100644
index 0000000000..c5b3808d5a
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_redirect.erl
@@ -0,0 +1,13 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2010-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_redirect).
+-export([init/2]).
+
+init(Req0, RedirectTo) ->
+ Req = cowboy_req:reply(301, #{<<"location">> => RedirectTo}, Req0),
+ {ok, Req, RedirectTo}.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_reset.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_reset.erl
new file mode 100644
index 0000000000..ea3d698ece
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_reset.erl
@@ -0,0 +1,54 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_reset).
+
+-export([init/2, is_authorized/2, resource_exists/2,
+ allowed_methods/2, delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ case get_node(ReqData) of
+ none -> {true, ReqData, Context};
+ {ok, Node} -> {lists:member(Node, rabbit_nodes:all_running()),
+ ReqData, Context}
+ end.
+
+delete_resource(ReqData, Context) ->
+ case get_node(ReqData) of
+ none ->
+ rabbit_mgmt_storage:reset_all();
+ {ok, Node} ->
+ rpc:call(Node, rabbit_mgmt_storage, reset, [])
+ end,
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+
+get_node(ReqData) ->
+ case rabbit_mgmt_util:id(node, ReqData) of
+ none ->
+ none;
+ Node0 ->
+ Node = list_to_atom(binary_to_list(Node0)),
+ {ok, Node}
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl
new file mode 100644
index 0000000000..4a7f608a8a
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl
@@ -0,0 +1,65 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2010-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+%% Alias for cowboy_static that accepts a list of directories
+%% where static files can be found.
+
+-module(rabbit_mgmt_wm_static).
+
+-include_lib("kernel/include/file.hrl").
+
+-export([init/2]).
+-export([malformed_request/2]).
+-export([forbidden/2]).
+-export([content_types_provided/2]).
+-export([resource_exists/2]).
+-export([last_modified/2]).
+-export([generate_etag/2]).
+-export([get_file/2]).
+
+
+init(Req0, {priv_file, _App, _Path}=Opts) ->
+ Req1 = rabbit_mgmt_headers:set_common_permission_headers(Req0, ?MODULE),
+ cowboy_static:init(Req1, Opts);
+init(Req0, [{App, Path}]) ->
+ Req1 = rabbit_mgmt_headers:set_common_permission_headers(Req0, ?MODULE),
+ do_init(Req1, App, Path);
+init(Req0, [{App, Path}|Tail]) ->
+ Req1 = rabbit_mgmt_headers:set_common_permission_headers(Req0, ?MODULE),
+ PathInfo = cowboy_req:path_info(Req1),
+ Filepath = filename:join([code:priv_dir(App), Path|PathInfo]),
+ %% We use erl_prim_loader because the file may be inside an .ez archive.
+ FileInfo = erl_prim_loader:read_file_info(binary_to_list(Filepath)),
+ case FileInfo of
+ {ok, #file_info{type = regular}} -> do_init(Req1, App, Path);
+ {ok, #file_info{type = symlink}} -> do_init(Req1, App, Path);
+ _ -> init(Req0, Tail)
+ end.
+
+do_init(Req, App, Path) ->
+ cowboy_static:init(Req, {priv_dir, App, Path}).
+
+malformed_request(Req, State) ->
+ cowboy_static:malformed_request(Req, State).
+
+forbidden(Req, State) ->
+ cowboy_static:forbidden(Req, State).
+
+content_types_provided(Req, State) ->
+ cowboy_static:content_types_provided(Req, State).
+
+resource_exists(Req, State) ->
+ cowboy_static:resource_exists(Req, State).
+
+last_modified(Req, State) ->
+ cowboy_static:last_modified(Req, State).
+
+generate_etag(Req, State) ->
+ cowboy_static:generate_etag(Req, State).
+
+get_file(Req, State) ->
+ cowboy_static:get_file(Req, State).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permission.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permission.erl
new file mode 100644
index 0000000000..99f25bb69e
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permission.erl
@@ -0,0 +1,103 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_topic_permission).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case topic_perms(ReqData) of
+ none -> false;
+ not_found -> false;
+ _ -> true
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply(topic_perms(ReqData), ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ case topic_perms(ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found,
+ ReqData0, Context);
+ _ ->
+ User = rabbit_mgmt_util:id(user, ReqData0),
+ VHost = rabbit_mgmt_util:id(vhost, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [exchange, write, read], ReqData0, Context,
+ fun([Exchange, Write, Read], _, ReqData) ->
+ rabbit_auth_backend_internal:set_topic_permissions(
+ User, VHost, Exchange, Write, Read, Username),
+ {true, ReqData, Context}
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ VHost = rabbit_mgmt_util:id(vhost, ReqData),
+ case rabbit_mgmt_util:id(exchange, ReqData) of
+ none ->
+ rabbit_auth_backend_internal:clear_topic_permissions(User, VHost, Username);
+ Exchange ->
+ rabbit_auth_backend_internal:clear_topic_permissions(User, VHost, Exchange,
+ Username)
+ end,
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+topic_perms(ReqData) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ case rabbit_auth_backend_internal:lookup_user(User) of
+ {ok, _} ->
+ case rabbit_mgmt_util:vhost(ReqData) of
+ not_found ->
+ not_found;
+ VHost ->
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms =
+ rabbit_auth_backend_internal:list_user_vhost_topic_permissions(
+ User, VHost),
+ case Perms of
+ [] -> none;
+ TopicPermissions -> [[{user, User}, {vhost, VHost} | TopicPermission]
+ || TopicPermission <- TopicPermissions]
+ end
+ end,
+ fun() -> not_found end)
+ end;
+ {error, _} ->
+ not_found
+ end.
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions.erl
new file mode 100644
index 0000000000..c1406043cd
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions.erl
@@ -0,0 +1,38 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_topic_permissions).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([topic_permissions/0]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(topic_permissions(), ["vhost", "user"],
+ ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+topic_permissions() ->
+ rabbit_auth_backend_internal:list_topic_permissions().
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_user.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_user.erl
new file mode 100644
index 0000000000..7d9c2d58fc
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_user.erl
@@ -0,0 +1,47 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_topic_permissions_user).
+
+-export([init/2, to_json/2, content_types_provided/2, resource_exists/2,
+ is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case rabbit_mgmt_wm_user:user(ReqData) of
+ {ok, _} -> true;
+ {error, _} -> false
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms = rabbit_auth_backend_internal:list_user_topic_permissions(User),
+ rabbit_mgmt_util:reply_list([[{user, User} | Rest] || Rest <- Perms],
+ ["vhost", "user"], ReqData, Context)
+ end,
+ fun() ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found, ReqData, Context)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_vhost.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_vhost.erl
new file mode 100644
index 0000000000..8540ee9a77
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_topic_permissions_vhost.erl
@@ -0,0 +1,44 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_topic_permissions_vhost).
+
+-export([init/2, to_json/2, content_types_provided/2, resource_exists/2,
+ is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {rabbit_vhost:exists(rabbit_mgmt_wm_vhost:id(ReqData)), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ VHost = rabbit_mgmt_util:id(vhost, ReqData),
+ rabbit_mgmt_util:catch_no_such_user_or_vhost(
+ fun() ->
+ Perms = rabbit_auth_backend_internal:list_vhost_topic_permissions(VHost),
+ rabbit_mgmt_util:reply_list([[{vhost, VHost} | Rest] || Rest <- Perms],
+ ["vhost", "user"], ReqData, Context)
+ end,
+ fun() ->
+ rabbit_mgmt_util:bad_request(vhost_or_user_not_found, ReqData, Context)
+ end).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_user.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user.erl
new file mode 100644
index 0000000000..4c014565f1
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user.erl
@@ -0,0 +1,73 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_user).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2, user/1, put_user/2, put_user/3]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case user(ReqData) of
+ {ok, _} -> true;
+ {error, _} -> false
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ {ok, User} = user(ReqData),
+ rabbit_mgmt_util:reply(rabbit_mgmt_format:internal_user(User),
+ ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = ActingUser}}) ->
+ Username = rabbit_mgmt_util:id(user, ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [], ReqData0, Context,
+ fun(_, User, ReqData) ->
+ put_user(User#{name => Username}, ActingUser),
+ {true, ReqData, Context}
+ end).
+
+delete_resource(ReqData, Context = #context{user = #user{username = ActingUser}}) ->
+ User = rabbit_mgmt_util:id(user, ReqData),
+ rabbit_auth_backend_internal:delete_user(User, ActingUser),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+user(ReqData) ->
+ rabbit_auth_backend_internal:lookup_user(rabbit_mgmt_util:id(user, ReqData)).
+
+put_user(User, ActingUser) ->
+ put_user(User, undefined, ActingUser).
+
+put_user(User, Version, ActingUser) ->
+ rabbit_auth_backend_internal:put_user(User, Version, ActingUser).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limit.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limit.erl
new file mode 100644
index 0000000000..8e59c55371
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limit.erl
@@ -0,0 +1,62 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_user_limit).
+
+-export([init/2,
+ content_types_accepted/2, is_authorized/2,
+ allowed_methods/2, accept_content/2,
+ delete_resource/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{{<<"application">>, <<"json">>, '*'}, accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+accept_content(ReqData0, Context = #context{user = #user{username = ActingUser}}) ->
+ case rabbit_mgmt_util:id(user, ReqData0) of
+ not_found ->
+ rabbit_mgmt_util:not_found(user_not_found, ReqData0, Context);
+ Username ->
+ rabbit_mgmt_util:with_decode(
+ [value], ReqData0, Context,
+ fun([Value], _Body, ReqData) ->
+ Limit = #{name(ReqData) => Value},
+ case rabbit_auth_backend_internal:set_user_limits(Username, Limit, ActingUser) of
+ ok ->
+ {true, ReqData, Context};
+ {error_string, Reason} ->
+ rabbit_mgmt_util:bad_request(
+ list_to_binary(Reason), ReqData, Context)
+ end
+ end)
+ end.
+
+delete_resource(ReqData, Context = #context{user = #user{username = ActingUser}}) ->
+ ok = rabbit_auth_backend_internal:clear_user_limits(
+ rabbit_mgmt_util:id(user, ReqData), name(ReqData), ActingUser),
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+name(ReqData) -> rabbit_mgmt_util:id(name, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limits.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limits.erl
new file mode 100644
index 0000000000..7e4d541f32
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_user_limits.erl
@@ -0,0 +1,63 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_user_limits).
+
+-export([init/2, to_json/2, content_types_provided/2,
+ resource_exists/2, is_authorized/2, allowed_methods/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"GET">>, <<"OPTIONS">>], ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_vhost_visible(ReqData, Context).
+
+content_types_provided(ReqData, Context) ->
+ {[{<<"application/json">>, to_json}], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {case user(ReqData) of
+ none -> true;
+ Username -> rabbit_auth_backend_internal:exists(Username)
+ end, ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ rabbit_mgmt_util:reply_list(limits(ReqData, Context), [], ReqData, Context).
+
+limits(ReqData, _Context) ->
+ case user(ReqData) of
+ none ->
+ [ [{user, internal_user:get_username(U)}, {value, maps:to_list(internal_user:get_limits(U))}]
+ || U <- rabbit_auth_backend_internal:all_users(),
+ internal_user:get_limits(U) =/= #{}];
+ Username when is_binary(Username) ->
+ case rabbit_auth_backend_internal:get_user_limits(Username) of
+ Value when is_map(Value) ->
+ case maps:size(Value) of
+ 0 -> [];
+ _ -> [[{user, Username}, {value, maps:to_list(Value)}]]
+ end;
+ undefined ->
+ []
+ end
+ end.
+%%--------------------------------------------------------------------
+
+user(ReqData) ->
+ rabbit_mgmt_util:id(user, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_users.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_users.erl
new file mode 100644
index 0000000000..67b8345ab9
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_users.erl
@@ -0,0 +1,59 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_users).
+
+-export([init/2, to_json/2,
+ content_types_provided/2,
+ is_authorized/2, allowed_methods/2]).
+-export([variances/2]).
+-export([users/1]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, [Mode]) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), {Mode, #context{}}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"OPTIONS">>], ReqData, Context}.
+
+to_json(ReqData, {Mode, Context}) ->
+ rabbit_mgmt_util:reply_list(users(Mode), ReqData, Context).
+
+is_authorized(ReqData, {Mode, Context}) ->
+ {Res, Req2, Context2} = rabbit_mgmt_util:is_authorized_admin(ReqData, Context),
+ {Res, Req2, {Mode, Context2}}.
+
+%%--------------------------------------------------------------------
+
+users(all) ->
+ [begin
+ {ok, User} = rabbit_auth_backend_internal:lookup_user(pget(user, U)),
+ rabbit_mgmt_format:internal_user(User)
+ end || U <- rabbit_auth_backend_internal:list_users()];
+users(without_permissions) ->
+ lists:foldl(fun(U, Acc) ->
+ Username = pget(user, U),
+ case rabbit_auth_backend_internal:list_user_permissions(Username) of
+ [] ->
+ {ok, User} = rabbit_auth_backend_internal:lookup_user(Username),
+ [rabbit_mgmt_format:internal_user(User) | Acc];
+ _ ->
+ Acc
+ end
+ end, [], rabbit_auth_backend_internal:list_users()).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_users_bulk_delete.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_users_bulk_delete.erl
new file mode 100644
index 0000000000..6f6a7863a2
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_users_bulk_delete.erl
@@ -0,0 +1,47 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_users_bulk_delete).
+
+-export([init/2,
+ content_types_accepted/2,
+ content_types_provided/2,
+ is_authorized/2,
+ allowed_methods/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+accept_content(ReqData0, Context = #context{user = #user{username = ActingUser}}) ->
+ rabbit_mgmt_util:with_decode(
+ [users], ReqData0, Context,
+ fun([Users], _, ReqData) ->
+ [rabbit_auth_backend_internal:delete_user(User, ActingUser)
+ || User <- Users],
+ {true, ReqData, Context}
+ end).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost.erl
new file mode 100644
index 0000000000..e62eecac97
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost.erl
@@ -0,0 +1,97 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_vhost).
+
+-export([init/2, resource_exists/2, to_json/2,
+ content_types_provided/2, content_types_accepted/2,
+ is_authorized/2, allowed_methods/2, accept_content/2,
+ delete_resource/2, id/1, put_vhost/5]).
+-export([variances/2]).
+
+-import(rabbit_misc, [pget/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"HEAD">>, <<"GET">>, <<"PUT">>, <<"DELETE">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ {rabbit_vhost:exists(id(ReqData)), ReqData, Context}.
+
+to_json(ReqData, Context) ->
+ try
+ Id = id(ReqData),
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_util:reply(
+ hd(rabbit_mgmt_db:augment_vhosts(
+ [rabbit_vhost:info(Id)], rabbit_mgmt_util:range(ReqData))),
+ ReqData, Context);
+ true ->
+ rabbit_mgmt_util:reply(rabbit_vhost:info(Id), ReqData, Context)
+ end
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+accept_content(ReqData0, Context = #context{user = #user{username = Username}}) ->
+ Name = id(ReqData0),
+ rabbit_mgmt_util:with_decode(
+ [], ReqData0, Context,
+ fun(_, BodyMap, ReqData) ->
+ Trace = rabbit_mgmt_util:parse_bool(maps:get(tracing, BodyMap, undefined)),
+ Description = maps:get(description, BodyMap, <<"">>),
+ Tags = maps:get(tags, BodyMap, <<"">>),
+ case put_vhost(Name, Description, Tags, Trace, Username) of
+ ok ->
+ {true, ReqData, Context};
+ {error, timeout} = E ->
+ rabbit_mgmt_util:internal_server_error(
+ "Timed out while waiting for the vhost to initialise", E,
+ ReqData0, Context)
+ end
+ end).
+
+delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
+ VHost = id(ReqData),
+ try
+ rabbit_vhost:delete(VHost, Username)
+ catch _:{error, {no_such_vhost, _}} ->
+ ok
+ end,
+ {true, ReqData, Context}.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+id(ReqData) ->
+ case rabbit_mgmt_util:id(vhost, ReqData) of
+ [Value] -> Value;
+ Value -> Value
+ end.
+
+put_vhost(Name, Description, Tags, Trace, Username) ->
+ rabbit_vhost:put_vhost(Name, Description, Tags, Trace, Username).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost_restart.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost_restart.erl
new file mode 100644
index 0000000000..e22ff058c7
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost_restart.erl
@@ -0,0 +1,56 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2011-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_vhost_restart).
+
+-export([init/2, resource_exists/2, is_authorized/2,
+ allowed_methods/2, content_types_accepted/2, accept_content/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+allowed_methods(ReqData, Context) ->
+ {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}.
+
+resource_exists(ReqData, Context) ->
+ VHost = id(ReqData),
+ {rabbit_vhost:exists(VHost), ReqData, Context}.
+
+content_types_accepted(ReqData, Context) ->
+ {[{'*', accept_content}], ReqData, Context}.
+
+accept_content(ReqData, Context) ->
+ VHost = id(ReqData),
+ NodeB = rabbit_mgmt_util:id(node, ReqData),
+ Node = binary_to_atom(NodeB, utf8),
+ case rabbit_vhost_sup_sup:start_vhost(VHost, Node) of
+ {ok, _} ->
+ {true, ReqData, Context};
+ {error, {already_started, _}} ->
+ {true, ReqData, Context};
+ {error, Err} ->
+ Message = io_lib:format("Request to node ~s failed with ~p",
+ [Node, Err]),
+ rabbit_mgmt_util:bad_request(list_to_binary(Message), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized_admin(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+id(ReqData) ->
+ rabbit_mgmt_util:id(vhost, ReqData).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhosts.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhosts.erl
new file mode 100644
index 0000000000..7380faa4f0
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_vhosts.erl
@@ -0,0 +1,69 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_vhosts).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+-export([basic/0, augmented/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-define(BASIC_COLUMNS, ["name", "tracing", "pid"]).
+
+-define(DEFAULT_SORT, ["name"]).
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context = #context{user = User}) ->
+ try
+ Basic = [rabbit_vhost:info(V)
+ || V <- rabbit_mgmt_util:list_visible_vhosts(User)],
+ Data = rabbit_mgmt_util:augment_resources(Basic, ?DEFAULT_SORT,
+ ?BASIC_COLUMNS, ReqData,
+ Context, fun augment/2),
+ rabbit_mgmt_util:reply(Data, ReqData, Context)
+ catch
+ {error, invalid_range_parameters, Reason} ->
+ rabbit_mgmt_util:bad_request(iolist_to_binary(Reason), ReqData, Context)
+ end.
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).
+
+%%--------------------------------------------------------------------
+
+augment(Basic, ReqData) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_vhosts(Basic, rabbit_mgmt_util:range(ReqData));
+ true ->
+ Basic
+ end.
+
+augmented(ReqData, #context{user = User}) ->
+ case rabbit_mgmt_util:disable_stats(ReqData) of
+ false ->
+ rabbit_mgmt_db:augment_vhosts(
+ [rabbit_vhost:info(V) || V <- rabbit_mgmt_util:list_visible_vhosts(User)],
+ rabbit_mgmt_util:range(ReqData));
+ true ->
+ [rabbit_vhost:info(V) || V <- rabbit_mgmt_util:list_visible_vhosts(User)]
+ end.
+
+basic() ->
+ rabbit_vhost:info_all([name]).
diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_whoami.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_whoami.erl
new file mode 100644
index 0000000000..22a1063443
--- /dev/null
+++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_whoami.erl
@@ -0,0 +1,36 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_mgmt_wm_whoami).
+
+-export([init/2, to_json/2, content_types_provided/2, is_authorized/2]).
+-export([variances/2]).
+
+-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+%%--------------------------------------------------------------------
+
+init(Req, _State) ->
+ {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}.
+
+variances(Req, Context) ->
+ {[<<"accept-encoding">>, <<"origin">>], Req, Context}.
+
+content_types_provided(ReqData, Context) ->
+ {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}.
+
+to_json(ReqData, Context = #context{user = User}) ->
+ FormattedUser = rabbit_mgmt_format:user(User),
+ Expiration = case application:get_env(rabbitmq_management, login_session_timeout) of
+ undefined -> [];
+ {ok, Val} -> [{login_session_timeout, Val}]
+ end,
+ rabbit_mgmt_util:reply(FormattedUser ++ Expiration, ReqData, Context).
+
+is_authorized(ReqData, Context) ->
+ rabbit_mgmt_util:is_authorized(ReqData, Context).