Merge bug23580 (Add statistics for 'basic.return')

This commit is contained in:
Simon MacMullen 2011-02-28 10:59:59 +00:00
commit 317d3f85f8
20 changed files with 248 additions and 109 deletions

View File

@ -1,5 +1,12 @@
This package, the RabbitMQ Management Plugin is licensed under the MPL. For the
MPL, please see LICENSE-MPL-RabbitMQ.
This package makes use of the following third party libraries:
jQuery - http://jquery.com/ - MIT license
EJS - http://embeddedjs.com/ - MIT license
Sammy - http://code.quirkey.com/sammy/ - MIT license
webmachine - http://webmachine.basho.com/ - Apache license, 2.0
mochiweb - http://github.com/mochi/mochiweb/ - MIT license
If you have any questions regarding licensing, please contact us at
info@rabbitmq.com.

View File

@ -277,7 +277,7 @@ Content-Length: 0</pre>
<td></td>
<td class="path">/api/queues/<i>vhost</i>/<i>name</i></td>
<td>An individual queue. To PUT a queue, you will need a body looking something like this:
<pre>{"auto_delete":false,"durable":true,"arguments":[]}</pre>
<pre>{"auto_delete":false,"durable":true,"arguments":[],"node":"rabbit@smacmullen"}</pre>
All keys are optional.</td>
</tr>
<tr>
@ -382,7 +382,13 @@ Content-Length: 0</pre>
<td class="path">/api/users/<i>name</i></td>
<td>An individual user. To PUT a user, you will need a body looking something like this:
<pre>{"password":"secret", "administrator":true}</pre>
All keys are mandatory.</td>
or:
<pre>{"password_hash":"2lmoth8l4H0DViLaK9Fxi6l9ds8=", "administrator":true}</pre>
The <code>administrator</code> key is mandatory. Either
<code>password</code> or <code>password_hash</code>
must be set. Setting <code>password_hash</code> to "" will ensure the
user cannot use a password to log in.
</td>
</tr>
<tr>
<td>X</td>

View File

