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_runtime_parameters). 9 10%% Runtime parameters are bits of configuration that are 11%% set, as the name implies, at runtime and not in the config file. 12%% 13%% The benefits of storing some bits of configuration at runtime vary: 14%% 15%% * Some parameters are vhost-specific 16%% * Others are specific to individual nodes 17%% * ...or even queues, exchanges, etc 18%% 19%% The most obvious use case for runtime parameters is policies but 20%% there are others: 21%% 22%% * Plugin-specific parameters that only make sense at runtime, 23%% e.g. Federation and Shovel link settings 24%% * Exchange and queue decorators 25%% 26%% Parameters are grouped by components, e.g. <<"policy">> or <<"shovel">>. 27%% Components are mapped to modules that perform validation. 28%% Runtime parameter values are then looked up by the modules that 29%% need to use them. 30%% 31%% Parameters are stored in Mnesia and can be global. Their changes 32%% are broadcasted over rabbit_event. 33%% 34%% Global parameters keys are atoms and values are JSON documents. 35%% 36%% See also: 37%% 38%% * rabbit_policies 39%% * rabbit_policy 40%% * rabbit_registry 41%% * rabbit_event 42 43-include_lib("rabbit_common/include/rabbit.hrl"). 44 45-export([parse_set/5, set/5, set_any/5, clear/4, clear_any/4, list/0, list/1, 46 list_component/1, list/2, list_formatted/1, list_formatted/3, 47 lookup/3, value/3, value/4, info_keys/0, clear_component/2]). 48 49-export([parse_set_global/3, set_global/3, value_global/1, value_global/2, 50 list_global/0, list_global_formatted/0, list_global_formatted/2, 51 lookup_global/1, global_info_keys/0, clear_global/2]). 52 53%%---------------------------------------------------------------------------- 54 55-type ok_or_error_string() :: 'ok' | {'error_string', string()}. 56-type ok_thunk_or_error_string() :: ok_or_error_string() | fun(() -> 'ok'). 57 58%%--------------------------------------------------------------------------- 59 60-import(rabbit_misc, [pget/2]). 61 62-define(TABLE, rabbit_runtime_parameters). 63 64%%--------------------------------------------------------------------------- 65 66-spec parse_set(rabbit_types:vhost(), binary(), binary(), string(), 67 rabbit_types:user() | rabbit_types:username() | 'none') 68 -> ok_or_error_string(). 69 70parse_set(_, <<"policy">>, _, _, _) -> 71 {error_string, "policies may not be set using this method"}; 72parse_set(VHost, Component, Name, String, User) -> 73 Definition = rabbit_data_coercion:to_binary(String), 74 case rabbit_json:try_decode(Definition) of 75 {ok, Term} when is_map(Term) -> set(VHost, Component, Name, maps:to_list(Term), User); 76 {ok, Term} -> set(VHost, Component, Name, Term, User); 77 {error, Reason} -> 78 {error_string, 79 rabbit_misc:format("JSON decoding error. Reason: ~ts", [Reason])} 80 end. 81 82-spec set(rabbit_types:vhost(), binary(), binary(), term(), 83 rabbit_types:user() | rabbit_types:username() | 'none') 84 -> ok_or_error_string(). 85 86set(_, <<"policy">>, _, _, _) -> 87 {error_string, "policies may not be set using this method"}; 88set(VHost, Component, Name, Term, User) -> 89 set_any(VHost, Component, Name, Term, User). 90 91parse_set_global(Name, String, ActingUser) -> 92 Definition = rabbit_data_coercion:to_binary(String), 93 case rabbit_json:try_decode(Definition) of 94 {ok, Term} when is_map(Term) -> set_global(Name, maps:to_list(Term), ActingUser); 95 {ok, Term} -> set_global(Name, Term, ActingUser); 96 {error, Reason} -> 97 {error_string, 98 rabbit_misc:format("JSON decoding error. Reason: ~ts", [Reason])} 99 end. 100 101-spec set_global(atom(), term(), rabbit_types:username()) -> 'ok'. 102 103set_global(Name, Term, ActingUser) -> 104 NameAsAtom = rabbit_data_coercion:to_atom(Name), 105 rabbit_log:debug("Setting global parameter '~s' to ~p", [NameAsAtom, Term]), 106 mnesia_update(NameAsAtom, Term), 107 event_notify(parameter_set, none, global, [{name, NameAsAtom}, 108 {value, Term}, 109 {user_who_performed_action, ActingUser}]), 110 ok. 111 112format_error(L) -> 113 {error_string, rabbit_misc:format_many([{"Validation failed~n", []} | L])}. 114 115-spec set_any(rabbit_types:vhost(), binary(), binary(), term(), 116 rabbit_types:user() | rabbit_types:username() | 'none') 117 -> ok_or_error_string(). 118 119set_any(VHost, Component, Name, Term, User) -> 120 case set_any0(VHost, Component, Name, Term, User) of 121 ok -> ok; 122 {errors, L} -> format_error(L) 123 end. 124 125set_any0(VHost, Component, Name, Term, User) -> 126 rabbit_log:debug("Asked to set or update runtime parameter '~s' in vhost '~s' " 127 "for component '~s', value: ~p", 128 [Name, VHost, Component, Term]), 129 case lookup_component(Component) of 130 {ok, Mod} -> 131 case flatten_errors( 132 Mod:validate(VHost, Component, Name, Term, get_user(User))) of 133 ok -> 134 case mnesia_update(VHost, Component, Name, Term) of 135 {old, Term} -> 136 ok; 137 _ -> 138 ActingUser = get_username(User), 139 event_notify( 140 parameter_set, VHost, Component, 141 [{name, Name}, 142 {value, Term}, 143 {user_who_performed_action, ActingUser}]), 144 Mod:notify(VHost, Component, Name, Term, ActingUser) 145 end, 146 ok; 147 E -> 148 E 149 end; 150 E -> 151 E 152 end. 153 154%% Validate only an user record as expected by the API before #rabbitmq-event-exchange-10 155get_user(#user{} = User) -> 156 User; 157get_user(_) -> 158 none. 159 160get_username(#user{username = Username}) -> 161 Username; 162get_username(none) -> 163 ?INTERNAL_USER; 164get_username(Any) -> 165 Any. 166 167mnesia_update(Key, Term) -> 168 rabbit_misc:execute_mnesia_transaction(mnesia_update_fun(Key, Term)). 169 170mnesia_update(VHost, Comp, Name, Term) -> 171 rabbit_misc:execute_mnesia_transaction( 172 rabbit_vhost:with(VHost, mnesia_update_fun({VHost, Comp, Name}, Term))). 173 174mnesia_update_fun(Key, Term) -> 175 fun () -> 176 Res = case mnesia:read(?TABLE, Key, read) of 177 [] -> new; 178 [Params] -> {old, Params#runtime_parameters.value} 179 end, 180 ok = mnesia:write(?TABLE, c(Key, Term), write), 181 Res 182 end. 183 184-spec clear(rabbit_types:vhost(), binary(), binary(), rabbit_types:username()) 185 -> ok_thunk_or_error_string(). 186 187clear(_, <<"policy">> , _, _) -> 188 {error_string, "policies may not be cleared using this method"}; 189clear(VHost, Component, Name, ActingUser) -> 190 clear_any(VHost, Component, Name, ActingUser). 191 192clear_global(Key, ActingUser) -> 193 KeyAsAtom = rabbit_data_coercion:to_atom(Key), 194 Notify = fun() -> 195 event_notify(parameter_set, none, global, 196 [{name, KeyAsAtom}, 197 {user_who_performed_action, ActingUser}]), 198 ok 199 end, 200 case value_global(KeyAsAtom) of 201 not_found -> 202 {error_string, "Parameter does not exist"}; 203 _ -> 204 F = fun () -> 205 ok = mnesia:delete(?TABLE, KeyAsAtom, write) 206 end, 207 ok = rabbit_misc:execute_mnesia_transaction(F), 208 case mnesia:is_transaction() of 209 true -> Notify; 210 false -> Notify() 211 end 212 end. 213 214clear_component(Component, ActingUser) -> 215 case list_component(Component) of 216 [] -> 217 ok; 218 Xs -> 219 [clear(pget(vhost, X), 220 pget(component, X), 221 pget(name, X), 222 ActingUser) || X <- Xs], 223 ok 224 end. 225 226-spec clear_any(rabbit_types:vhost(), binary(), binary(), rabbit_types:username()) 227 -> ok_thunk_or_error_string(). 228 229clear_any(VHost, Component, Name, ActingUser) -> 230 Notify = fun () -> 231 case lookup_component(Component) of 232 {ok, Mod} -> event_notify( 233 parameter_cleared, VHost, Component, 234 [{name, Name}, 235 {user_who_performed_action, ActingUser}]), 236 Mod:notify_clear(VHost, Component, Name, ActingUser); 237 _ -> ok 238 end 239 end, 240 case lookup(VHost, Component, Name) of 241 not_found -> {error_string, "Parameter does not exist"}; 242 _ -> mnesia_clear(VHost, Component, Name), 243 case mnesia:is_transaction() of 244 true -> Notify; 245 false -> Notify() 246 end 247 end. 248 249mnesia_clear(VHost, Component, Name) -> 250 F = fun () -> 251 ok = mnesia:delete(?TABLE, {VHost, Component, Name}, write) 252 end, 253 ok = rabbit_misc:execute_mnesia_transaction(rabbit_vhost:with(VHost, F)). 254 255event_notify(_Event, _VHost, <<"policy">>, _Props) -> 256 ok; 257event_notify(Event, none, Component, Props) -> 258 rabbit_event:notify(Event, [{component, Component} | Props]); 259event_notify(Event, VHost, Component, Props) -> 260 rabbit_event:notify(Event, [{vhost, VHost}, 261 {component, Component} | Props]). 262 263-spec list() -> [rabbit_types:infos()]. 264 265list() -> 266 [p(P) || #runtime_parameters{ key = {_VHost, Comp, _Name}} = P <- 267 rabbit_misc:dirty_read_all(?TABLE), Comp /= <<"policy">>]. 268 269-spec list(rabbit_types:vhost() | '_') -> [rabbit_types:infos()]. 270 271list(VHost) -> list(VHost, '_'). 272 273-spec list_component(binary()) -> [rabbit_types:infos()]. 274 275list_component(Component) -> list('_', Component). 276 277%% Not dirty_match_object since that would not be transactional when used in a 278%% tx context 279-spec list(rabbit_types:vhost() | '_', binary() | '_') 280 -> [rabbit_types:infos()]. 281 282list(VHost, Component) -> 283 mnesia:async_dirty( 284 fun () -> 285 case VHost of 286 '_' -> ok; 287 _ -> rabbit_vhost:assert(VHost) 288 end, 289 Match = #runtime_parameters{key = {VHost, Component, '_'}, 290 _ = '_'}, 291 [p(P) || #runtime_parameters{key = {_VHost, Comp, _Name}} = P <- 292 mnesia:match_object(?TABLE, Match, read), 293 Comp =/= <<"policy">> orelse Component =:= <<"policy">>] 294 end). 295 296list_global() -> 297 %% list only atom keys 298 mnesia:async_dirty( 299 fun () -> 300 Match = #runtime_parameters{key = '_', _ = '_'}, 301 [p(P) || P <- mnesia:match_object(?TABLE, Match, read), 302 is_atom(P#runtime_parameters.key)] 303 end). 304 305-spec list_formatted(rabbit_types:vhost()) -> [rabbit_types:infos()]. 306 307list_formatted(VHost) -> 308 [ format_parameter(info_keys(), P) || P <- list(VHost) ]. 309 310format_parameter(InfoKeys, P) -> 311 lists:foldr(fun 312 (value, Acc) -> 313 [{value, rabbit_json:encode(pget(value, P))} | Acc]; 314 (Key, Acc) -> 315 case lists:keyfind(Key, 1, P) of 316 false -> Acc; 317 {Key, Val} -> [{Key, Val} | Acc] 318 end 319 end, 320 [], InfoKeys). 321 322-spec list_formatted(rabbit_types:vhost(), reference(), pid()) -> 'ok'. 323 324list_formatted(VHost, Ref, AggregatorPid) -> 325 rabbit_control_misc:emitting_map( 326 AggregatorPid, Ref, 327 fun(P) -> format_parameter(info_keys(), P) end, list(VHost)). 328 329list_global_formatted() -> 330 [ format_parameter(global_info_keys(), P) || P <- list_global() ]. 331 332list_global_formatted(Ref, AggregatorPid) -> 333 rabbit_control_misc:emitting_map( 334 AggregatorPid, Ref, 335 fun(P) -> format_parameter(global_info_keys(), P) end, list_global()). 336 337-spec lookup(rabbit_types:vhost(), binary(), binary()) 338 -> rabbit_types:infos() | 'not_found'. 339 340lookup(VHost, Component, Name) -> 341 case lookup0({VHost, Component, Name}, rabbit_misc:const(not_found)) of 342 not_found -> not_found; 343 Params -> p(Params) 344 end. 345 346lookup_global(Name) -> 347 case lookup0(Name, rabbit_misc:const(not_found)) of 348 not_found -> not_found; 349 Params -> p(Params) 350 end. 351 352-spec value(rabbit_types:vhost(), binary(), binary()) -> term(). 353 354value(VHost, Comp, Name) -> value0({VHost, Comp, Name}). 355 356-spec value(rabbit_types:vhost(), binary(), binary(), term()) -> term(). 357 358value(VHost, Comp, Name, Def) -> value0({VHost, Comp, Name}, Def). 359 360-spec value_global(atom()) -> term() | 'not_found'. 361 362value_global(Key) -> 363 value0(Key). 364 365-spec value_global(atom(), term()) -> term(). 366 367value_global(Key, Default) -> 368 value0(Key, Default). 369 370value0(Key) -> 371 case lookup0(Key, rabbit_misc:const(not_found)) of 372 not_found -> not_found; 373 Params -> Params#runtime_parameters.value 374 end. 375 376value0(Key, Default) -> 377 Params = lookup0(Key, fun () -> lookup_missing(Key, Default) end), 378 Params#runtime_parameters.value. 379 380lookup0(Key, DefaultFun) -> 381 case mnesia:dirty_read(?TABLE, Key) of 382 [] -> DefaultFun(); 383 [R] -> R 384 end. 385 386lookup_missing(Key, Default) -> 387 rabbit_misc:execute_mnesia_transaction( 388 fun () -> 389 case mnesia:read(?TABLE, Key, read) of 390 [] -> Record = c(Key, Default), 391 mnesia:write(?TABLE, Record, write), 392 Record; 393 [R] -> R 394 end 395 end). 396 397c(Key, Default) -> 398 #runtime_parameters{key = Key, 399 value = Default}. 400 401p(#runtime_parameters{key = {VHost, Component, Name}, value = Value}) -> 402 [{vhost, VHost}, 403 {component, Component}, 404 {name, Name}, 405 {value, Value}]; 406 407p(#runtime_parameters{key = Key, value = Value}) when is_atom(Key) -> 408 [{name, Key}, 409 {value, Value}]. 410 411-spec info_keys() -> rabbit_types:info_keys(). 412 413info_keys() -> [component, name, value]. 414 415global_info_keys() -> [name, value]. 416 417%%--------------------------------------------------------------------------- 418 419lookup_component(Component) -> 420 case rabbit_registry:lookup_module( 421 runtime_parameter, list_to_atom(binary_to_list(Component))) of 422 {error, not_found} -> {errors, 423 [{"component ~s not found", [Component]}]}; 424 {ok, Module} -> {ok, Module} 425 end. 426 427flatten_errors(L) -> 428 case [{F, A} || I <- lists:flatten([L]), {error, F, A} <- [I]] of 429 [] -> ok; 430 E -> {errors, E} 431 end. 432