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