@ -48,8 +48,11 @@ table.facts th, table.facts td { vertical-align: top; padding: 0 10px 10px 10px;
table.facts-long th { text-align: right; font-weight: bold; }
table.facts-long th, table.facts-long td { vertical-align: top; }
tr.alt1 td { background: #eee; }
tr.alt2 td { background: #fff; }
table.mini th { border: none; padding: 0 2px 2px 2px; text-align: right; }
table.mini td { border: none; padding: 0 2px 2px 2px; }
tr.alt1>td { background: #eee; }
tr.alt2>td { background: #fff; }
td.status div { padding: 5px; text-align: center; border-radius: 5px; -moz-border-radius: 5px; }
td.status div.red { background: #F62817; color:white; }
@ -122,12 +125,12 @@ td.binding-endpoint span.arrow { font-size: 200%; }
#scratch { display: none; }
tr.alt1 td {
tr.alt1>td {
background: -moz-linear-gradient(center top, #f0f0f0 0%,#e0e0e0 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #f0f0f0),color-stop(1, #e0e0e0));
}
tr.alt2 td {
tr.alt2>td {
background: -moz-linear-gradient(center top, #f8f8f8 0%,#ffffff 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #f8f8f8),color-stop(1, #ffffff));
}

View File

@ -1,6 +1,10 @@
UNKNOWN_REPR = '<span class="unknown">?</span>';
DESCRIPTOR_THRESHOLDS=[[0.75, 'red'],
[0.5, 'yellow']];
FD_THRESHOLDS=[[0.95, 'red'],
[0.8, 'yellow']];
SOCKETS_THRESHOLDS=[[1.0, 'red'],
[0.8, 'yellow']];
PROCESS_THRESHOLDS=[[0.75, 'red'],
[0.5, 'yellow']];
MEMORY_THRESHOLDS=[[1.0, 'red']];
function fmt_string(str) {
@ -57,7 +61,7 @@ function fmt_parameters(obj) {
}
var args = fmt_table_short(obj.arguments);
if (args != '') {
res += '<p>' + args + '</p>';
res += args;
}
return res;
}
@ -76,15 +80,14 @@ function fmt_channel_mode(ch) {
function fmt_color(r, thresholds) {
if (r == undefined) return '';
if (thresholds == undefined) thresholds = DESCRIPTOR_THRESHOLDS;
for (var i in thresholds) {
var threshold = thresholds[i][0];
var color = thresholds[i][1];
var threshold = thresholds[i][0];
var color = thresholds[i][1];
if (r > threshold) {
return color;
}
if (r >= threshold) {
return color;
}
}
return 'green';
}
@ -142,19 +145,31 @@ function fmt_download_filename(host) {
}
function fmt_table_short(table) {
return '<table class="mini">' + fmt_table_body(table, ':') + '</table>';
}
function fmt_table_long(table) {
return '<table class="facts">' + fmt_table_body(table, '') +
'</table><span class="br"></span>';
}
function fmt_table_body(table, x) {
var res = '';
for (k in table) {
res += k + '=' + table[k] + '<br/>';
res += '<tr><th>' + k + x + '</th><td>' + fmt_amqp_value(table[k]) +
'</td>';
}
return res;
}
function fmt_table_long(table) {
var res = '<table class="facts">';
for (k in table) {
res += '<tr><th>' + k + '</th><td>' + table[k] + '</td>';
function fmt_amqp_value(val) {
if (val instanceof Array) {
return val.join("<br/>");
} else if (val instanceof Object) {
return fmt_table_short(val);
} else {
return val;
}
return res + '</table><span class="br"></span>';
}
function fmt_uptime(u) {
@ -290,4 +305,4 @@ function fmt_sort(display, sort) {
'</span>';
}
return '<a class="sort" sort="' + sort + '">' + prefix + display + '</a>';
}
}

View File

@ -46,6 +46,19 @@ HELP = {
<dd>Channel is transactional.</dd> \
<dl>',
'file-descriptors':
'File descriptor count and limit, as reported by the operating system. \
The count includes network sockets and file handlers.<br/> \
To optimize disk access RabbitMQ uses as many free descriptors as are \
available, so the count may safely approach the limit. \
However, if most of the file descriptors are used by sockets then \
persister performance will be negatively impacted.',
'socket-descriptors':
'The network sockets count and limit managed by RabbitMQ.<br/> \
When the limit is exhausted RabbitMQ will stop accepting new \
network connections.',
'foo': 'foo' // No comma.
};

View File

@ -143,7 +143,7 @@ function dispatcher() {
return false;
});
path('#/queues', {'queues': '/queues', 'vhosts': '/vhosts'}, 'queues');
path('#/queues', {'queues': '/queues', 'vhosts': '/vhosts', 'nodes': '/nodes'}, 'queues');
this.get('#/queues/:vhost/:name', function() {
var path = '/queues/' + esc(this.params['vhost']) + '/' + esc(this.params['name']);
render({'queue': path,
@ -411,7 +411,7 @@ function postprocess() {
}
});
$('#download-configuration').click(function() {
var path = '/api/all-configuration?download=' +
var path = '../api/all-configuration?download=' +
esc($('#download-filename').val());
window.location = path;
setTimeout('app.run()');
@ -536,7 +536,7 @@ function toggle_visibility(item) {
function with_reqs(reqs, acc, fun) {
if (keys(reqs).length > 0) {
var key = keys(reqs)[0];
with_req('/api' + reqs[key], function(resp) {
with_req('../api' + reqs[key], function(resp) {
acc[key] = jQuery.parseJSON(resp.responseText);
var remainder = {};
for (var k in reqs) {
@ -641,7 +641,7 @@ function sync_req(type, params0, path_template) {
return false;
}
var req = xmlHttpRequest();
req.open(type, '/api' + path, false);
req.open(type, '../api' + path, false);
req.setRequestHeader('content-type', 'application/json');
try {
if (type == 'GET')
@ -689,7 +689,8 @@ function fill_path_template(template, params) {
}
// Better suggestions appreciated
var INTEGER_ARGUMENTS = map(['x-expires']);
var INTEGER_ARGUMENTS = map(['x-expires', 'x-message-ttl']);
var ARRAY_ARGUMENTS = map(['upstreams']); // Used by the federation plugin
function params_magic(params) {
return maybe_remove_password(
@ -718,6 +719,8 @@ function collapse_multifields(params0) {
var v = params0[name + '_' + id + '_mfvalue'];
if (k in INTEGER_ARGUMENTS) {
v = parseInt(v);
} else if (k in ARRAY_ARGUMENTS) {
v = v.split(" ");
}
params[name][k] = v;
}

View File

@ -1,6 +1,4 @@
<div class="section-hidden">
<h2>Add Binding</h2>
<div class="hider">
<h3>Add Binding</h3>
<form action="#/bindings" method="post">
<table class="bindings">
<tr>
@ -68,5 +66,3 @@
</tr>
</table>
</form>
</div>
</div>

View File

@ -123,7 +123,7 @@
<div class="section-hidden">
<h2>Client Library</h2>
<div class="hider">
<%= format('table', {'table': connection.client_properties}) %>
<%= fmt_table_long(connection.client_properties) %>
</div>
</div>

View File

@ -53,7 +53,7 @@
<div class="section-hidden">
<h2>Bindings</h2>
<div class="hider updatable">
<div class="hider">
<% if (exchange.name == "") { %>
<h3>Default exchange</h3>
<p>
@ -65,7 +65,7 @@
<% } else { %>
<% if (bindings_destination.length > 0) { %>
<h3>Incoming to <b><%= fmt_exchange(exchange.name) %></b></h3>
<table class="bindings">
<table class="bindings updatable">
<tr>
<td>
<%= format('bindings', {'mode': 'exchange_destination', 'bindings': bindings_destination}) %>
@ -76,9 +76,9 @@
</td>
</tr>
</table>
<% } %>
<% } %>
<h3>Outgoing from <b><%= fmt_exchange(exchange.name) %></b></h3>
<table class="bindings">
<table class="bindings updatable">
<tr>
<td class="binding-endpoint">
<span class="object"><%= fmt_exchange(exchange.name) %></span>
@ -89,14 +89,12 @@
</td>
</tr>
</table>
<%= format('add-binding', {'mode': 'exchange_source', 'parent': exchange}) %>
<% } %>
</div>
</div>
<% if (exchange.name != "") { %>
<%= format('add-binding', {'mode': 'exchange_source', 'parent': exchange}) %>
<div class="section-hidden">
<h2>Delete This Exchange</h2>
<div class="hider">

View File

@ -6,22 +6,36 @@
<table class="facts">
<tr>
<th>
File Descriptors
File Descriptors <span class="help" id="file-descriptors"></span>
<sub>(used / available)</sub>
</th>
<td class="status">
<div class="<%= fmt_color(node.fd_used / node.fd_total) %>">
<div class="<%= fmt_color(node.fd_used / node.fd_total,
FD_THRESHOLDS) %>">
<%= node.fd_used %> / <%= node.fd_total %>
</div>
</td>
</tr>
<tr>
<th>
Socket Descriptors <span class="help" id="socket-descriptors"></span>
<sub>(used / available)</sub>
</th>
<td class="status">
<div class="<%= fmt_color(node.sockets_used / node.sockets_total,
SOCKETS_THRESHOLDS) %>">
<%= node.sockets_used %> / <%= node.sockets_total %>
</div>
</td>
</tr>
<tr>
<th>
Erlang Processes
<sub>(used / available)</sub>
</th>
<td class="status">
<div class="<%= fmt_color(node.proc_used / node.proc_total) %>">
<div class="<%= fmt_color(node.proc_used / node.proc_total,
PROCESS_THRESHOLDS) %>">
<%= node.proc_used %> / <%= node.proc_total %>
</div>
</td>

View File

@ -34,7 +34,11 @@
<tr>
<th>Name</th>
<th>
File Descriptors
File Descriptors <span class="help" id="file-descriptors"></span>
<sub>(used / available)</sub>
</th>
<th>
Socket Descriptors <span class="help" id="socket-descriptors"></span>
<sub>(used / available)</sub>
</th>
<th>
@ -74,12 +78,20 @@
</td>
<% } else { %>
<td class="status">
<div class="<%= fmt_color(node.fd_used / node.fd_total) %>">
<div class="<%= fmt_color(node.fd_used / node.fd_total,
FD_THRESHOLDS) %>">
<%= node.fd_used %> / <%= node.fd_total %>
</div>
</td>
<td class="status">
<div class="<%= fmt_color(node.proc_used / node.proc_total) %>">
<div class="<%= fmt_color(node.sockets_used / node.sockets_total,
SOCKETS_THRESHOLDS) %>">
<%= node.sockets_used %> / <%= node.sockets_total %>
</div>
</td>
<td class="status">
<div class="<%= fmt_color(node.proc_used / node.proc_total,
PROCESS_THRESHOLDS) %>">
<%= node.proc_used %> / <%= node.proc_total %>
</div>
</td>
@ -148,7 +160,7 @@ for (var i = 0; i < overview.listeners.length; i++) {
<div class="section-hidden administrator-only">
<h2>Import / Export Configuration</h2>
<div class="hider">
<form action="/api/all-configuration" method="post" enctype="multipart/form-data">
<form action="../api/all-configuration" method="post" enctype="multipart/form-data">
<table class="two-col-layout">
<tr>
<td>

View File

@ -69,13 +69,6 @@
</div>
</div>
<div class="section-hidden">
<h2>Consumers</h2>
<div class="hider updatable">
<%= format('consumers', {'mode': 'queue', 'consumers': queue.consumer_details}) %>
</div>
</div>
<% if (statistics_level == 'fine') { %>
<div class="section-hidden">
<h2>Message Rates</h2>
@ -104,10 +97,17 @@
<% } %>
<div class="section-hidden">
<h2>Bindings</h2>
<h2>Consumers</h2>
<div class="hider updatable">
<%= format('consumers', {'mode': 'queue', 'consumers': queue.consumer_details}) %>
</div>
</div>
<div class="section-hidden">
<h2>Bindings</h2>
<div class="hider">
<h3>Incoming to <b><%= queue.name %></b></h3>
<table class="bindings">
<table class="bindings updatable">
<tr>
<td>
<%= format('bindings', {'mode': 'queue', 'bindings': bindings}) %>
@ -118,11 +118,12 @@
</td>
</tr>
</table>
</div>
</div>
<%= format('add-binding', {'mode': 'queue', 'parent': queue}) %>
</div>
</div>
<div class="section-hidden">
<h2>Delete / Purge</h2>
<div class="hider">
@ -143,10 +144,3 @@
<span class="br"></span>
</div>
</div>
<div class="section-hidden">
<h2>Backing Queue Status</h2>
<div class="hider updatable">
<%= fmt_table_long(queue.backing_queue_status) %>
</div>
</div>

View File

@ -102,6 +102,18 @@
</select>
</td>
</tr>
<% if (nodes_interesting) { %>
<tr>
<th><label>On node:</label></th>
<td>
<select name="node">
<% for (var i = 0; i < nodes.length; i++) { %>
<option value="<%= nodes[i].name %>"><%= nodes[i].name %></option>
<% } %>
</select>
</td>
</tr>
<% } %>
<tr>
<th><label>Auto delete:</label></th>
<td>

View File

@ -1,5 +0,0 @@
<table class="facts-long">
<% for (var key in table) { %>
<tr><th><%= key %></th><td><%= table[key] %></td></tr>
<% } %>
</table>

View File

@ -36,7 +36,11 @@
-define(PREFIX, "api").
-define(UI_PREFIX, "mgmt").
-define(CLI_PREFIX, "cli").
-ifdef(trace).
-define(SETUP_WM_TRACE, true).
-else.
-define(SETUP_WM_TRACE, false).
-endif.
%% Make sure our database is hooked in *before* listening on the network or
%% recovering queues (i.e. so there can't be any events fired before it starts).
@ -61,11 +65,9 @@ stop(_State) ->
ok.
register_contexts() ->
application:set_env(
webmachine, dispatch_list,
[{[?PREFIX | Path], F, A} ||
{Path, F, A} <- rabbit_mgmt_dispatcher:dispatcher()]),
application:set_env(webmachine, error_handler, webmachine_error_handler),
Dispatch =
[{[?PREFIX | Path], F, A} ||
{Path, F, A} <- rabbit_mgmt_dispatcher:dispatcher()],
rabbit_mochiweb:register_authenticated_static_context(
?UI_PREFIX, ?MODULE, "priv/www", "Management: Web UI",
fun (U, P) ->
@ -75,7 +77,8 @@ register_contexts() ->
end
end),
rabbit_mochiweb:register_context_handler(?PREFIX,
fun webmachine_mochiweb:loop/1,
rabbit_webmachine:makeloop(
Dispatch),
"Management: HTTP API"),
rabbit_mochiweb:register_static_context(?CLI_PREFIX, ?MODULE,
"priv/www-cli",
@ -84,22 +87,22 @@ setup_wm_logging() ->
{ok, LogDir} = application:get_env(rabbit_management, http_log_dir),
case LogDir of
none ->
ok;
rabbit_webmachine:setup(none);
_ ->
application:set_env(webmachine, webmachine_logger_module,
webmachine_logger),
rabbit_webmachine:setup(webmachine_logger),
webmachine_sup:start_logger(LogDir)
end.
%% This doesn't *entirely* seem to work. It fails to load a non-existent
%% image which seems to partly break it, but some stuff is usable.
setup_wm_trace_app() ->
webmachine_router:start_link(),
wmtrace_resource:add_dispatch_rule("wmtrace", "/tmp"),
Loop = rabbit_webmachine:makeloop([{["wmtrace", '*'],
wmtrace_resource,
[{trace_dir, "/tmp"}]}]),
rabbit_mochiweb:register_static_context(
"wmtrace/static", ?MODULE, "deps/webmachine/webmachine/priv/trace", none),
rabbit_mochiweb:register_context_handler("wmtrace",
fun webmachine_mochiweb:loop/1,
Loop,
"Webmachine tracer").
log_startup() ->
{ok, Hostname} = inet:gethostname(),

View File

@ -20,7 +20,7 @@
-export([node_and_pid/1, protocol/1, resource/1, permissions/1, queue/1]).
-export([exchange/1, user/1, internal_user/1, binding/1, url/2]).
-export([pack_binding_props/2, unpack_binding_props/1, tokenise/1]).
-export([args_type/1, listener/1, properties/1]).
-export([to_amqp_table/1, listener/1, properties/1]).
-include_lib("rabbit_common/include/rabbit.hrl").
@ -71,8 +71,12 @@ properties(Table) -> {struct, [{Name, tuple(Value)} ||
{Name, Value} <- Table]}.
amqp_table(unknown) -> unknown;
amqp_table(Table) -> {struct, [{Name, tuple(Value)} ||
{Name, _Type, Value} <- Table]}.
amqp_table(Table) -> {struct, [{Name, amqp_value(Type, Value)} ||
{Name, Type, Value} <- Table]}.
amqp_value(array, Val) -> [amqp_value(T, V) || {T, V} <- Val];
amqp_value(table, Val) -> amqp_table(Val);
amqp_value(_Type, Val) -> Val.
tuple(unknown) -> unknown;
tuple(Tuple) when is_tuple(Tuple) -> [tuple(E) || E <- tuple_to_list(Tuple)];
@ -156,7 +160,7 @@ unpack_binding_props(Str) ->
unpack_binding_props0([Key | Args]) ->
try
{unquote_binding(Key), unpack_binding_args(Args)}
{unquote_binding(Key), to_amqp_table(unpack_binding_args(Args))}
catch E -> E
end;
unpack_binding_props0([]) ->
@ -167,8 +171,7 @@ unpack_binding_args([]) ->
unpack_binding_args([K]) ->
throw({bad_request, {no_value, K}});
unpack_binding_args([K, V | Rest]) ->
Value = unquote_binding(V),
[{unquote_binding(K), args_type(Value), Value} | unpack_binding_args(Rest)].
[{unquote_binding(K), unquote_binding(V)} | unpack_binding_args(Rest)].
unquote_binding(Name) ->
list_to_binary(mochiweb_util:unquote(Name)).
@ -185,6 +188,14 @@ tokenise(Str) ->
tokenise(string:sub_string(Str, Count + 2))]
end.
to_amqp_table(T) ->
[to_amqp_table_row(K, V) || {K, V} <- T].
to_amqp_table_row(K, Vs) when is_list(Vs) ->
{K, array, [{args_type(V), V} || V <- Vs]};
to_amqp_table_row(K, V) ->
{K, args_type(V), V}.
args_type(X) when is_binary(X) ->
longstr;
args_type(X) when is_number(X) ->

View File

@ -24,6 +24,7 @@
-export([all_or_one_vhost/2, http_to_amqp/5, reply/3, filter_vhost/3]).
-export([filter_user/3, with_decode/5, redirect/2, args/1]).
-export([reply_list/3, reply_list/4, sort_list/4, destination_type/1]).
-export([relativise/2]).
-include("rabbit_mgmt.hrl").
-include_lib("amqp_client/include/amqp_client.hrl").
@ -256,10 +257,15 @@ http_to_amqp(MethodName, ReqData, Context, Transformers, Extra) ->
case decode(wrq:req_body(ReqData)) of
{ok, Props} ->
try
rabbit_mgmt_util:amqp_request(
VHost, ReqData, Context,
props_to_method(
MethodName, Props, Transformers, Extra))
Node =
case proplists:get_value(<<"node">>, Props) of
undefined -> node();
N -> rabbit_misc:makenode(
binary_to_list(N))
end,
amqp_request(VHost, ReqData, Context, Node,
props_to_method(
MethodName, Props, Transformers, Extra))
catch {error, Error} ->
bad_request(Error, ReqData, Context)
end;
@ -295,12 +301,16 @@ parse_bool(true) -> true;
parse_bool(false) -> false;
parse_bool(V) -> throw({error, {not_boolean, V}}).
amqp_request(VHost, ReqData, Context, Method) ->
amqp_request(VHost, ReqData, Context, node(), Method).
amqp_request(VHost, ReqData,
Context = #context{ user = #user { username = Username },
password = Password }, Method) ->
password = Password }, Node, Method) ->
try
Params = #amqp_params{username = Username,
password = Password,
Params = #amqp_params{username = Username,
password = Password,
node = Node,
virtual_host = VHost},
case amqp_connection:start(direct, Params) of
{ok, Conn} ->
@ -310,19 +320,23 @@ amqp_request(VHost, ReqData,
amqp_connection:close(Conn),
{true, ReqData, Context};
{error, auth_failure} ->
not_authorised(<<"">>, ReqData, Context)
not_authorised(<<"">>, ReqData, Context);
{error, {nodedown, N}} ->
bad_request(
list_to_binary(
io_lib:format("Node ~s could not be contacted", [N])),
ReqData, Context)
end
catch
exit:{{server_initiated_close, ?NOT_FOUND, Reason}, _} ->
not_found(list_to_binary(Reason), ReqData, Context);
not_found(Reason, ReqData, Context);
exit:{{server_initiated_close, ?ACCESS_REFUSED, Reason}, _} ->
not_authorised(list_to_binary(Reason), ReqData, Context);
not_authorised(Reason, ReqData, Context);
exit:{{ServerClose, Code, Reason}, _}
when ServerClose =:= server_initiated_close;
ServerClose =:= server_initiated_hard_close ->
bad_request(list_to_binary(io_lib:format("~p ~s", [Code, Reason])),
ReqData, Context);
E:R -> io:format("~p~n", [{E,R}])
ReqData, Context)
end.
all_or_one_vhost(ReqData, Fun) ->
@ -349,4 +363,23 @@ redirect(Location, ReqData) ->
args({struct, L}) ->
args(L);
args(L) ->
[{K, rabbit_mgmt_format:args_type(V), V} || {K, V} <- L].
rabbit_mgmt_format:to_amqp_table(L).
relativise("/" ++ F, "/" ++ T) ->
From = string:tokens(F, "/"),
To = string:tokens(T, "/"),
relativise0(From, To).
relativise0([H], [H|_] = To) ->
string:join(To, "/");
relativise0([H|From], [H|To]) ->
relativise0(From, To);
relativise0(From, []) ->
relativise(From, [], 0);
relativise0(From, To) ->
relativise(From, To, 1).
relativise(From, To, Diff) ->
string:join(lists:duplicate(length(From) - Diff, "..") ++ To, "/").

View File

@ -82,12 +82,14 @@ accept_content(ReqData, {_Mode, Context}) ->
rabbit_mgmt_util:with_decode(
[routing_key, arguments], ReqData, Context,
fun([Key, Args]) ->
Loc = binary_to_list(
rabbit_mgmt_format:url(
"/api/bindings/~s/e/~s/~s/~s/~s",
[VHost, Source, DestType, Dest,
rabbit_mgmt_format:pack_binding_props(
Key, rabbit_mgmt_util:args(Args))])),
Loc = rabbit_mgmt_util:relativise(
wrq:path(ReqData),
binary_to_list(
rabbit_mgmt_format:url(
"/api/bindings/~s/e/~s/~s/~s/~s",
[VHost, Source, DestType, Dest,
rabbit_mgmt_format:pack_binding_props(
Key, rabbit_mgmt_util:args(Args))]))),
ReqData2 = wrq:set_resp_header("Location", Loc, ReqData),
{true, ReqData2, Context2}
end)

View File

@ -312,7 +312,7 @@ bindings_post_test() ->
http_post("/bindings/%2f/e/badexchange/q/myqueue", BArgs, ?NOT_FOUND),
http_post("/bindings/%2f/e/myexchange/q/myqueue", [{a, "b"}], ?BAD_REQUEST),
Headers = http_post("/bindings/%2f/e/myexchange/q/myqueue", BArgs, ?CREATED),
"/api/bindings/%2F/e/myexchange/q/myqueue/routing_foo_bar" =
"../../../../%2F/e/myexchange/q/myqueue/routing_foo_bar" =
pget("location", Headers),
[{source,<<"myexchange">>},
{vhost,<<"/">>},
@ -332,7 +332,7 @@ bindings_e2e_test() ->
http_post("/bindings/%2f/e/amq.direct/e/badexchange", BArgs, ?NOT_FOUND),
http_post("/bindings/%2f/e/badexchange/e/amq.fanout", BArgs, ?NOT_FOUND),
Headers = http_post("/bindings/%2f/e/amq.direct/e/amq.fanout", BArgs, ?CREATED),
"/api/bindings/%2F/e/amq.direct/e/amq.fanout/routing" =
"../../../../%2F/e/amq.direct/e/amq.fanout/routing" =
pget("location", Headers),
[{source,<<"amq.direct">>},
{vhost,<<"/">>},
@ -618,6 +618,19 @@ arguments_test() ->
http_delete("/queues/%2f/myqueue", ?NO_CONTENT),
ok.
arguments_table_test() ->
Args = [{'upstreams', [<<"amqp://localhost/%2f/upstream1">>,
<<"amqp://localhost/%2f/upstream2">>]}],
XArgs = [{type, <<"headers">>},
{arguments, Args}],
http_put("/exchanges/%2f/myexchange", XArgs, ?NO_CONTENT),
AllConfig = http_get("/all-configuration", ?OK),
http_delete("/exchanges/%2f/myexchange", ?NO_CONTENT),
http_post("/all-configuration", AllConfig, ?NO_CONTENT),
Args = pget(arguments, http_get("/exchanges/%2f/myexchange", ?OK)),
http_delete("/exchanges/%2f/myexchange", ?NO_CONTENT),
ok.
queue_purge_test() ->
QArgs = [],
http_put("/queues/%2f/myqueue", QArgs, ?NO_CONTENT),

View File

@ -48,6 +48,15 @@ pack_binding_test() ->
rabbit_mgmt_format:unpack_binding_props(<<"bad_routing">>),
ok.
relativise_test() ->
"baz" = rabbit_mgmt_util:relativise("/foo/bar/bash", "/foo/bar/baz"),
"../bax/baz" = rabbit_mgmt_util:relativise("/foo/bar/bash", "/foo/bax/baz"),
"../bax/baz" = rabbit_mgmt_util:relativise("/bar/bash", "/bax/baz"),
".." = rabbit_mgmt_util:relativise("/foo/bar/bash", "/foo/bar"),
"../.." = rabbit_mgmt_util:relativise("/foo/bar/bash", "/foo"),
"bar/baz" = rabbit_mgmt_util:relativise("/foo/bar", "/foo/bar/baz"),
ok.
%%--------------------------------------------------------------------
assert_binding(Packed, Routing, Args) ->