Skip to content

Commit de2082a

Browse files
Merge pull request #7800 from rabbitmq/mergify/bp/v3.12.x/pr-7798
Introduce a limit for how many virtual hosts can be created in a cluster (backport #7798)
2 parents 1bb6304 + 1eaf52d commit de2082a

File tree

7 files changed

+112
-24
lines changed

7 files changed

+112
-24
lines changed

deps/rabbit/priv/schema/rabbit.schema

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,19 @@ end}.
897897
end
898898
}.
899899

900+
{mapping, "vhost_max", "rabbit.vhost_max",
901+
[{datatype, [{atom, infinity}, integer]}, {validators, ["non_negative_integer"]}]}.
902+
903+
{translation, "rabbit.vhost_max",
904+
fun(Conf) ->
905+
case cuttlefish:conf_get("vhost_max", Conf, undefined) of
906+
undefined -> cuttlefish:unset();
907+
infinity -> infinity;
908+
Val when is_integer(Val) -> Val;
909+
_ -> cuttlefish:invalid("should be a non-negative integer")
910+
end
911+
end
912+
}.
900913

901914
{mapping, "max_message_size", "rabbit.max_message_size",
902915
[{datatype, integer}, {validators, ["max_message_size"]}]}.

deps/rabbit/src/rabbit_queue_type.erl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
-type arguments() :: queue_arguments | consumer_arguments.
5656
-type queue_type() :: rabbit_classic_queue | rabbit_quorum_queue | rabbit_stream_queue.
5757

58+
-export_type([queue_type/0]).
59+
5860
-define(STATE, ?MODULE).
5961

6062
%% Recoverable mirrors shouldn't really be a generic one, but let's keep it here until

deps/rabbit/src/rabbit_vhost.erl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ add(Name, Metadata, ActingUser) ->
162162
end.
163163

164164
do_add(Name, Metadata, ActingUser) ->
165+
ok = is_over_vhost_limit(Name),
165166
Description = maps:get(description, Metadata, undefined),
166167
Tags = maps:get(tags, Metadata, []),
167168

@@ -194,6 +195,7 @@ do_add(Name, Metadata, ActingUser) ->
194195
[Name, Description, Tags])
195196
end,
196197
DefaultLimits = rabbit_db_vhost_defaults:list_limits(Name),
198+
197199
{NewOrNot, VHost} = rabbit_db_vhost:create_or_get(Name, DefaultLimits, Metadata),
198200
case NewOrNot of
199201
new ->
@@ -284,9 +286,22 @@ delete(VHost, ActingUser) ->
284286
rabbit_vhost_sup_sup:delete_on_all_nodes(VHost),
285287
ok.
286288

289+
-spec put_vhost(vhost:name(),
290+
binary(),
291+
vhost:tags(),
292+
boolean(),
293+
rabbit_types:username()) ->
294+
'ok' | {'error', any()} | {'EXIT', any()}.
287295
put_vhost(Name, Description, Tags0, Trace, Username) ->
288296
put_vhost(Name, Description, Tags0, undefined, Trace, Username).
289297

298+
-spec put_vhost(vhost:name(),
299+
binary(),
300+
vhost:unparsed_tags() | vhost:tags(),
301+
rabbit_queue_type:queue_type() | 'undefined',
302+
boolean(),
303+
rabbit_types:username()) ->
304+
'ok' | {'error', any()} | {'EXIT', any()}.
290305
put_vhost(Name, Description, Tags0, DefaultQueueType, Trace, Username) ->
291306
Tags = case Tags0 of
292307
undefined -> <<"">>;
@@ -331,6 +346,26 @@ put_vhost(Name, Description, Tags0, DefaultQueueType, Trace, Username) ->
331346
end,
332347
Result.
333348

