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_limit). 9 10-behaviour(rabbit_runtime_parameter). 11 12-include_lib("rabbit_common/include/rabbit.hrl"). 13 14-export([register/0]). 15-export([parse_set/3, set/3, clear/2]). 16-export([list/0, list/1]). 17-export([update_limit/4, clear_limit/3, get_limit/2]). 18-export([validate/5, notify/5, notify_clear/4]). 19-export([connection_limit/1, queue_limit/1, 20 is_over_queue_limit/1, would_exceed_queue_limit/2, 21 is_over_connection_limit/1]). 22 23-import(rabbit_misc, [pget/2, pget/3]). 24 25-rabbit_boot_step({?MODULE, 26 [{description, "vhost limit parameters"}, 27 {mfa, {rabbit_vhost_limit, register, []}}, 28 {requires, rabbit_registry}, 29 {enables, recovery}]}). 30 31%%---------------------------------------------------------------------------- 32 33register() -> 34 rabbit_registry:register(runtime_parameter, <<"vhost-limits">>, ?MODULE). 35 36validate(_VHost, <<"vhost-limits">>, Name, Term, _User) -> 37 rabbit_parameter_validation:proplist( 38 Name, vhost_limit_validation(), Term). 39 40notify(VHost, <<"vhost-limits">>, <<"limits">>, Limits, ActingUser) -> 41 rabbit_event:notify(vhost_limits_set, [{name, <<"limits">>}, 42 {user_who_performed_action, ActingUser} 43 | Limits]), 44 update_vhost(VHost, Limits). 45 46notify_clear(VHost, <<"vhost-limits">>, <<"limits">>, ActingUser) -> 47 rabbit_event:notify(vhost_limits_cleared, [{name, <<"limits">>}, 48 {user_who_performed_action, ActingUser}]), 49 %% If the function is called as a part of vhost deletion, the vhost can 50 %% be already deleted. 51 case rabbit_vhost:exists(VHost) of 52 true -> update_vhost(VHost, undefined); 53 false -> ok 54 end. 55 56connection_limit(VirtualHost) -> 57 get_limit(VirtualHost, <<"max-connections">>). 58 59queue_limit(VirtualHost) -> 60 get_limit(VirtualHost, <<"max-queues">>). 61 62 63query_limits(VHost) -> 64 case rabbit_runtime_parameters:list(VHost, <<"vhost-limits">>) of 65 [] -> []; 66 Params -> [ {pget(vhost, Param), pget(value, Param)} 67 || Param <- Params, 68 pget(value, Param) =/= undefined, 69 pget(name, Param) == <<"limits">> ] 70 end. 71 72 73-spec list() -> [{vhost:name(), rabbit_types:infos()}]. 74list() -> 75 query_limits('_'). 76 77-spec list(vhost:name()) -> rabbit_types:infos(). 78list(VHost) -> 79 case query_limits(VHost) of 80 [] -> []; 81 [{VHost, Value}] -> Value 82 end. 83 84-spec is_over_connection_limit(vhost:name()) -> {true, non_neg_integer()} | false. 85 86is_over_connection_limit(VirtualHost) -> 87 case rabbit_vhost_limit:connection_limit(VirtualHost) of 88 %% no limit configured 89 undefined -> false; 90 %% with limit = 0, no connections are allowed 91 {ok, 0} -> {true, 0}; 92 {ok, Limit} when is_integer(Limit) andalso Limit > 0 -> 93 ConnectionCount = 94 rabbit_connection_tracking:count_tracked_items_in({vhost, VirtualHost}), 95 case ConnectionCount >= Limit of 96 false -> false; 97 true -> {true, Limit} 98 end; 99 %% any negative value means "no limit". Note that parameter validation 100 %% will replace negative integers with 'undefined', so this is to be 101 %% explicit and extra defensive 102 {ok, Limit} when is_integer(Limit) andalso Limit < 0 -> false; 103 %% ignore non-integer limits 104 {ok, _Limit} -> false 105 end. 106 107-spec would_exceed_queue_limit(non_neg_integer(), vhost:name()) -> 108 {true, non_neg_integer(), non_neg_integer()} | false. 109 110would_exceed_queue_limit(AdditionalCount, VirtualHost) -> 111 case queue_limit(VirtualHost) of 112 undefined -> 113 %% no limit configured 114 false; 115 {ok, 0} -> 116 %% with limit = 0, no queues can be declared (perhaps not very 117 %% useful but consistent with the connection limit) 118 {true, 0, 0}; 119 {ok, Limit} when is_integer(Limit) andalso Limit > 0 -> 120 QueueCount = rabbit_amqqueue:count(VirtualHost), 121 case (AdditionalCount + QueueCount) > Limit of 122 false -> false; 123 true -> {true, Limit, QueueCount} 124 end; 125 {ok, Limit} when is_integer(Limit) andalso Limit < 0 -> 126 %% any negative value means "no limit". Note that parameter validation 127 %% will replace negative integers with 'undefined', so this is to be 128 %% explicit and extra defensive 129 false; 130 {ok, _Limit} -> 131 %% ignore non-integer limits 132 false 133 end. 134 135-spec is_over_queue_limit(vhost:name()) -> {true, non_neg_integer()} | false. 136 137is_over_queue_limit(VirtualHost) -> 138 case would_exceed_queue_limit(1, VirtualHost) of 139 {true, Limit, _QueueCount} -> {true, Limit}; 140 false -> false 141 end. 142 143%%---------------------------------------------------------------------------- 144 145parse_set(VHost, Defn, ActingUser) -> 146 Definition = rabbit_data_coercion:to_binary(Defn), 147 case rabbit_json:try_decode(Definition) of 148 {ok, Term} -> 149 set(VHost, maps:to_list(Term), ActingUser); 150 {error, Reason} -> 151 {error_string, 152 rabbit_misc:format("JSON decoding error. Reason: ~ts", [Reason])} 153 end. 154 155set(VHost, Defn, ActingUser) -> 156 rabbit_runtime_parameters:set_any(VHost, <<"vhost-limits">>, 157 <<"limits">>, Defn, ActingUser). 158 159clear(VHost, ActingUser) -> 160 rabbit_runtime_parameters:clear_any(VHost, <<"vhost-limits">>, 161 <<"limits">>, ActingUser). 162 163update_limit(VHost, Name, Value, ActingUser) -> 164 OldDef = case rabbit_runtime_parameters:list(VHost, <<"vhost-limits">>) of 165 [] -> []; 166 [Param] -> pget(value, Param, []) 167 end, 168 NewDef = [{Name, Value} | lists:keydelete(Name, 1, OldDef)], 169 set(VHost, NewDef, ActingUser). 170 171clear_limit(VHost, Name, ActingUser) -> 172 OldDef = case rabbit_runtime_parameters:list(VHost, <<"vhost-limits">>) of 173 [] -> []; 174 [Param] -> pget(value, Param, []) 175 end, 176 NewDef = lists:keydelete(Name, 1, OldDef), 177 set(VHost, NewDef, ActingUser). 178 179vhost_limit_validation() -> 180 [{<<"max-connections">>, fun rabbit_parameter_validation:integer/2, optional}, 181 {<<"max-queues">>, fun rabbit_parameter_validation:integer/2, optional}]. 182 183update_vhost(VHostName, Limits) -> 184 rabbit_misc:execute_mnesia_transaction( 185 fun() -> 186 rabbit_vhost:update(VHostName, 187 fun(VHost) -> 188 rabbit_vhost:set_limits(VHost, Limits) 189 end) 190 end), 191 ok. 192 193get_limit(VirtualHost, Limit) -> 194 case rabbit_runtime_parameters:list(VirtualHost, <<"vhost-limits">>) of 195 [] -> undefined; 196 [Param] -> case pget(value, Param) of 197 undefined -> undefined; 198 Val -> case pget(Limit, Val) of 199 undefined -> undefined; 200 %% no limit 201 N when N < 0 -> undefined; 202 N when N >= 0 -> {ok, N} 203 end 204 end 205 end. 206