Merged bug25676 into default

This commit is contained in:
Emile Joubert 2013-07-24 15:32:42 +01:00
commit 2493c446d4
22 changed files with 325 additions and 162 deletions

View File

@ -78,7 +78,7 @@ DECLARABLE = {
'optional': {'destination_type': 'queue',
'routing_key': '', 'arguments': {}}},
'vhost': {'mandatory': ['name'],
'optional': {}},
'optional': {'tracing': None}},
'user': {'mandatory': ['name', 'password', 'tags'],
'optional': {}},
'permission': {'mandatory': ['vhost', 'user', 'configure', 'write', 'read'],

View File

@ -478,9 +478,10 @@ Content-Length: 0</pre>
<td>X</td>
<td></td>
<td class="path">/api/vhosts/<i>name</i></td>
<td>An individual virtual host. As a virtual host only has a
name, you do not need an HTTP body when PUTing one of
these.</td>
<td>An individual virtual host. As a virtual host usually only
has a name, you do not need an HTTP body when PUTing one of
these. To enable / disable tracing, provide a body looking like:
<pre>{"tracing":true}</pre></td>
</tr>
<tr>
<td>X</td>

View File

@ -102,7 +102,7 @@ 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; }
table.facts-fixed-width th, table.facts-fixed-width td { width: 100px; }
table.facts-fixed-width th, table.facts-fixed-width td { width: 130px; }
table.mini th { border: none; padding: 0 2px 2px 2px; text-align: right; }
table.mini td { border: none; padding: 0 2px 2px 2px; }
@ -182,10 +182,14 @@ table.form input[type=text].wide, table.form input[type=password].wide { width:
table.form select { width: 200px; }
table.form select.narrow { width: 110px; }
table.form .multifield { margin: 0; padding: 0; }
table.form .multifield p { margin: 0; padding: 0; }
table.form .multifield p select { width: 70px; }
table.form .multifield td { margin: 0; padding: 0; vertical-align: top; }
table.form .multifield td.equals { padding: 3px; }
table.form .multifield td input { float: left; }
table.form .multifield td select { width: 70px; display: block; float: left; margin-left: 5px; }
table.form label { margin-top: 5px; display: block; }
.multifield-sub { border: 1px solid #ddd; background: #f8f8f8; padding: 10px; border-radius: 10px; -moz-border-radius: 10px; float: left; margin-bottom: 10px; }
label.radio { padding: 5px; border: 1px solid #eee; cursor: pointer; border-radius: 5px; -moz-border-radius: 5px; }
table.two-col-layout { width: 100%; }

View File

@ -54,7 +54,12 @@ function render_chart(div) {
function fmt_y_axis(fmt) {
return function (val, axis) {
return fmt(val.toFixed(axis.tickDecimals));
// axis.ticks seems to include the bottom value but not the top
if (axis.max == 1 && axis.ticks.length > 1) {
var newTicks = [axis.ticks[0]];
axis.ticks = newTicks;
}
return fmt(val, axis.max);
}
}

View File

@ -14,17 +14,23 @@ function fmt_string(str, unknown) {
function fmt_bytes(bytes) {
if (bytes == undefined) return UNKNOWN_REPR;
return fmt_si_prefix(bytes, bytes, 1024) + 'B';
}
function f(n, p) {
if (n > 1024) return f(n / 1024, p + 1);
function fmt_si_prefix(num, max, thousand) {
if (num == 0) return 0;
function f(n, m, p) {
if (m > thousand) return f(n / thousand, m / thousand, p + 1);
else return [n, p];
}
var num_power = f(bytes, 0);
var num_power = f(num, max, 0);
var num = num_power[0];
var power = num_power[1];
var powers = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return (power == 0 ? num.toFixed(0) : num.toFixed(1)) + powers[power];
var powers = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
return ((power == 0 || num > 10) ? num.toFixed(0) :
num.toFixed(1)) + powers[power];
}
function fmt_memory(memory, key) {
@ -182,7 +188,13 @@ function fmt_rate_num(num) {
if (num == undefined) return UNKNOWN_REPR;
else if (num < 1) return num.toFixed(2);
else if (num < 10) return num.toFixed(1);
else return num.toFixed(0);
else return fmt_num_thousands(num.toFixed(0));
}
function fmt_num_thousands(num) {
num = '' + num;
if (num.length < 4) return num;
return fmt_num_thousands(num.slice(0, -3)) + ',' + num.slice(-3);
}
function fmt_rate(obj, name, mode) {
@ -226,7 +238,8 @@ function fmt_msgs0(obj, name, mode) {
if (obj == undefined || obj[name] == undefined ||
obj[name + '_details'] == undefined) return '';
var details = obj[name + '_details'];
return mode == 'avg' ? fmt_rate_num(details.avg) : obj[name];
return mode == 'avg' ? fmt_rate_num(details.avg) :
fmt_num_thousands(obj[name]);
}
function fmt_msgs_rate(num) {
@ -235,15 +248,15 @@ function fmt_msgs_rate(num) {
else return '&nbsp;';
}
function fmt_rate_axis(num) {
return num + '/s';
function fmt_rate_axis(num, max) {
return fmt_si_prefix(num, max, 1000) + '/s';
}
function fmt_msgs_axis(num) {
return num;
function fmt_msgs_axis(num, max) {
return fmt_si_prefix(num, max, 1000);
}
function fmt_rate_bytes_axis(num) {
function fmt_rate_bytes_axis(num, max) {
num = parseInt(num);
return fmt_bytes(isNaN(num) ? 0 : num) + '/s';
}

View File

@ -26,6 +26,12 @@ HELP = {
'queue-dead-letter-routing-key':
'Optional replacement routing key to use when a message is dead-lettered. If this is not set, the message\'s original routing key will be used.<br/>(Sets the "<a target="_blank" href="http://rabbitmq.com/dlx.html">x-dead-letter-routing-key</a>" argument.)',
'queue-memory-resident':
'<p>Number of messages in the queue which are held in memory. These messages may also be on disc (if they are persistent).</p><p>There may be a limit imposed in order to manage total memory use. If the number of memory-resident messages in the queue exceeds the limit some messages will be paged out.</p>',
'queue-persistent':
'Number of messages in the queue which are persistent. These messages will be on disc but may also be available in memory. Note that if a message is published as persistent but routed to a transient queue it is not considered persistent by that queue, so transient queues will always report 0 persistent messages.',
'internal-users-only':
'Only users within the internal RabbitMQ database are shown here. Other users (e.g. those authenticated over LDAP) will not appear.',
@ -194,13 +200,12 @@ HELP = {
<dt><code>ha-mode</code></dt>\
<dd>\
One of <code>all</code>, <code>exactly</code>\
or <code>nodes</code> (the latter currently not supported by\
web UI).\
or <code>nodes</code>.\
</dd>\
<dt><code>ha-params</code></dt>\
<dd>\
Absent if <code>ha-mode</code> is <code>all</code>, a number\
if <code>ha-mode</code> is <code>exactly</code>, or an array\
if <code>ha-mode</code> is <code>exactly</code>, or a list\
of strings if <code>ha-mode</code> is <code>nodes</code>.\
</dd>\
<dt><code>ha-sync-mode</code></dt>\

View File

@ -451,7 +451,10 @@ function postprocess() {
update_manual($(this).attr('for'), $(this).attr('query'));
});
$('input, select').die();
$('.multifield input').live('blur', function() {
$('.multifield input').live('keyup', function() {
update_multifields();
});
$('.multifield select').live('change', function() {
update_multifields();
});
$('.controls-appearance').change(function() {
@ -523,46 +526,84 @@ function postprocess_partial() {
}
function update_multifields() {
$('.multifield').each(function(index) {
var largest_id = 0;
var empty_found = false;
var name = $(this).attr('id');
$('#' + name + ' input[name$="_mfkey"]').each(function(index) {
var match = $(this).attr('name').
match(/[a-z]*_([0-9]*)_mfkey/);
var id = parseInt(match[1]);
largest_id = Math.max(id, largest_id);
var key = $(this).val();
var value = $(this).next('input').val();
if (key == '' && value == '') {
if (empty_found) {
$(this).parent().remove();
}
else {
empty_found = true;
}
}
});
if (!empty_found) {
var prefix = name + '_' + (largest_id + 1);
var type_part;
if ($(this).hasClass('string-only')) {
type_part = '<input type="hidden" name="' + prefix +
'_mftype" value="string"/>';
} else {
type_part = '<select name="' + prefix +
'_mftype">' +
'<option value="string">String</option>' +
'<option value="number">Number</option>' +
'<option value="boolean">Boolean</option>' +
'</select>';
}
$(this).append('<p><input type="text" name="' + prefix +
'_mfkey" value=""/> = ' +
'<input type="text" name="' + prefix +
'_mfvalue" value=""/> ' + type_part + '</p>');
$('div.multifield').each(function(index) {
update_multifield($(this), true);
});
}
function update_multifield(multifield, dict) {
var largest_id = 0;
var empty_found = false;
var name = multifield.attr('id');
$('#' + name + ' *[name$="_mftype"]').each(function(index) {
var re = new RegExp(name + '_([0-9]*)_mftype');
var match = $(this).attr('name').match(re);
if (!match) return;
var id = parseInt(match[1]);
largest_id = Math.max(id, largest_id);
var prefix = name + '_' + id;
var type = $(this).val();
var input = $('#' + prefix + '_mfvalue');
if (type == 'list') {
if (input.size() == 1) {
input.replaceWith('<div class="multifield-sub" id="' + prefix +
'"></div>');
}
});
update_multifield($('#' + prefix), false);
}
else {
if (input.size() == 1) {
var key = dict ? $('#' + prefix + '_mfkey').val() : '';
var value = input.val();
if (key == '' && value == '') {
if (empty_found) {
$(this).parent().remove();
}
empty_found = true;
}
}
else {
$('#' + prefix).replaceWith(multifield_input(prefix, 'value',
'text'));
}
}
});
if (!empty_found) {
var prefix = name + '_' + (largest_id + 1);
var t = multifield.hasClass('string-only') ? 'hidden' : 'select';
var val_type = multifield_input(prefix, 'value', 'text') + ' ' +
multifield_input(prefix, 'type', t);
if (dict) {
multifield.append('<table><tr><td>' +
multifield_input(prefix, 'key', 'text') +
'</td><td class="equals"> = </td><td>' +
val_type + '</td></tr></table>');
}
else {
multifield.append('<div>' + val_type + '</div>');
}
}
}
function multifield_input(prefix, suffix, type) {
if (type == 'hidden' ) {
return '<input type="hidden" id="' + prefix + '_mf' + suffix +
'" name="' + prefix + '_mf' + suffix + '" value="string"/>';
}
else if (type == 'text' ) {
return '<input type="text" id="' + prefix + '_mf' + suffix +
'" name="' + prefix + '_mf' + suffix + '" value=""/>';
}
else if (type == 'select' ) {
return '<select id="' + prefix + '_mf' + suffix + '" name="' + prefix +
'_mf' + suffix + '">' +
'<option value="string">String</option>' +
'<option value="number">Number</option>' +
'<option value="boolean">Boolean</option>' +
'<option value="list">List</option>' +
'</select>';
}
}
function update_filter() {
@ -855,11 +896,16 @@ function params_magic(params) {
}
function collapse_multifields(params0) {
function set(x) { return x != '' && x != undefined }
var params = {};
for (key in params0) {
var match = key.match(/([a-z]*)_([0-9]*)_mfkey/);
var match2 = key.match(/[a-z]*_[0-9]*_mfvalue/);
var match3 = key.match(/[a-z]*_[0-9]*_mftype/);
var ks = keys(params0);
var ids = [];
for (i in ks) {
var key = ks[i];
var match = key.match(/([a-z]*)_([0-9_]*)_mftype/);
var match2 = key.match(/[a-z]*_[0-9_]*_mfkey/);
var match3 = key.match(/[a-z]*_[0-9_]*_mfvalue/);
if (match == null && match2 == null && match3 == null) {
params[key] = params0[key];
}
@ -869,27 +915,50 @@ function collapse_multifields(params0) {
else {
var name = match[1];
var id = match[2];
if (params[name] == undefined) {
params[name] = {};
ids.push([name, id]);
}
}
ids.sort();
var id_map = {};
for (i in ids) {
var name = ids[i][0];
var id = ids[i][1];
if (params[name] == undefined) {
params[name] = {};
id_map[name] = {};
}
var id_parts = id.split('_');
var k = params0[name + '_' + id_parts[0] + '_mfkey'];
var v = params0[name + '_' + id + '_mfvalue'];
var t = params0[name + '_' + id + '_mftype'];
var val = null;
if (t == 'list') {
val = [];
id_map[name][id] = val;
}
else if (set(k) || set(v)) {
if (t == 'boolean') {
if (v != 'true' && v != 'false')
throw(k + ' must be "true" or "false"; got ' + v);
val = (v == 'true');
}
if (params0[key] != "") {
var k = params0[key];
var v = params0[name + '_' + id + '_mfvalue'];
var t = params0[name + '_' + id + '_mftype'];
if (t == 'boolean') {
if (v != 'true' && v != 'false')
throw(k + ' must be "true" or "false"; got ' + v);
params[name][k] = (v == 'true');
}
else if (t == 'number') {
var n = parseFloat(v);
if (isNaN(n))
throw(k + ' must be a number; got ' + v);
params[name][k] = n;
}
else {
params[name][k] = v;
}
else if (t == 'number') {
var n = parseFloat(v);
if (isNaN(n))
throw(k + ' must be a number; got ' + v);
val = n;
}
else {
val = v;
}
}
if (val != null) {
if (id_parts.length == 1) {
params[name][k] = val;
}
else {
var prefix = id_parts.slice(0, id_parts.length - 1).join('_');
id_map[name][prefix].push(val);
}
}
}
@ -907,9 +976,6 @@ function add_known_arguments(params) {
throw(k + " must be an integer.");
}
}
else if (type == 'array' && typeof(v) == 'string') {
v = v.split(' ');
}
params.arguments[k] = v;
}
delete params[k];

View File

@ -42,7 +42,7 @@
</tr>
<tr>
<th><label>Arguments:</label></th>
<td><span class="multifield" id="arguments"></span></td>
<td><div class="multifield" id="arguments"></div></td>
</tr>
</table>
<input type="submit" value="Bind"/>

View File

@ -114,7 +114,7 @@
<tr>
<th><label>Arguments:</label></th>
<td>
<span class="multifield" id="arguments"></span>
<div class="multifield" id="arguments"></div>
</td>
</tr>
</table>

View File

@ -70,7 +70,7 @@
</tr>
<tr>
<th><label>Definition: <span class="help" id="policy-definitions"></span></label></th>
<td><span class="multifield" id="definition"></span></td>
<td><div class="multifield" id="definition"></div></td>
<td class="t"><span class="mand">*</span></td>
</tr>
<tr>

View File

@ -38,7 +38,7 @@
</label>
</th>
<td>
<span class="multifield" id="headers"></span>
<div class="multifield" id="headers"></div>
</td>
</tr>
<tr>
@ -49,7 +49,7 @@
</label>
</th>
<td>
<span class="multifield string-only" id="props"></span>
<div class="multifield string-only" id="props"></div>
</td>
</tr>
<tr>

View File

@ -46,6 +46,38 @@
</tr>
</table>
<table class="facts">
<tr>
<th>
Paging <span class="help" id="queue-memory-resident"></span>
</th>
<td>
<% var messages_ram = queue.backing_queue_status.ram_msg_count + queue.backing_queue_status.ram_ack_count; %>
<% if (messages_ram == queue.messages) { %>
No paging
<% } else { %>
<%= fmt_num_thousands(messages_ram) %> /
<%= fmt_num_thousands(queue.messages) %> msg (in RAM / total)
<% } %>
<sub>
<% if (queue.backing_queue_status.target_ram_count == 'infinity') { %>
No limit
<% } else { %>
RAM target: <%= fmt_num_thousands(queue.backing_queue_status.target_ram_count) %> msg
<% } %>
</sub>
</td>
</tr>
<tr>
<th>
Persistent <span class="help" id="queue-persistent"></span>
</th>
<td>
<%= fmt_num_thousands(queue.backing_queue_status.persistent_count) %> msg
</td>
</tr>
</table>
<table class="facts">
<% if (vhosts_interesting) { %>
<tr>

View File

@ -70,9 +70,9 @@
<td class="c"><%= fmt_parameters_short(queue) %></td>
<td class="c"><%= fmt_string(queue.policy, '') %></td>
<td class="c"><%= fmt_idle(queue) %></td>
<td class="r"><%= fmt_string(queue.messages_ready) %></td>
<td class="r"><%= fmt_string(queue.messages_unacknowledged) %></td>
<td class="r"><%= fmt_string(queue.messages) %></td>
<td class="r"><%= fmt_num_thousands(queue.messages_ready) %></td>
<td class="r"><%= fmt_num_thousands(queue.messages_unacknowledged) %></td>
<td class="r"><%= fmt_num_thousands(queue.messages) %></td>
<% if (statistics_level == 'fine') { %>
<td class="r"><%= fmt_rate(queue.message_stats, 'publish') %></td>
<td class="r"><%= fmt_deliver_rate(queue.message_stats, col_redeliver) %></td>
@ -167,7 +167,7 @@
</tr>
<tr>
<th><label>Arguments:</label></th>
<td><span class="multifield" id="arguments"></span></td>
<td><div class="multifield" id="arguments"></div></td>
</tr>
</table>
<input type="submit" value="Add queue"/>

View File

@ -420,14 +420,10 @@ lookup_element(Table, Key, Pos) ->
fine_stats_id(ChPid, {Q, X}) -> {ChPid, Q, X};
fine_stats_id(ChPid, QorX) -> {ChPid, QorX}.
floor(TS, State) -> floor0(rabbit_mgmt_format:timestamp_ms(TS), State).
ceil (TS, State) -> ceil0 (rabbit_mgmt_format:timestamp_ms(TS), State).
floor0(MS, #state{interval = Interval}) -> (MS div Interval) * Interval.
ceil0(MS, State = #state{interval = Interval}) -> case floor0(MS, State) of
MS -> MS;
Floor -> Floor + Interval
end.
floor(TS, #state{interval = Interval}) ->
rabbit_mgmt_util:floor(rabbit_mgmt_format:timestamp_ms(TS), Interval).
ceil(TS, #state{interval = Interval}) ->
rabbit_mgmt_util:ceil (rabbit_mgmt_format:timestamp_ms(TS), Interval).
details_key(Key) -> list_to_atom(atom_to_list(Key) ++ "_details").
@ -530,14 +526,6 @@ handle_event(#event{type = consumer_deleted, props = Props}, State) ->
handle_consumer(fun(Table, Id, _P) -> ets:delete(Table, Id) end,
Props, State);
handle_event(#event{type = queue_mirror_deaths, props = Props},
#state{tables = Tables}) ->
Dead = pget(pids, Props),
Table = orddict:fetch(queue_stats, Tables),
%% Only the master can be in the DB, but it's easier just to
%% delete all of them
[ets:delete(Table, {Pid, stats}) || Pid <- Dead];
%% TODO: we don't clear up after dead nodes here - this is a very tiny
%% leak every time a node is permanently removed from the cluster. Do
%% we care?

View File

@ -32,7 +32,8 @@
-export([with_decode/5, decode/1, decode/2, redirect/2, args/1]).
-export([reply_list/3, reply_list/4, sort_list/2, destination_type/1]).
-export([post_respond/1, columns/1, is_monitor/1]).
-export([list_visible_vhosts/1, b64decode_or_throw/1, no_range/0, range/1]).
-export([list_visible_vhosts/1, b64decode_or_throw/1, no_range/0, range/1,
range_ceil/1, floor/2, ceil/2]).
-import(rabbit_misc, [pget/2, pget/3]).
@ -257,7 +258,7 @@ id(Key, ReqData) ->
id0(Key, ReqData).
id0(Key, ReqData) ->
case dict:find(Key, wrq:path_info(ReqData)) of
case orddict:find(Key, wrq:path_info(ReqData)) of
{ok, Id} -> list_to_binary(mochiweb_util:unquote(Id));
error -> none
end.
@ -286,6 +287,9 @@ decode(Keys, Body) ->
Else -> Else
end.
decode(<<"">>) ->
{ok, []};
decode(Body) ->
try
{struct, J} = mochijson2:decode(Body),
@ -348,6 +352,7 @@ parse_bool(<<"true">>) -> true;
parse_bool(<<"false">>) -> false;
parse_bool(true) -> true;
parse_bool(false) -> false;
parse_bool(undefined) -> undefined;
parse_bool(V) -> throw({error, {not_boolean, V}}).
parse_int(I) when is_integer(I) -> I;
@ -501,22 +506,33 @@ b64decode_or_throw(B64) ->
no_range() -> {no_range, no_range, no_range}.
range(ReqData) -> {range("lengths", ReqData),
range("msg_rates", ReqData),
range("data_rates", ReqData)}.
%% 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(Prefix, 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.
%%
%% Why does msg_rates still use floor/2? Because in the cases where we
%% call this function (for connections and queues) the msg_rates are still
%% aggregated even though the lengths and data rates aren't.
range_ceil(ReqData) -> {range("lengths", fun ceil/2, ReqData),
range("msg_rates", fun floor/2, ReqData),
range("data_rates", fun ceil/2, ReqData)}.
range(Prefix, Round, ReqData) ->
Age0 = int(Prefix ++ "_age", ReqData),
Incr0 = int(Prefix ++ "_incr", ReqData),
if
is_integer(Age0) andalso is_integer(Incr0) ->
Age = Age0 * 1000,
Incr = Incr0 * 1000,
%% 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.
Now = rabbit_mgmt_format:timestamp_ms(erlang:now()),
Last = (Now div Incr) * Incr,
Last = Round(Now, Incr),
#range{first = (Last - Age),
last = Last,
incr = Incr};
@ -524,6 +540,13 @@ range(Prefix, ReqData) ->
no_range
end.
floor(TS, Interval) -> (TS div Interval) * Interval.
ceil(TS, Interval) -> case floor(TS, Interval) of
TS -> TS;
Floor -> Floor + Interval
end.
int(Name, ReqData) ->
case wrq:get_qs_value(Name, ReqData) of
undefined -> undefined;

View File

@ -63,4 +63,4 @@ is_authorized(ReqData, Context) ->
conn(ReqData) ->
rabbit_mgmt_db:get_connection(rabbit_mgmt_util:id(connection, ReqData),
rabbit_mgmt_util:range(ReqData)).
rabbit_mgmt_util:range_ceil(ReqData)).

View File

@ -41,4 +41,4 @@ is_authorized(ReqData, Context) ->
augmented(ReqData, Context) ->
rabbit_mgmt_util:filter_conn_ch_list(
rabbit_mgmt_db:get_all_connections(
rabbit_mgmt_util:range(ReqData)), ReqData, Context).
rabbit_mgmt_util:range_ceil(ReqData)), ReqData, Context).

View File

@ -140,6 +140,7 @@ apply_defs(Body, SuccessFun, ErrorFun) ->
end.
get_part(Name, Parts) ->
%% TODO any reason not to use lists:keyfind instead?
Filtered = [Value || {N, _Meta, Value} <- Parts, N == Name],
case Filtered of
[] -> unknown;
@ -150,10 +151,10 @@ export_queue(Queue) ->
pget(owner_pid, Queue) == none.
export_binding(Binding, Qs) ->
Src = pget(source, Binding),
Dest = pget(destination, Binding),
Src = pget(source, Binding),
Dest = pget(destination, Binding),
DestType = pget(destination_type, Binding),
VHost = pget(vhost, Binding),
VHost = pget(vhost, Binding),
Src =/= <<"">>
andalso
( (DestType =:= queue andalso lists:member({Dest, VHost}, Qs))
@ -193,22 +194,19 @@ filter_item(Item, Allowed) ->
for_all(Name, All, Fun) ->
case pget(Name, All) of
undefined ->
ok;
List ->
[Fun([{atomise_name(K), V} || {K, V} <- I]) ||
{struct, I} <- List]
undefined -> ok;
List -> [Fun([{atomise_name(K), V} || {K, V} <- I]) ||
{struct, I} <- List]
end.
atomise_name(N) ->
list_to_atom(binary_to_list(N)).
atomise_name(N) -> list_to_atom(binary_to_list(N)).
%%--------------------------------------------------------------------
add_parameter(Param) ->
VHost = pget(vhost, Param),
Comp = pget(component, Param),
Key = pget(name, Param),
VHost = pget(vhost, Param),
Comp = pget(component, Param),
Key = pget(name, Param),
case rabbit_runtime_parameters:set(
VHost, Comp, Key, rabbit_misc:json_to_term(pget(value, Param))) of
ok -> ok;
@ -219,7 +217,7 @@ add_parameter(Param) ->
add_policy(Param) ->
VHost = pget(vhost, Param),
Key = pget(name, Param),
Key = pget(name, Param),
case rabbit_policy:set(
VHost, Key, pget(pattern, Param),
rabbit_misc:json_to_term(pget(definition, Param)),
@ -234,7 +232,8 @@ add_user(User) ->
add_vhost(VHost) ->
VHostName = pget(name, VHost),
rabbit_mgmt_wm_vhost:put_vhost(VHostName).
VHostTrace = pget(tracing, VHost),
rabbit_mgmt_wm_vhost:put_vhost(VHostName, VHostTrace).
add_permission(Permission) ->
rabbit_auth_backend_internal:set_permissions(pget(user, Permission),
@ -270,8 +269,7 @@ add_binding(Binding) ->
key = pget(routing_key, Binding),
args = rabbit_mgmt_util:args(pget(arguments, Binding))}).
r(Type, Props) ->
r(Type, name, Props).
r(Type, Props) -> r(Type, name, Props).
r(Type, Name, Props) ->
rabbit_misc:r(pget(vhost, Props), Type, pget(Name, Props)).

View File

@ -45,7 +45,7 @@ resource_exists(ReqData, Context) ->
to_json(ReqData, Context) ->
[Q] = rabbit_mgmt_db:augment_queues(
[queue(ReqData)], rabbit_mgmt_util:range(ReqData), full),
[queue(ReqData)], rabbit_mgmt_util:range_ceil(ReqData), full),
rabbit_mgmt_util:reply(rabbit_mgmt_format:strip_pids(Q), ReqData, Context).
accept_content(ReqData, Context) ->

View File

@ -48,7 +48,7 @@ augmented(ReqData, Context) ->
rabbit_mgmt_format:strip_pids(
rabbit_mgmt_db:augment_queues(
rabbit_mgmt_util:filter_vhost(basic(ReqData), ReqData, Context),
rabbit_mgmt_util:range(ReqData), basic)).
rabbit_mgmt_util:range_ceil(ReqData), basic)).
basic(ReqData) ->
[rabbit_mgmt_format:queue(Q) || Q <- queues0(ReqData)].

View File

@ -19,7 +19,9 @@
-export([init/1, 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, put_vhost/1]).
delete_resource/2, put_vhost/2]).
-import(rabbit_misc, [pget/2]).
-include("rabbit_mgmt.hrl").
-include_lib("webmachine/include/webmachine.hrl").
@ -47,8 +49,14 @@ to_json(ReqData, Context) ->
ReqData, Context).
accept_content(ReqData, Context) ->
put_vhost(id(ReqData)),
{true, ReqData, Context}.
Name = id(ReqData),
rabbit_mgmt_util:with_decode(
[], ReqData, Context,
fun(_, VHost) ->
put_vhost(Name, rabbit_mgmt_util:parse_bool(
pget(tracing, VHost))),
{true, ReqData, Context}
end).
delete_resource(ReqData, Context) ->
VHost = id(ReqData),
@ -63,8 +71,13 @@ is_authorized(ReqData, Context) ->
id(ReqData) ->
rabbit_mgmt_util:id(vhost, ReqData).
put_vhost(VHost) ->
case rabbit_vhost:exists(VHost) of
put_vhost(Name, Trace) ->
case rabbit_vhost:exists(Name) of
true -> ok;
false -> rabbit_vhost:add(VHost)
false -> rabbit_vhost:add(Name)
end,
case Trace of
true -> rabbit_trace:start(Name);
false -> rabbit_trace:stop(Name);
undefined -> ok
end.

View File

@ -68,9 +68,9 @@ auth_test() ->
vhosts_test() ->
assert_list([[{name, <<"/">>}]], http_get("/vhosts")),
%% Create a new one
http_put("/vhosts/myvhost", [], ?NO_CONTENT),
http_put("/vhosts/myvhost", none, ?NO_CONTENT),
%% PUT should be idempotent
http_put("/vhosts/myvhost", [], ?NO_CONTENT),
http_put("/vhosts/myvhost", none, ?NO_CONTENT),
%% Check it's there
assert_list([[{name, <<"/">>}], [{name, <<"myvhost">>}]],
http_get("/vhosts")),
@ -83,6 +83,19 @@ vhosts_test() ->
http_get("/vhosts/myvhost", ?NOT_FOUND),
http_delete("/vhosts/myvhost", ?NOT_FOUND).
vhosts_trace_test() ->
http_put("/vhosts/myvhost", none, ?NO_CONTENT),
Disabled = [{name, <<"myvhost">>}, {tracing, false}],
Enabled = [{name, <<"myvhost">>}, {tracing, true}],
Disabled = http_get("/vhosts/myvhost"),
http_put("/vhosts/myvhost", [{tracing, true}], ?NO_CONTENT),
Enabled = http_get("/vhosts/myvhost"),
http_put("/vhosts/myvhost", [{tracing, true}], ?NO_CONTENT),
Enabled = http_get("/vhosts/myvhost"),
http_put("/vhosts/myvhost", [{tracing, false}], ?NO_CONTENT),
Disabled = http_get("/vhosts/myvhost"),
http_delete("/vhosts/myvhost", ?NO_CONTENT).
users_test() ->
assert_item([{name, <<"guest">>}, {tags, <<"administrator">>}],
http_get("/whoami")),
@ -140,8 +153,8 @@ permissions_list_test() ->
?NO_CONTENT),
http_put("/users/myuser2", [{password, <<"">>}, {tags, <<"administrator">>}],
?NO_CONTENT),
http_put("/vhosts/myvhost1", [], ?NO_CONTENT),
http_put("/vhosts/myvhost2", [], ?NO_CONTENT),
http_put("/vhosts/myvhost1", none, ?NO_CONTENT),
http_put("/vhosts/myvhost2", none, ?NO_CONTENT),
Perms = [{configure, <<"foo">>}, {write, <<"foo">>}, {read, <<"foo">>}],
http_put("/permissions/myvhost1/myuser1", Perms, ?NO_CONTENT),
@ -161,7 +174,7 @@ permissions_list_test() ->
permissions_test() ->
http_put("/users/myuser", [{password, <<"myuser">>}, {tags, <<"administrator">>}],
?NO_CONTENT),
http_put("/vhosts/myvhost", [], ?NO_CONTENT),
http_put("/vhosts/myvhost", none, ?NO_CONTENT),
http_put("/permissions/myvhost/myuser",
[{configure, <<"foo">>}, {write, <<"foo">>}, {read, <<"foo">>}],
@ -207,7 +220,7 @@ test_auth(Code, Headers) ->
exchanges_test() ->
%% Can pass booleans or strings
Good = [{type, <<"direct">>}, {durable, <<"true">>}],
http_put("/vhosts/myvhost", [], ?NO_CONTENT),
http_put("/vhosts/myvhost", none, ?NO_CONTENT),
http_get("/exchanges/myvhost/foo", ?NOT_AUTHORISED),
http_put("/exchanges/myvhost/foo", Good, ?NOT_AUTHORISED),
http_put("/permissions/myvhost/guest",
@ -420,8 +433,8 @@ permissions_vhost_test() ->
PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
http_put("/users/myuser", [{password, <<"myuser">>},
{tags, <<"management">>}], ?NO_CONTENT),
http_put("/vhosts/myvhost1", [], ?NO_CONTENT),
http_put("/vhosts/myvhost2", [], ?NO_CONTENT),
http_put("/vhosts/myvhost1", none, ?NO_CONTENT),
http_put("/vhosts/myvhost2", none, ?NO_CONTENT),
http_put("/permissions/myvhost1/myuser", PermArgs, ?NO_CONTENT),
http_put("/permissions/myvhost1/guest", PermArgs, ?NO_CONTENT),
http_put("/permissions/myvhost2/guest", PermArgs, ?NO_CONTENT),
@ -572,7 +585,7 @@ defs_v(Key, URI, CreateMethod, Args) ->
defs(Key, Rep1(URI, "%2f"), CreateMethod, Rep2(Args, <<"/">>)),
%% Test against new vhost
http_put("/vhosts/test", [], ?NO_CONTENT),
http_put("/vhosts/test", none, ?NO_CONTENT),
PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
http_put("/permissions/test/guest", PermArgs, ?NO_CONTENT),
defs(Key, Rep1(URI, "test"), CreateMethod, Rep2(Args, <<"test">>),
@ -793,7 +806,7 @@ exclusive_consumer_test() ->
sorting_test() ->
QArgs = [],
PermArgs = [{configure, <<".*">>}, {write, <<".*">>}, {read, <<".*">>}],
http_put("/vhosts/vh1", [], ?NO_CONTENT),
http_put("/vhosts/vh1", none, ?NO_CONTENT),
http_put("/permissions/vh1/guest", PermArgs, ?NO_CONTENT),
http_put("/queues/%2f/test0", QArgs, ?NO_CONTENT),
http_put("/queues/vh1/test1", QArgs, ?NO_CONTENT),
@ -1078,6 +1091,8 @@ http_post(Path, List, CodeExp) ->
http_post(Path, List, User, Pass, CodeExp) ->
http_post_raw(Path, format_for_upload(List), User, Pass, CodeExp).
format_for_upload(none) ->
<<"">>;
format_for_upload(List) ->
iolist_to_binary(mochijson2:encode({struct, List})).