349+
-spec is_over_vhost_limit(vhost:name()) -> 'ok' | no_return().
350+
is_over_vhost_limit(Name) ->
351+
Limit = rabbit_misc:get_env(rabbit, vhost_max, infinity),
352+
is_over_vhost_limit(Name, Limit).
353+
354+
-spec is_over_vhost_limit(vhost:name(), 'infinity' | non_neg_integer())
355+
-> 'ok' | no_return().
356+
is_over_vhost_limit(_Name, infinity) ->
357+
ok;
358+
is_over_vhost_limit(Name, Limit) when is_integer(Limit) ->
359+
case length(rabbit_db_vhost:list()) >= Limit of
360+
false ->
361+
ok;
362+
true ->
363+
ErrorMsg = rabbit_misc:format("cannot create vhost '~ts': "
364+
"vhost limit of ~tp is reached",
365+
[Name, Limit]),
366+
exit({vhost_limit_exceeded, ErrorMsg})
367+
end.
368+
334369
%% when definitions are loaded on boot, Username here will be ?INTERNAL_USER,
335370
%% which does not actually exist
336371
maybe_grant_full_permissions(_Name, ?INTERNAL_USER) ->

deps/rabbit/src/vhost.erl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646

4747
-type(description() :: binary()).
4848
-type(tag() :: atom()).
49+
-type(tags() :: [tag()]).
50+
-type(unparsed_tags() :: binary() | string() | atom()).
4951

5052
-type vhost() :: vhost_v2().
5153

@@ -76,6 +78,8 @@
7678
metadata/0,
7779
description/0,
7880
tag/0,
81+
unparsed_tags/0,
82+
tags/0,
7983
vhost/0,
8084
vhost_v2/0,
8185
vhost_pattern/0,

deps/rabbit/test/per_node_limit_SUITE.erl

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ all() ->
2121
groups() ->
2222
[
2323
{parallel_tests, [parallel], [
24-
node_connection_limit
24+
node_connection_limit,
25+
vhost_limit
2526
]}
2627
].
2728

@@ -58,6 +59,9 @@ end_per_group(_Group, Config) ->
5859
init_per_testcase(Testcase, Config) ->
5960
rabbit_ct_helpers:testcase_started(Config, Testcase).
6061

62+
end_per_testcase(vhost_limit = Testcase, Config) ->
63+
[rabbit_ct_broker_helpers:delete_vhost(Config, integer_to_binary(I)) || I <- lists:seq(1,4)],
64+
rabbit_ct_helpers:testcase_finished(Config, Testcase);
6165
end_per_testcase(Testcase, Config) ->
6266
rabbit_ct_helpers:testcase_finished(Config, Testcase).
6367

@@ -78,7 +82,25 @@ node_connection_limit(Config) ->
7882

7983
set_node_limit(Config, infinity),
8084
C = rabbit_ct_client_helpers:open_unmanaged_connection(Config, 0),
81-
true = is_pid(C).
85+
true = is_pid(C),
86+
close_all_connections([C]),
87+
ok.
88+
89+
vhost_limit(Config) ->
90+
set_vhost_limit(Config, 0),
91+
{'EXIT',{vhost_limit_exceeded, _}} = rabbit_ct_broker_helpers:add_vhost(Config, <<"foo">>),
92+
93+
set_vhost_limit(Config, 5),
94+
[ok = rabbit_ct_broker_helpers:add_vhost(Config, integer_to_binary(I)) || I <- lists:seq(1,4)],
95+
{'EXIT',{vhost_limit_exceeded, _}} = rabbit_ct_broker_helpers:add_vhost(Config, <<"5">>),
96+
[rabbit_ct_broker_helpers:delete_vhost(Config, integer_to_binary(I)) || I <- lists:seq(1,4)],
97+
98+
set_vhost_limit(Config, infinity),
99+
[ok = rabbit_ct_broker_helpers:add_vhost(Config, integer_to_binary(I)) || I <- lists:seq(1,4)],
100+
ok = rabbit_ct_broker_helpers:add_vhost(Config, <<"5">>),
101+
[rabbit_ct_broker_helpers:delete_vhost(Config, integer_to_binary(I)) || I <- lists:seq(1,5)],
102+
ok.
103+
82104

83105
%% -------------------------------------------------------------------
84106
%% Implementation
@@ -97,3 +119,8 @@ set_node_limit(Config, Limit) ->
97119
rabbit_ct_broker_helpers:rpc(Config, 0,
98120
application,
99121
set_env, [rabbit, connection_max, Limit]).
122+
123+
set_vhost_limit(Config, Limit) ->
124+
rabbit_ct_broker_helpers:rpc(Config, 0,
125+
application,
126+
set_env, [rabbit, vhost_max, Limit]).

deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/add_vhost_command.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ defmodule RabbitMQ.CLI.Ctl.Commands.AddVhostCommand do
5151
{:error, ExitCodes.exit_usage(), "Unsupported default queue type"}
5252
end
5353

54+
def output({:badrpc, {:EXIT, {:vhost_limit_exceeded, msg}}}, _opts) do
55+
{:error, ExitCodes.exit_usage(), msg}
56+
end
57+
5458
use RabbitMQ.CLI.DefaultOutput
5559

5660
def usage,

deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost.erl

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@
1010
-export([init/2, resource_exists/2, to_json/2,
1111
content_types_provided/2, content_types_accepted/2,
1212
is_authorized/2, allowed_methods/2, accept_content/2,
13-
delete_resource/2, id/1, put_vhost/6]).
13+
delete_resource/2, id/1]).
1414
-export([variances/2]).
1515

1616
-import(rabbit_misc, [pget/2]).
1717

1818
-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl").
1919
-include_lib("rabbit_common/include/rabbit.hrl").
2020

21+
-dialyzer({nowarn_function, accept_content/2}).
22+
2123
%%--------------------------------------------------------------------
2224

2325
init(Req, _State) ->
@@ -60,25 +62,28 @@ accept_content(ReqData0, Context = #context{user = #user{username = Username}})
6062
rabbit_mgmt_util:with_decode(
6163
[], ReqData0, Context,
6264
fun(_, BodyMap, ReqData) ->
63-
Trace = rabbit_mgmt_util:parse_bool(maps:get(tracing, BodyMap, undefined)),
64-
Description = maps:get(description, BodyMap, <<"">>),
65-
Tags = maps:get(tags, BodyMap, <<"">>),
66-
%% defaultqueuetype was an unfortunate name picked originally for 3.11.0,
67-
%% so fall back to it. See rabbitmq/rabbitmq-server#7734.
68-
FallbackQT = maps:get(defaultqueuetype, BodyMap, undefined),
69-
DefaultQT = maps:get(default_queue_type, BodyMap, FallbackQT),
70-
case put_vhost(Name, Description, Tags, DefaultQT, Trace, Username) of
71-
ok ->
72-
{true, ReqData, Context};
73-
{error, timeout} = E ->
74-
rabbit_mgmt_util:internal_server_error(
75-
"Timed out while waiting for the vhost to initialise", E,
76-
ReqData0, Context);
77-
{error, E} ->
78-
rabbit_mgmt_util:internal_server_error(
79-
"Error occured while adding vhost", E,
80-
ReqData0, Context)
81-
end
65+
Trace = rabbit_mgmt_util:parse_bool(maps:get(tracing, BodyMap, undefined)),
66+
Description = maps:get(description, BodyMap, <<"">>),
67+
Tags = maps:get(tags, BodyMap, <<"">>),
68+
%% defaultqueuetype was an unfortunate name picked originally for 3.11.0,
69+
%% so fall back to it. See rabbitmq/rabbitmq-server#7734.
70+
FallbackQT = maps:get(defaultqueuetype, BodyMap, undefined),
71+
DefaultQT = maps:get(default_queue_type, BodyMap, FallbackQT),
72+
case rabbit_vhost:put_vhost(Name, Description, Tags, DefaultQT, Trace, Username) of
73+
ok ->
74+
{true, ReqData, Context};
75+
{error, timeout} = E ->
76+
rabbit_mgmt_util:internal_server_error(
77+
"Timed out while waiting for the vhost to initialise", E,
78+
ReqData0, Context);
79+
{error, E} ->
80+
rabbit_mgmt_util:internal_server_error(
81+
"Error occured while adding vhost", E,
82+
ReqData0, Context);
83+
{'EXIT', {vhost_limit_exceeded,
84+
Explanation}} ->
85+
rabbit_mgmt_util:bad_request(list_to_binary(Explanation), ReqData, Context)
86+
end
8287
end).
8388

8489
delete_resource(ReqData, Context = #context{user = #user{username = Username}}) ->
@@ -101,5 +106,3 @@ id(ReqData) ->
101106
Value -> Value
102107
end.
103108

104-
put_vhost(Name, Description, Tags, DefaultQT, Trace, Username) ->
105-
rabbit_vhost:put_vhost(Name, Description, Tags, DefaultQT, Trace, Username).

0 commit comments

Comments
 (0)