1%% This Source Code Form is subject to the terms of the Mozilla Public
2%% License, v. 2.0. If a copy of the MPL was not distributed with this
3%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
4%%
5%% Copyright (c) 2007-2021 VMware, Inc. or its affiliates.  All rights reserved.
6%%
7
8-module(rabbit_vhost).
9
10-include_lib("rabbit_common/include/rabbit.hrl").
11-include("vhost.hrl").
12
13-export([recover/0, recover/1, read_config/1]).
14-export([add/2, add/4, delete/2, exists/1, with/2, with_user_and_vhost/3, assert/1, update/2,
15         set_limits/2, vhost_cluster_state/1, is_running_on_all_nodes/1, await_running_on_all_nodes/2,
16        list/0, count/0, list_names/0, all/0, all_tagged_with/1]).
17-export([parse_tags/1, update_metadata/2, tag_with/2, untag_from/2, update_tags/2, update_tags/3]).
18-export([lookup/1]).
19-export([info/1, info/2, info_all/0, info_all/1, info_all/2, info_all/3]).
20-export([dir/1, msg_store_dir_path/1, msg_store_dir_wildcard/0, config_file_path/1, ensure_config_file/1]).
21-export([delete_storage/1]).
22-export([vhost_down/1]).
23-export([put_vhost/5]).
24
25%%
26%% API
27%%
28
29-type vhost_tag() :: atom() | string() | binary().
30-export_type([vhost_tag/0]).
31
32recover() ->
33    %% Clear out remnants of old incarnation, in case we restarted
34    %% faster than other nodes handled DOWN messages from us.
35    rabbit_amqqueue:on_node_down(node()),
36
37    rabbit_amqqueue:warn_file_limit(),
38
39    %% Prepare rabbit_semi_durable_route table
40    {Time, _} = timer:tc(fun() ->
41                                 rabbit_binding:recover()
42                         end),
43    rabbit_log:debug("rabbit_binding:recover/0 completed in ~fs", [Time/1000000]),
44
45    %% rabbit_vhost_sup_sup will start the actual recovery.
46    %% So recovery will be run every time a vhost supervisor is restarted.
47    ok = rabbit_vhost_sup_sup:start(),
48
49    [ok = rabbit_vhost_sup_sup:init_vhost(VHost) || VHost <- list_names()],
50    ok.
51
52recover(VHost) ->
53    VHostDir = msg_store_dir_path(VHost),
54    rabbit_log:info("Making sure data directory '~ts' for vhost '~s' exists",
55                    [VHostDir, VHost]),
56    VHostStubFile = filename:join(VHostDir, ".vhost"),
57    ok = rabbit_file:ensure_dir(VHostStubFile),
58    ok = file:write_file(VHostStubFile, VHost),
59    ok = ensure_config_file(VHost),
60    {Recovered, Failed} = rabbit_amqqueue:recover(VHost),
61    AllQs = Recovered ++ Failed,
62    QNames = [amqqueue:get_name(Q) || Q <- AllQs],
63    {Time, ok} = timer:tc(fun() ->
64                                  rabbit_binding:recover(rabbit_exchange:recover(VHost), QNames)
65                          end),
66    rabbit_log:debug("rabbit_binding:recover/2 for vhost ~s completed in ~fs", [VHost, Time/1000000]),
67
68    ok = rabbit_amqqueue:start(Recovered),
69    %% Start queue mirrors.
70    ok = rabbit_mirror_queue_misc:on_vhost_up(VHost),
71    ok.
72
73ensure_config_file(VHost) ->
74    Path = config_file_path(VHost),
75    case filelib:is_regular(Path) of
76        %% The config file exists. Do nothing.
77        true ->
78            ok;
79        %% The config file does not exist.
80        %% Check if there are queues in this vhost.
81        false ->
82            QueueDirs = rabbit_queue_index:all_queue_directory_names(VHost),
83            SegmentEntryCount = case QueueDirs of
84                %% There are no queues. Write the configured value for
85                %% the segment entry count, or the new RabbitMQ default
86                %% introduced in v3.8.17. The new default provides much
87                %% better memory footprint when many queues are used.
88                [] ->
89                    application:get_env(rabbit, queue_index_segment_entry_count,
90                        2048);
91                %% There are queues already. Write the historic RabbitMQ
92                %% default of 16384 for forward compatibility. Historic
93                %% default calculated as trunc(math:pow(2,?REL_SEQ_BITS)).
94                _ ->
95                    ?LEGACY_INDEX_SEGMENT_ENTRY_COUNT
96            end,
97            rabbit_log:info("Setting segment_entry_count for vhost '~s' with ~b queues to '~b'",
98                            [VHost, length(QueueDirs), SegmentEntryCount]),
99            file:write_file(Path, io_lib:format(
100                "%% This file is auto-generated! Edit at your own risk!~n"
101                "{segment_entry_count, ~b}.",
102                [SegmentEntryCount]))
103    end.
104
105read_config(VHost) ->
106    Config = case file:consult(config_file_path(VHost)) of
107        {ok, Val}       -> Val;
108        %% the file does not exist yet, likely due to an upgrade from a pre-3.7
109        %% message store layout so use the history default.
110        {error, _}      -> #{
111            segment_entry_count => ?LEGACY_INDEX_SEGMENT_ENTRY_COUNT
112        }
113    end,
114    rabbit_data_coercion:to_map(Config).
115
116-define(INFO_KEYS, vhost:info_keys()).
117
118-spec parse_tags(binary() | string() | atom()) -> [atom()].
119parse_tags(undefined) ->
120    [];
121parse_tags(<<"">>) ->
122    [];
123parse_tags([]) ->
124    [];
125parse_tags(Val) when is_binary(Val) ->
126    SVal = rabbit_data_coercion:to_list(Val),
127    [trim_tag(Tag) || Tag <- re:split(SVal, ",", [{return, list}])];
128parse_tags(Val) when is_list(Val) ->
129    case hd(Val) of
130      Bin when is_binary(Bin) ->
131        %% this is a list of binaries
132        [trim_tag(Tag) || Tag <- Val];
133      Atom when is_atom(Atom) ->
134        %% this is a list of atoms
135        [trim_tag(Tag) || Tag <- Val];
136      Int when is_integer(Int) ->
137        %% this is a string/charlist
138        [trim_tag(Tag) || Tag <- re:split(Val, ",", [{return, list}])]
139    end.
140
141-spec add(vhost:name(), rabbit_types:username()) -> rabbit_types:ok_or_error(any()).
142
143add(VHost, ActingUser) ->
144    case exists(VHost) of
145        true  -> ok;
146        false -> do_add(VHost, <<"">>, [], ActingUser)
147    end.
148
149-spec add(vhost:name(), binary(), [atom()], rabbit_types:username()) -> rabbit_types:ok_or_error(any()).
150
151add(Name, Description, Tags, ActingUser) ->
152    case exists(Name) of
153        true  -> ok;
154        false -> do_add(Name, Description, Tags, ActingUser)
155    end.
156
157do_add(Name, Description, Tags, ActingUser) ->
158    case Description of
159        undefined ->
160            rabbit_log:info("Adding vhost '~s' without a description", [Name]);
161        Value ->
162            rabbit_log:info("Adding vhost '~s' (description: '~s', tags: ~p)", [Name, Value, Tags])
163    end,
164    VHost = rabbit_misc:execute_mnesia_transaction(
165          fun () ->
166                  case mnesia:wread({rabbit_vhost, Name}) of
167                      [] ->
168                        Row = vhost:new(Name, [], #{description => Description, tags => Tags}),
169                        rabbit_log:debug("Inserting a virtual host record ~p", [Row]),
170                        ok = mnesia:write(rabbit_vhost, Row, write),
171                        Row;
172                      %% the vhost already exists
173                      [Row] ->
174                        Row
175                  end
176          end,
177          fun (VHost1, true) ->
178                  VHost1;
179              (VHost1, false) ->
180                  [begin
181                    Resource = rabbit_misc:r(Name, exchange, ExchangeName),
182                    rabbit_log:debug("Will declare an exchange ~p", [Resource]),
183                    _ = rabbit_exchange:declare(Resource, Type, true, false, Internal, [], ActingUser)
184                  end || {ExchangeName, Type, Internal} <-
185                          [{<<"">>,                   direct,  false},
186                           {<<"amq.direct">>,         direct,  false},
187                           {<<"amq.topic">>,          topic,   false},
188                           %% per 0-9-1 pdf
189                           {<<"amq.match">>,          headers, false},
190                           %% per 0-9-1 xml
191                           {<<"amq.headers">>,        headers, false},
192                           {<<"amq.fanout">>,         fanout,  false},
193                           {<<"amq.rabbitmq.trace">>, topic,   true}]],
194                  VHost1
195          end),
196    case rabbit_vhost_sup_sup:start_on_all_nodes(Name) of
197        ok ->
198            rabbit_event:notify(vhost_created, info(VHost)
199                                ++ [{user_who_performed_action, ActingUser},
200                                    {description, Description},
201                                    {tags, Tags}]),
202            ok;
203        {error, Reason} ->
204            Msg = rabbit_misc:format("failed to set up vhost '~s': ~p",
205                                     [Name, Reason]),
206            {error, Msg}
207    end.
208
209-spec update(vhost:name(), binary(), [atom()], rabbit_types:username()) -> rabbit_types:ok_or_error(any()).
210update(Name, Description, Tags, ActingUser) ->
211    rabbit_misc:execute_mnesia_transaction(
212          fun () ->
213                  case mnesia:wread({rabbit_vhost, Name}) of
214                      [] ->
215                          {error, {no_such_vhost, Name}};
216                      [VHost0] ->
217                          VHost = vhost:merge_metadata(VHost0, #{description => Description, tags => Tags}),
218                          rabbit_log:debug("Updating a virtual host record ~p", [VHost]),
219                          ok = mnesia:write(rabbit_vhost, VHost, write),
220                          rabbit_event:notify(vhost_updated, info(VHost)
221                                ++ [{user_who_performed_action, ActingUser},
222                                    {description, Description},
223                                    {tags, Tags}]),
224                          ok
225                  end
226          end).
227
228
229-spec delete(vhost:name(), rabbit_types:username()) -> rabbit_types:ok_or_error(any()).
230
231delete(VHost, ActingUser) ->
232    %% FIXME: We are forced to delete the queues and exchanges outside
233    %% the TX below. Queue deletion involves sending messages to the queue
234    %% process, which in turn results in further mnesia actions and
235    %% eventually the termination of that process. Exchange deletion causes
236    %% notifications which must be sent outside the TX
237    rabbit_log:info("Deleting vhost '~s'", [VHost]),
238    QDelFun = fun (Q) -> rabbit_amqqueue:delete(Q, false, false, ActingUser) end,
239    [begin
240         Name = amqqueue:get_name(Q),
241         assert_benign(rabbit_amqqueue:with(Name, QDelFun), ActingUser)
242     end || Q <- rabbit_amqqueue:list(VHost)],
243    [assert_benign(rabbit_exchange:delete(Name, false, ActingUser), ActingUser) ||
244        #exchange{name = Name} <- rabbit_exchange:list(VHost)],
245    Funs = rabbit_misc:execute_mnesia_transaction(
246          with(VHost, fun () -> internal_delete(VHost, ActingUser) end)),
247    ok = rabbit_event:notify(vhost_deleted, [{name, VHost},
248                                             {user_who_performed_action, ActingUser}]),
249    [case Fun() of
250         ok                                  -> ok;
251         {error, {no_such_vhost, VHost}} -> ok
252     end || Fun <- Funs],
253    %% After vhost was deleted from mnesia DB, we try to stop vhost supervisors
254    %% on all the nodes.
255    rabbit_vhost_sup_sup:delete_on_all_nodes(VHost),
256    ok.
257
258put_vhost(Name, Description, Tags0, Trace, Username) ->
259    Tags = case Tags0 of
260      undefined   -> <<"">>;
261      null        -> <<"">>;
262      "undefined" -> <<"">>;
263      "null"      -> <<"">>;
264      Other       -> Other
265    end,
266    ParsedTags = parse_tags(Tags),
267    rabbit_log:debug("Parsed tags ~p to ~p", [Tags, ParsedTags]),
268    Result = case exists(Name) of
269        true  ->
270            update(Name, Description, ParsedTags, Username);
271        false ->
272            add(Name, Description, ParsedTags, Username),
273             %% wait for up to 45 seconds for the vhost to initialise
274             %% on all nodes
275             case await_running_on_all_nodes(Name, 45000) of
276                 ok               ->
277                     maybe_grant_full_permissions(Name, Username);
278                 {error, timeout} ->
279                     {error, timeout}
280             end
281    end,
282    case Trace of
283        true      -> rabbit_trace:start(Name);
284        false     -> rabbit_trace:stop(Name);
285        undefined -> ok
286    end,
287    Result.
288
289%% when definitions are loaded on boot, Username here will be ?INTERNAL_USER,
290%% which does not actually exist
291maybe_grant_full_permissions(_Name, ?INTERNAL_USER) ->
292    ok;
293maybe_grant_full_permissions(Name, Username) ->
294    U = rabbit_auth_backend_internal:lookup_user(Username),
295    maybe_grant_full_permissions(U, Name, Username).
296
297maybe_grant_full_permissions({ok, _}, Name, Username) ->
298    rabbit_auth_backend_internal:set_permissions(
299      Username, Name, <<".*">>, <<".*">>, <<".*">>, Username);
300maybe_grant_full_permissions(_, _Name, _Username) ->
301    ok.
302
303
304%% 50 ms
305-define(AWAIT_SAMPLE_INTERVAL, 50).
306
307-spec await_running_on_all_nodes(vhost:name(), integer()) -> ok | {error, timeout}.
308await_running_on_all_nodes(VHost, Timeout) ->
309    Attempts = round(Timeout / ?AWAIT_SAMPLE_INTERVAL),
310    await_running_on_all_nodes0(VHost, Attempts).
311
312await_running_on_all_nodes0(_VHost, 0) ->
313    {error, timeout};
314await_running_on_all_nodes0(VHost, Attempts) ->
315    case is_running_on_all_nodes(VHost) of
316        true  -> ok;
317        _     ->
318            timer:sleep(?AWAIT_SAMPLE_INTERVAL),
319            await_running_on_all_nodes0(VHost, Attempts - 1)
320    end.
321
322-spec is_running_on_all_nodes(vhost:name()) -> boolean().
323is_running_on_all_nodes(VHost) ->
324    States = vhost_cluster_state(VHost),
325    lists:all(fun ({_Node, State}) -> State =:= running end,
326              States).
327
328-spec vhost_cluster_state(vhost:name()) -> [{atom(), atom()}].
329vhost_cluster_state(VHost) ->
330    Nodes = rabbit_nodes:all_running(),
331    lists:map(fun(Node) ->
332        State = case rabbit_misc:rpc_call(Node,
333                                          rabbit_vhost_sup_sup, is_vhost_alive,
334                                          [VHost]) of
335            {badrpc, nodedown} -> nodedown;
336            true               -> running;
337            false              -> stopped
338        end,
339        {Node, State}
340    end,
341    Nodes).
342
343vhost_down(VHost) ->
344    ok = rabbit_event:notify(vhost_down,
345                             [{name, VHost},
346                              {node, node()},
347                              {user_who_performed_action, ?INTERNAL_USER}]).
348
349delete_storage(VHost) ->
350    VhostDir = msg_store_dir_path(VHost),
351    rabbit_log:info("Deleting message store directory for vhost '~s' at '~s'", [VHost, VhostDir]),
352    %% Message store should be closed when vhost supervisor is closed.
353    case rabbit_file:recursive_delete([VhostDir]) of
354        ok                   -> ok;
355        {error, {_, enoent}} ->
356            %% a concurrent delete did the job for us
357            rabbit_log:warning("Tried to delete storage directories for vhost '~s', it failed with an ENOENT", [VHost]),
358            ok;
359        Other                ->
360            rabbit_log:warning("Tried to delete storage directories for vhost '~s': ~p", [VHost, Other]),
361            Other
362    end.
363
364assert_benign(ok, _)                 -> ok;
365assert_benign({ok, _}, _)            -> ok;
366assert_benign({ok, _, _}, _)         -> ok;
367assert_benign({error, not_found}, _) -> ok;
368assert_benign({error, {absent, Q, _}}, ActingUser) ->
369    %% Removing the mnesia entries here is safe. If/when the down node
370    %% restarts, it will clear out the on-disk storage of the queue.
371    QName = amqqueue:get_name(Q),
372    rabbit_amqqueue:internal_delete(QName, ActingUser).
373
374internal_delete(VHost, ActingUser) ->
375    [ok = rabbit_auth_backend_internal:clear_permissions(
376            proplists:get_value(user, Info), VHost, ActingUser)
377     || Info <- rabbit_auth_backend_internal:list_vhost_permissions(VHost)],
378    TopicPermissions = rabbit_auth_backend_internal:list_vhost_topic_permissions(VHost),
379    [ok = rabbit_auth_backend_internal:clear_topic_permissions(
380        proplists:get_value(user, TopicPermission), VHost, ActingUser)
381     || TopicPermission <- TopicPermissions],
382    Fs1 = [rabbit_runtime_parameters:clear(VHost,
383                                           proplists:get_value(component, Info),
384                                           proplists:get_value(name, Info),
385                                           ActingUser)
386     || Info <- rabbit_runtime_parameters:list(VHost)],
387    Fs2 = [rabbit_policy:delete(VHost, proplists:get_value(name, Info), ActingUser)
388           || Info <- rabbit_policy:list(VHost)],
389    ok = mnesia:delete({rabbit_vhost, VHost}),
390    Fs1 ++ Fs2.
391
392-spec exists(vhost:name()) -> boolean().
393
394exists(VHost) ->
395    mnesia:dirty_read({rabbit_vhost, VHost}) /= [].
396
397-spec list_names() -> [vhost:name()].
398list_names() -> mnesia:dirty_all_keys(rabbit_vhost).
399
400%% Exists for backwards compatibility, prefer list_names/0.
401-spec list() -> [vhost:name()].
402list() -> list_names().
403
404-spec all() -> [vhost:vhost()].
405all() -> mnesia:dirty_match_object(rabbit_vhost, vhost:pattern_match_all()).
406
407-spec all_tagged_with(atom()) -> [vhost:vhost()].
408all_tagged_with(TagName) ->
409    lists:filter(
410        fun(VHost) ->
411            Meta = vhost:get_metadata(VHost),
412            case Meta of
413                #{tags := Tags} ->
414                    lists:member(rabbit_data_coercion:to_atom(TagName), Tags);
415                _ -> false
416            end
417        end, all()).
418
419-spec count() -> non_neg_integer().
420count() ->
421    length(list()).
422
423-spec lookup(vhost:name()) -> vhost:vhost() | rabbit_types:ok_or_error(any()).
424lookup(VHostName) ->
425    case rabbit_misc:dirty_read({rabbit_vhost, VHostName}) of
426        {error, not_found} -> {error, {no_such_vhost, VHostName}};
427        {ok, Record}       -> Record
428    end.
429
430-spec with(vhost:name(), rabbit_misc:thunk(A)) -> A.
431with(VHostName, Thunk) ->
432    fun () ->
433        case mnesia:read({rabbit_vhost, VHostName}) of
434            []   -> mnesia:abort({no_such_vhost, VHostName});
435            [_V] -> Thunk()
436        end
437    end.
438
439-spec with_user_and_vhost(rabbit_types:username(), vhost:name(), rabbit_misc:thunk(A)) -> A.
440with_user_and_vhost(Username, VHostName, Thunk) ->
441    rabbit_misc:with_user(Username, with(VHostName, Thunk)).
442
443%% Like with/2 but outside an Mnesia tx
444
445-spec assert(vhost:name()) -> 'ok'.
446assert(VHostName) ->
447    case exists(VHostName) of
448        true  -> ok;
449        false -> throw({error, {no_such_vhost, VHostName}})
450    end.
451
452-spec update(vhost:name(), fun((vhost:vhost()) -> vhost:vhost())) -> vhost:vhost().
453update(VHostName, Fun) ->
454    case mnesia:read({rabbit_vhost, VHostName}) of
455        [] ->
456            mnesia:abort({no_such_vhost, VHostName});
457        [V] ->
458            V1 = Fun(V),
459            ok = mnesia:write(rabbit_vhost, V1, write),
460            V1
461    end.
462
463-spec update_metadata(vhost:name(), fun((map())-> map())) -> vhost:vhost() | rabbit_types:ok_or_error(any()).
464update_metadata(VHostName, Fun) ->
465    update(VHostName, fun(Record) ->
466        Meta = Fun(vhost:get_metadata(Record)),
467        vhost:set_metadata(Record, Meta)
468    end).
469
470-spec update_tags(vhost:name(), [vhost_tag()], rabbit_types:username()) -> vhost:vhost() | rabbit_types:ok_or_error(any()).
471update_tags(VHostName, Tags, ActingUser) ->
472    ConvertedTags = [rabbit_data_coercion:to_atom(I) || I <- Tags],
473    try
474        R = rabbit_misc:execute_mnesia_transaction(fun() ->
475            update_tags(VHostName, ConvertedTags)
476        end),
477        rabbit_log:info("Successfully set tags for virtual host '~s' to ~p", [VHostName, ConvertedTags]),
478        rabbit_event:notify(vhost_tags_set, [{name, VHostName},
479                                             {tags, ConvertedTags},
480                                             {user_who_performed_action, ActingUser}]),
481        R
482    catch
483        throw:{error, {no_such_vhost, _}} = Error ->
484            rabbit_log:warning("Failed to set tags for virtual host '~s': the virtual host does not exist", [VHostName]),
485            throw(Error);
486        throw:Error ->
487            rabbit_log:warning("Failed to set tags for virtual host '~s': ~p", [VHostName, Error]),
488            throw(Error);
489        exit:Error ->
490            rabbit_log:warning("Failed to set tags for virtual host '~s': ~p", [VHostName, Error]),
491            exit(Error)
492    end.
493
494-spec update_tags(vhost:name(), [vhost_tag()]) -> vhost:vhost() | rabbit_types:ok_or_error(any()).
495update_tags(VHostName, Tags) ->
496    ConvertedTags = [rabbit_data_coercion:to_atom(I) || I <- Tags],
497    update(VHostName, fun(Record) ->
498        Meta0 = vhost:get_metadata(Record),
499        Meta  = maps:update(tags, ConvertedTags, Meta0),
500        vhost:set_metadata(Record, Meta)
501    end).
502
503-spec tag_with(vhost:name(), [atom()]) -> vhost:vhost() | rabbit_types:ok_or_error(any()).
504tag_with(VHostName, Tags) when is_list(Tags) ->
505    update_metadata(VHostName, fun(#{tags := Tags0} = Meta) ->
506        maps:update(tags, lists:usort(Tags0 ++ Tags), Meta)
507    end).
508
509-spec untag_from(vhost:name(), [atom()]) -> vhost:vhost() | rabbit_types:ok_or_error(any()).
510untag_from(VHostName, Tags) when is_list(Tags) ->
511    update_metadata(VHostName, fun(#{tags := Tags0} = Meta) ->
512        maps:update(tags, lists:usort(Tags0 -- Tags), Meta)
513    end).
514
515set_limits(VHost, undefined) ->
516    vhost:set_limits(VHost, []);
517set_limits(VHost, Limits) ->
518    vhost:set_limits(VHost, Limits).
519
520
521dir(Vhost) ->
522    <<Num:128>> = erlang:md5(Vhost),
523    rabbit_misc:format("~.36B", [Num]).
524
525msg_store_dir_path(VHost) ->
526    EncodedName = dir(VHost),
527    rabbit_data_coercion:to_list(filename:join([msg_store_dir_base(), EncodedName])).
528
529msg_store_dir_wildcard() ->
530    rabbit_data_coercion:to_list(filename:join([msg_store_dir_base(), "*"])).
531
532msg_store_dir_base() ->
533    Dir = rabbit_mnesia:dir(),
534    filename:join([Dir, "msg_stores", "vhosts"]).
535
536config_file_path(VHost) ->
537    VHostDir = msg_store_dir_path(VHost),
538    filename:join(VHostDir, ".config").
539
540-spec trim_tag(list() | binary() | atom()) -> atom().
541trim_tag(Val) ->
542    rabbit_data_coercion:to_atom(string:trim(rabbit_data_coercion:to_list(Val))).
543
544%%----------------------------------------------------------------------------
545
546infos(Items, X) -> [{Item, i(Item, X)} || Item <- Items].
547
548i(name,    VHost) -> vhost:get_name(VHost);
549i(tracing, VHost) -> rabbit_trace:enabled(vhost:get_name(VHost));
550i(cluster_state, VHost) -> vhost_cluster_state(vhost:get_name(VHost));
551i(description, VHost) -> vhost:get_description(VHost);
552i(tags, VHost) -> vhost:get_tags(VHost);
553i(metadata, VHost) -> vhost:get_metadata(VHost);
554i(Item, VHost)     ->
555  rabbit_log:error("Don't know how to compute a virtual host info item '~s' for virtual host '~p'", [Item, VHost]),
556  throw({bad_argument, Item}).
557
558-spec info(vhost:vhost() | vhost:name()) -> rabbit_types:infos().
559
560info(VHost) when ?is_vhost(VHost) ->
561    infos(?INFO_KEYS, VHost);
562info(Key) ->
563    case mnesia:dirty_read({rabbit_vhost, Key}) of
564        [] -> [];
565        [VHost] -> infos(?INFO_KEYS, VHost)
566    end.
567
568-spec info(vhost:vhost(), rabbit_types:info_keys()) -> rabbit_types:infos().
569info(VHost, Items) -> infos(Items, VHost).
570
571-spec info_all() -> [rabbit_types:infos()].
572info_all()       -> info_all(?INFO_KEYS).
573
574-spec info_all(rabbit_types:info_keys()) -> [rabbit_types:infos()].
575info_all(Items) -> [info(VHost, Items) || VHost <- all()].
576
577info_all(Ref, AggregatorPid)        -> info_all(?INFO_KEYS, Ref, AggregatorPid).
578
579-spec info_all(rabbit_types:info_keys(), reference(), pid()) -> 'ok'.
580info_all(Items, Ref, AggregatorPid) ->
581    rabbit_control_misc:emitting_map(
582       AggregatorPid, Ref, fun(VHost) -> info(VHost, Items) end, all()).
583