chore: add more test for emqx_config_handler

This commit is contained in:
Zhongwen Deng 2022-03-03 17:02:55 +08:00
parent de0a074384
commit a862eb0252
4 changed files with 357 additions and 67 deletions

View File

@ -256,7 +256,7 @@ init_load(SchemaMod, Conf) when is_list(Conf) orelse is_binary(Conf) ->
init_load(SchemaMod, parse_hocon(Conf)); init_load(SchemaMod, parse_hocon(Conf));
init_load(SchemaMod, RawConf) when is_map(RawConf) -> init_load(SchemaMod, RawConf) when is_map(RawConf) ->
ok = save_schema_mod_and_names(SchemaMod), ok = save_schema_mod_and_names(SchemaMod),
%% Merge environment varialbe overrides on top %% Merge environment variable overrides on top
RawConfWithEnvs = merge_envs(SchemaMod, RawConf), RawConfWithEnvs = merge_envs(SchemaMod, RawConf),
ClusterOverrides = read_override_conf(#{override_to => cluster}), ClusterOverrides = read_override_conf(#{override_to => cluster}),
LocalOverrides = read_override_conf(#{override_to => local}), LocalOverrides = read_override_conf(#{override_to => local}),
@ -267,7 +267,7 @@ init_load(SchemaMod, RawConf) when is_map(RawConf) ->
check_config(SchemaMod, RawConfWithOverrides , #{}), check_config(SchemaMod, RawConfWithOverrides , #{}),
RootNames = get_root_names(), RootNames = get_root_names(),
ok = save_to_config_map(maps:with(get_atom_root_names(), CheckedConf), ok = save_to_config_map(maps:with(get_atom_root_names(), CheckedConf),
maps:with(RootNames, RawConfWithEnvs)). maps:with(RootNames, RawConfWithOverrides)).
parse_hocon(Conf) -> parse_hocon(Conf) ->
IncDirs = include_dirs(), IncDirs = include_dirs(),
@ -301,7 +301,7 @@ merge_envs(SchemaMod, RawConf) ->
}, },
hocon_tconf:merge_env_overrides(SchemaMod, RawConf, all, Opts). hocon_tconf:merge_env_overrides(SchemaMod, RawConf, all, Opts).
-spec check_config(module(), raw_config()) -> {AppEnvs, CheckedConf} -spec check_config(hocon_schema:shema(), raw_config()) -> {AppEnvs, CheckedConf}
when AppEnvs :: app_envs(), CheckedConf :: config(). when AppEnvs :: app_envs(), CheckedConf :: config().
check_config(SchemaMod, RawConf) -> check_config(SchemaMod, RawConf) ->
check_config(SchemaMod, RawConf, #{}). check_config(SchemaMod, RawConf, #{}).
@ -454,7 +454,7 @@ do_get(Type, [], Default) ->
AccIn#{conf_key(Type0, RootName) => Conf}; AccIn#{conf_key(Type0, RootName) => Conf};
(_, AccIn) -> AccIn (_, AccIn) -> AccIn
end, #{}, persistent_term:get()), end, #{}, persistent_term:get()),
case map_size(AllConf) == 0 of case AllConf =:= #{} of
true -> Default; true -> Default;
false -> AllConf false -> AllConf
end; end;

View File

@ -28,6 +28,7 @@
, remove_handler/1 , remove_handler/1
, update_config/3 , update_config/3
, get_raw_cluster_override_conf/0 , get_raw_cluster_override_conf/0
, info/0
, merge_to_old_config/2 , merge_to_old_config/2
]). ]).
@ -76,7 +77,8 @@ update_config(SchemaModule, ConfKeyPath, UpdateArgs) ->
-spec add_handler(emqx_config:config_key_path(), handler_name()) -> ok. -spec add_handler(emqx_config:config_key_path(), handler_name()) -> ok.
add_handler(ConfKeyPath, HandlerName) -> add_handler(ConfKeyPath, HandlerName) ->
gen_server:call(?MODULE, {add_handler, ConfKeyPath, HandlerName}, infinity). assert_callback_function(HandlerName),
gen_server:call(?MODULE, {add_handler, ConfKeyPath, HandlerName}).
%% @doc Remove handler asynchronously %% @doc Remove handler asynchronously
-spec remove_handler(emqx_config:config_key_path()) -> ok. -spec remove_handler(emqx_config:config_key_path()) -> ok.
@ -86,20 +88,20 @@ remove_handler(ConfKeyPath) ->
get_raw_cluster_override_conf() -> get_raw_cluster_override_conf() ->
gen_server:call(?MODULE, get_raw_cluster_override_conf). gen_server:call(?MODULE, get_raw_cluster_override_conf).
info() ->
gen_server:call(?MODULE, info).
%%============================================================================ %%============================================================================
-spec init(term()) -> {ok, state()}. -spec init(term()) -> {ok, state()}.
init(_) -> init(_) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
{ok, #{handlers => #{?MOD => ?MODULE}}}. Handlers = load_prev_handlers(),
{ok, #{handlers => Handlers#{?MOD => ?MODULE}}}.
handle_call({add_handler, ConfKeyPath, HandlerName}, _From, State = #{handlers := Handlers}) -> handle_call({add_handler, ConfKeyPath, HandlerName}, _From, State = #{handlers := Handlers}) ->
case deep_put_handler(ConfKeyPath, Handlers, HandlerName) of {ok, NewHandlers} = deep_put_handler(ConfKeyPath, Handlers, HandlerName),
{ok, NewHandlers} -> {reply, ok, State#{handlers => NewHandlers}};
{reply, ok, State#{handlers => NewHandlers}};
Error ->
{reply, Error, State}
end;
handle_call({change_config, SchemaModule, ConfKeyPath, UpdateArgs}, _From, handle_call({change_config, SchemaModule, ConfKeyPath, UpdateArgs}, _From,
#{handlers := Handlers} = State) -> #{handlers := Handlers} = State) ->
@ -108,20 +110,22 @@ handle_call({change_config, SchemaModule, ConfKeyPath, UpdateArgs}, _From,
handle_call(get_raw_cluster_override_conf, _From, State) -> handle_call(get_raw_cluster_override_conf, _From, State) ->
Reply = emqx_config:read_override_conf(#{override_to => cluster}), Reply = emqx_config:read_override_conf(#{override_to => cluster}),
{reply, Reply, State}; {reply, Reply, State};
handle_call(info, _From, State) ->
{reply, State, State};
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
Reply = ok, {reply, ok, State}.
{reply, Reply, State}.
handle_cast({remove_handler, ConfKeyPath}, handle_cast({remove_handler, ConfKeyPath}, State = #{handlers := Handlers}) ->
State = #{handlers := Handlers}) -> NewHandlers = do_remove_handler(ConfKeyPath, Handlers),
{noreply, State#{handlers => emqx_map_lib:deep_remove(ConfKeyPath ++ [?MOD], Handlers)}}; {noreply, State#{handlers => NewHandlers}};
handle_cast(_Msg, State) -> handle_cast(_Msg, State) ->
{noreply, State}. {noreply, State}.
handle_info(_Info, State) -> handle_info(_Info, State) ->
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, #{handlers := Handlers}) ->
save_handlers(Handlers),
ok. ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
@ -129,26 +133,10 @@ code_change(_OldVsn, State, _Extra) ->
deep_put_handler([], Handlers, Mod) when is_map(Handlers) -> deep_put_handler([], Handlers, Mod) when is_map(Handlers) ->
{ok, Handlers#{?MOD => Mod}}; {ok, Handlers#{?MOD => Mod}};
deep_put_handler([], _Handlers, Mod) ->
{ok, #{?MOD => Mod}};
deep_put_handler([?WKEY | KeyPath], Handlers, Mod) ->
deep_put_handler2(?WKEY, KeyPath, Handlers, Mod);
deep_put_handler([Key | KeyPath], Handlers, Mod) -> deep_put_handler([Key | KeyPath], Handlers, Mod) ->
case maps:find(?WKEY, Handlers) of
error ->
deep_put_handler2(Key, KeyPath, Handlers, Mod);
{ok, _SubHandlers} ->
{error, {cannot_override_a_wildcard_path, [?WKEY | KeyPath]}}
end.
deep_put_handler2(Key, KeyPath, Handlers, Mod) ->
SubHandlers = maps:get(Key, Handlers, #{}), SubHandlers = maps:get(Key, Handlers, #{}),
case deep_put_handler(KeyPath, SubHandlers, Mod) of {ok, SubHandlers1} = deep_put_handler(KeyPath, SubHandlers, Mod),
{ok, SubHandlers1} -> {ok, Handlers#{Key => SubHandlers1}}.
{ok, Handlers#{Key => SubHandlers1}};
Error ->
Error
end.
handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs) -> handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs) ->
try try
@ -157,9 +145,12 @@ handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs) ->
throw : Reason -> throw : Reason ->
{error, Reason}; {error, Reason};
Error : Reason : ST -> Error : Reason : ST ->
?SLOG(error, #{msg => "change_config_failed", ?SLOG(error, #{msg => "change_config_crashed",
exception => Error, exception => Error,
reason => Reason, reason => Reason,
update_req => UpdateArgs,
module => SchemaModule,
key_path => ConfKeyPath,
stacktrace => ST stacktrace => ST
}), }),
{error, config_update_crashed} {error, config_update_crashed}
@ -174,11 +165,12 @@ do_handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs) ->
{error, Result} {error, Result}
end. end.
process_update_request([_], _Handlers, {remove, _Opts}) ->
{error, "remove_root_is_forbidden"};
process_update_request(ConfKeyPath, _Handlers, {remove, Opts}) -> process_update_request(ConfKeyPath, _Handlers, {remove, Opts}) ->
OldRawConf = emqx_config:get_root_raw(ConfKeyPath), OldRawConf = emqx_config:get_root_raw(ConfKeyPath),
BinKeyPath = bin_path(ConfKeyPath), BinKeyPath = bin_path(ConfKeyPath),
NewRawConf = emqx_map_lib:deep_remove(BinKeyPath, OldRawConf), NewRawConf = emqx_map_lib:deep_remove(BinKeyPath, OldRawConf),
_ = remove_from_local_if_cluster_change(BinKeyPath, Opts),
OverrideConf = remove_from_override_config(BinKeyPath, Opts), OverrideConf = remove_from_override_config(BinKeyPath, Opts),
{ok, NewRawConf, OverrideConf, Opts}; {ok, NewRawConf, OverrideConf, Opts};
process_update_request(ConfKeyPath, Handlers, {{update, UpdateReq}, Opts}) -> process_update_request(ConfKeyPath, Handlers, {{update, UpdateReq}, Opts}) ->
@ -198,11 +190,12 @@ do_update_config([], Handlers, OldRawConf, UpdateReq, ConfKeyPath) ->
do_update_config([ConfKey | SubConfKeyPath], Handlers, OldRawConf, do_update_config([ConfKey | SubConfKeyPath], Handlers, OldRawConf,
UpdateReq, ConfKeyPath0) -> UpdateReq, ConfKeyPath0) ->
ConfKeyPath = ConfKeyPath0 ++ [ConfKey], ConfKeyPath = ConfKeyPath0 ++ [ConfKey],
SubOldRawConf = get_sub_config(bin(ConfKey), OldRawConf), ConfKeyBin = bin(ConfKey),
SubOldRawConf = get_sub_config(ConfKeyBin, OldRawConf),
SubHandlers = get_sub_handlers(ConfKey, Handlers), SubHandlers = get_sub_handlers(ConfKey, Handlers),
case do_update_config(SubConfKeyPath, SubHandlers, SubOldRawConf, UpdateReq, ConfKeyPath) of case do_update_config(SubConfKeyPath, SubHandlers, SubOldRawConf, UpdateReq, ConfKeyPath) of
{ok, NewUpdateReq} -> {ok, NewUpdateReq} ->
call_pre_config_update(Handlers, OldRawConf, #{bin(ConfKey) => NewUpdateReq}, call_pre_config_update(Handlers, OldRawConf, #{ConfKeyBin => NewUpdateReq},
ConfKeyPath); ConfKeyPath);
Error -> Error ->
Error Error
@ -211,12 +204,11 @@ do_update_config([ConfKey | SubConfKeyPath], Handlers, OldRawConf,
check_and_save_configs(SchemaModule, ConfKeyPath, Handlers, NewRawConf, OverrideConf, check_and_save_configs(SchemaModule, ConfKeyPath, Handlers, NewRawConf, OverrideConf,
UpdateArgs, Opts) -> UpdateArgs, Opts) ->
OldConf = emqx_config:get_root(ConfKeyPath), OldConf = emqx_config:get_root(ConfKeyPath),
FullRawConf = with_full_raw_confs(NewRawConf), Schema = schema(SchemaModule, ConfKeyPath),
{AppEnvs, CheckedConf} = emqx_config:check_config(SchemaModule, FullRawConf), {AppEnvs, #{root := NewConf}} = emqx_config:check_config(Schema, #{<<"root">> => NewRawConf}),
NewConf = maps:with(maps:keys(OldConf), CheckedConf),
_ = remove_from_local_if_cluster_change(ConfKeyPath, Opts),
case do_post_config_update(ConfKeyPath, Handlers, OldConf, NewConf, AppEnvs, UpdateArgs, #{}) of case do_post_config_update(ConfKeyPath, Handlers, OldConf, NewConf, AppEnvs, UpdateArgs, #{}) of
{ok, Result0} -> {ok, Result0} ->
remove_from_local_if_cluster_change(ConfKeyPath, Opts),
case save_configs(ConfKeyPath, AppEnvs, NewConf, NewRawConf, OverrideConf, case save_configs(ConfKeyPath, AppEnvs, NewConf, NewRawConf, OverrideConf,
UpdateArgs, Opts) of UpdateArgs, Opts) of
{ok, Result1} -> {ok, Result1} ->
@ -259,8 +251,7 @@ get_sub_config(ConfKey, Conf) when is_map(Conf) ->
get_sub_config(_, _Conf) -> %% the Conf is a primitive get_sub_config(_, _Conf) -> %% the Conf is a primitive
undefined. undefined.
call_pre_config_update(Handlers, OldRawConf, UpdateReq, ConfKeyPath) -> call_pre_config_update(#{?MOD := HandlerName}, OldRawConf, UpdateReq, ConfKeyPath) ->
HandlerName = maps:get(?MOD, Handlers, undefined),
case erlang:function_exported(HandlerName, pre_config_update, 3) of case erlang:function_exported(HandlerName, pre_config_update, 3) of
true -> true ->
case HandlerName:pre_config_update(ConfKeyPath, UpdateReq, OldRawConf) of case HandlerName:pre_config_update(ConfKeyPath, UpdateReq, OldRawConf) of
@ -268,21 +259,22 @@ call_pre_config_update(Handlers, OldRawConf, UpdateReq, ConfKeyPath) ->
{error, Reason} -> {error, {pre_config_update, HandlerName, Reason}} {error, Reason} -> {error, {pre_config_update, HandlerName, Reason}}
end; end;
false -> merge_to_old_config(UpdateReq, OldRawConf) false -> merge_to_old_config(UpdateReq, OldRawConf)
end. end;
call_pre_config_update(_Handlers, OldRawConf, UpdateReq, _ConfKeyPath) ->
merge_to_old_config(UpdateReq, OldRawConf).
call_post_config_update(Handlers, OldConf, NewConf, AppEnvs, UpdateReq, Result, ConfKeyPath) -> call_post_config_update(#{?MOD := HandlerName}, OldConf, NewConf, AppEnvs, UpdateReq, Result, ConfKeyPath) ->
HandlerName = maps:get(?MOD, Handlers, undefined),
case erlang:function_exported(HandlerName, post_config_update, 5) of case erlang:function_exported(HandlerName, post_config_update, 5) of
true -> true ->
case HandlerName:post_config_update(ConfKeyPath, UpdateReq, NewConf, OldConf, case HandlerName:post_config_update(ConfKeyPath, UpdateReq, NewConf, OldConf, AppEnvs) of
AppEnvs) of
ok -> {ok, Result}; ok -> {ok, Result};
{ok, Result1} -> {ok, Result1} -> {ok, Result#{HandlerName => Result1}};
{ok, Result#{HandlerName => Result1}};
{error, Reason} -> {error, {post_config_update, HandlerName, Reason}} {error, Reason} -> {error, {post_config_update, HandlerName, Reason}}
end; end;
false -> {ok, Result} false -> {ok, Result}
end. end;
call_post_config_update(_Handlers, _OldConf, _NewConf, _AppEnvs, _UpdateReq, Result, _ConfKeyPath) ->
{ok, Result}.
save_configs(ConfKeyPath, AppEnvs, CheckedConf, NewRawConf, OverrideConf, UpdateArgs, Opts) -> save_configs(ConfKeyPath, AppEnvs, CheckedConf, NewRawConf, OverrideConf, UpdateArgs, Opts) ->
case emqx_config:save_configs(AppEnvs, CheckedConf, NewRawConf, OverrideConf, Opts) of case emqx_config:save_configs(AppEnvs, CheckedConf, NewRawConf, OverrideConf, Opts) of
@ -296,19 +288,18 @@ save_configs(ConfKeyPath, AppEnvs, CheckedConf, NewRawConf, OverrideConf, Update
%% 2. either the old or the new config is not of map type %% 2. either the old or the new config is not of map type
%% the behaviour is merging the new the config to the old config if they are maps. %% the behaviour is merging the new the config to the old config if they are maps.
merge_to_old_config(UpdateReq, RawConf) when is_map(UpdateReq), is_map(RawConf) -> merge_to_old_config(UpdateReq, RawConf) when is_map(UpdateReq), is_map(RawConf) ->
{ok, maps:merge(RawConf, UpdateReq)}; {ok, emqx_map_lib:deep_merge(RawConf, UpdateReq)};
merge_to_old_config(UpdateReq, _RawConf) -> merge_to_old_config(UpdateReq, _RawConf) ->
{ok, UpdateReq}. {ok, UpdateReq}.
%% local-override.conf priority is higher than cluster-override.conf %% local-override.conf priority is higher than cluster-override.conf
%% If we want cluster to take effect, we must remove the local. %% If we want cluster to take effect, we must remove the local.
remove_from_local_if_cluster_change(BinKeyPath, Opts) -> remove_from_local_if_cluster_change(BinKeyPath, #{override_to := cluster} = Opts) ->
case maps:get(override, Opts, local) of Local = remove_from_override_config(BinKeyPath, Opts#{override_to => local}),
local -> ok; _ = emqx_config:save_to_override_conf(Local, Opts),
cluster -> ok;
Local = remove_from_override_config(BinKeyPath, Opts#{override_to => local}), remove_from_local_if_cluster_change(_BinKeyPath, _Opts) ->
emqx_config:save_to_override_conf(Local, Opts) ok.
end.
remove_from_override_config(_BinKeyPath, #{persistent := false}) -> remove_from_override_config(_BinKeyPath, #{persistent := false}) ->
undefined; undefined;
@ -337,17 +328,58 @@ return_rawconf(ConfKeyPath, #{rawconf_with_defaults := true}) ->
return_rawconf(ConfKeyPath, _) -> return_rawconf(ConfKeyPath, _) ->
emqx_config:get_raw(ConfKeyPath). emqx_config:get_raw(ConfKeyPath).
with_full_raw_confs(PartialConf) ->
maps:merge(emqx_config:get_raw([]), PartialConf).
bin_path(ConfKeyPath) -> [bin(Key) || Key <- ConfKeyPath]. bin_path(ConfKeyPath) -> [bin(Key) || Key <- ConfKeyPath].
bin(A) when is_atom(A) -> atom_to_binary(A, utf8); bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
bin(B) when is_binary(B) -> B. bin(B) when is_binary(B) -> B.
list(Atom) when is_atom(Atom) -> atom_to_list(Atom);
list(Bin) when is_binary(Bin) -> binary_to_list(Bin);
list(List) when is_list(List) -> List.
atom(Bin) when is_binary(Bin) -> atom(Bin) when is_binary(Bin) ->
binary_to_atom(Bin, utf8); binary_to_atom(Bin, utf8);
atom(Str) when is_list(Str) -> atom(Str) when is_list(Str) ->
list_to_atom(Str); list_to_atom(Str);
atom(Atom) when is_atom(Atom) -> atom(Atom) when is_atom(Atom) ->
Atom. Atom.
-dialyzer({nowarn_function, do_remove_handler/2}).
do_remove_handler(ConfKeyPath, Handlers) ->
NewHandlers = emqx_map_lib:deep_remove(ConfKeyPath ++ [?MOD], Handlers),
remove_empty_leaf(ConfKeyPath, NewHandlers).
remove_empty_leaf([], Handlers) -> Handlers;
remove_empty_leaf(KeyPath, Handlers) ->
case emqx_map_lib:deep_find(KeyPath, Handlers) =:= {ok, #{}} of
true -> %% empty leaf
Handlers1 = emqx_map_lib:deep_remove(KeyPath, Handlers),
SubKeyPath = lists:sublist(KeyPath, length(KeyPath) - 1),
remove_empty_leaf(SubKeyPath, Handlers1);
false -> Handlers
end.
assert_callback_function(Mod) ->
case erlang:function_exported(Mod, pre_config_update, 3) orelse
erlang:function_exported(Mod, post_config_update, 5) of
true -> ok;
false -> error(#{msg => "bad_emqx_config_handler_callback", module => Mod})
end,
ok.
schema(SchemaModule, [RootKey | _]) ->
Roots = SchemaModule:roots(),
Fields =
case lists:keyfind(list(RootKey), 1, Roots) of
false -> lists:keyfind(RootKey, 1, Roots);
Fields1 -> Fields1
end,
#{roots => [root], fields => #{root => [Fields]}}.
load_prev_handlers() ->
Handlers = application:get_env(emqx, ?MODULE, #{}),
application:unset_env(emqx, ?MODULE),
Handlers.
save_handlers(Handlers) ->
application:set_env(emqx, ?MODULE, Handlers).

View File

@ -0,0 +1,259 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_config_handler_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-define(MOD, {mod}).
-define(WKEY, '?').
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
all() -> emqx_common_test_helpers:all(?MODULE).
init_per_suite(Config) ->
emqx_common_test_helpers:boot_modules(all),
emqx_common_test_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_common_test_helpers:stop_apps([]).
init_per_testcase(_Case, Config) ->
Config.
end_per_testcase(_Case, _Config) ->
ok.
t_handler(_Config) ->
BadCallBackMod = emqx,
RootKey = sysmon,
%% bad
?assertError(#{msg := "bad_emqx_config_handler_callback", module := BadCallBackMod},
emqx_config_handler:add_handler([RootKey], BadCallBackMod)),
%% simple
ok = emqx_config_handler:add_handler([RootKey], ?MODULE),
#{handlers := Handlers0} = emqx_config_handler:info(),
?assertMatch(#{RootKey := #{?MOD := ?MODULE}}, Handlers0),
ok = emqx_config_handler:remove_handler([RootKey]),
#{handlers := Handlers1} = emqx_config_handler:info(),
ct:pal("Key:~p simple: ~p~n", [RootKey, Handlers1]),
?assertEqual(false, maps:is_key(RootKey, Handlers1)),
%% wildcard 1
Wildcard1 = [RootKey, '?', cpu_check_interval],
ok = emqx_config_handler:add_handler(Wildcard1, ?MODULE),
#{handlers := Handlers2} = emqx_config_handler:info(),
?assertMatch(#{RootKey := #{?WKEY := #{cpu_check_interval := #{?MOD := ?MODULE}}}}, Handlers2),
ok = emqx_config_handler:remove_handler(Wildcard1),
#{handlers := Handlers3} = emqx_config_handler:info(),
ct:pal("Key:~p wildcard1: ~p~n", [Wildcard1, Handlers3]),
?assertEqual(false, maps:is_key(RootKey, Handlers3)),
%% can_override_a_wildcard_path
ok = emqx_config_handler:add_handler(Wildcard1, ?MODULE),
?assertEqual(ok, emqx_config_handler:add_handler([RootKey, os, cpu_check_interval], ?MODULE)),
ok = emqx_config_handler:remove_handler(Wildcard1),
ok = emqx_config_handler:remove_handler([RootKey, os, cpu_check_interval]),
ok = emqx_config_handler:add_handler([RootKey, os, cpu_check_interval], ?MODULE),
ok = emqx_config_handler:add_handler(Wildcard1, ?MODULE),
ok = emqx_config_handler:remove_handler([RootKey, os, cpu_check_interval]),
ok = emqx_config_handler:remove_handler(Wildcard1),
ok.
t_root_key_update(_Config) ->
PathKey = [sysmon],
Opts = #{rawconf_with_defaults => true},
ok = emqx_config_handler:add_handler(PathKey, ?MODULE),
%% update
Old = #{<<"os">> := OS} = emqx:get_raw_config(PathKey),
{ok, Res} = emqx:update_config(PathKey,
Old#{<<"os">> => OS#{<<"cpu_check_interval">> => <<"12s">>}}, Opts),
?assertMatch(#{config := #{os := #{cpu_check_interval := 12000}},
post_config_update := #{?MODULE := ok},
raw_config := #{<<"os">> := #{<<"cpu_check_interval">> := <<"12s">>}}},
Res),
?assertMatch(#{os := #{cpu_check_interval := 12000}}, emqx:get_config(PathKey)),
%% update sub key
SubKey = PathKey ++ [os, cpu_high_watermark],
?assertEqual({ok,#{config => 0.81,
post_config_update => #{?MODULE => ok},
raw_config => <<"81%">>}},
emqx:update_config(SubKey, "81%", Opts)),
?assertEqual(0.81, emqx:get_config(SubKey)),
?assertEqual("81%", emqx:get_raw_config(SubKey)),
%% remove
?assertEqual({error, "remove_root_is_forbidden"}, emqx:remove_config(PathKey)),
?assertMatch(#{<<"os">> := _, <<"vm">> := _ }, emqx:get_raw_config(PathKey)),
ok = emqx_config_handler:remove_handler(PathKey),
ok.
t_sub_key_update_remove(_Config) ->
KeyPath = [sysmon, os, cpu_check_interval],
Opts = #{},
ok = emqx_config_handler:add_handler(KeyPath, ?MODULE),
{ok, Res} = emqx:update_config(KeyPath, <<"60s">>, Opts),
?assertMatch(#{config := 60000,
post_config_update := #{?MODULE := ok},
raw_config := <<"60s">>},
Res),
?assertMatch(60000, emqx:get_config(KeyPath)),
KeyPath2 = [sysmon, os, cpu_low_watermark],
ok = emqx_config_handler:add_handler(KeyPath2, ?MODULE),
{ok, Res1} = emqx:update_config(KeyPath2, <<"40%">>, Opts),
?assertMatch(#{config := 0.4,
post_config_update := #{},
raw_config := <<"40%">>},
Res1),
?assertMatch(0.4, emqx:get_config(KeyPath2)),
%% remove
?assertEqual({ok,#{post_config_update => #{emqx_config_handler_SUITE => ok}}},
emqx:remove_config(KeyPath)),
?assertError({config_not_found, [sysmon,os, cpu_check_interval]},
emqx:get_raw_config(KeyPath)),
ok = emqx_config_handler:remove_handler(KeyPath),
ok = emqx_config_handler:remove_handler(KeyPath2),
ok.
t_check_failed(_Config) ->
KeyPath = [sysmon, os, cpu_check_interval],
Opts = #{rawconf_with_defaults => true},
Origin = emqx:get_raw_config(KeyPath),
ok = emqx_config_handler:add_handler(KeyPath, ?MODULE),
%% It should be a duration("1h"), but we set it as a percent.
?assertMatch({error, _Res}, emqx:update_config(KeyPath, <<"80%">>, Opts)),
New = emqx:get_raw_config(KeyPath),
?assertEqual(Origin, New),
ok = emqx_config_handler:remove_handler(KeyPath),
ok.
t_stop(_Config) ->
OldPid = erlang:whereis(emqx_config_handler),
OldInfo = emqx_config_handler:info(),
emqx_config_handler:stop(),
NewPid = wait_for_new_pid(),
NewInfo = emqx_config_handler:info(),
?assertNotEqual(OldPid, NewPid),
?assertEqual(OldInfo, NewInfo),
ok.
t_callback_crash(_Config) ->
CrashPath = [sysmon, os, cpu_high_watermark],
Opts = #{rawconf_with_defaults => true},
ok = emqx_config_handler:add_handler(CrashPath, ?MODULE),
Old = emqx:get_raw_config(CrashPath),
?assertEqual({error, config_update_crashed}, emqx:update_config(CrashPath, <<"89%">>, Opts)),
New = emqx:get_raw_config(CrashPath),
?assertEqual(Old, New),
ok = emqx_config_handler:remove_handler(CrashPath),
ok.
t_pre_callback_error(_Config) ->
callback_error([sysmon, os, mem_check_interval], <<"100s">>,
{error, {pre_config_update, ?MODULE, pre_config_update_error}}),
ok.
t_post_update_error(_Config) ->
callback_error([sysmon, os, sysmem_high_watermark], <<"60%">>,
{error, {post_config_update, ?MODULE, post_config_update_error}}),
ok.
t_handler_root() ->
%% Don't rely on default emqx_config_handler's merge behaviour.
RootKey = [],
Opts = #{rawconf_with_defaults => true},
ok = emqx_config_handler:add_handler(RootKey, ?MODULE),
%% update
Old = #{<<"sysmon">> := #{<<"os">> := OS}} = emqx:get_raw_config(RootKey),
{ok, Res} = emqx:update_config(RootKey,
Old#{<<"sysmon">> => #{<<"os">> => OS#{<<"cpu_check_interval">> => <<"12s">>}}},
Opts),
?assertMatch(#{config := #{os := #{cpu_check_interval := 12000}},
post_config_update := #{?MODULE := ok},
raw_config := #{<<"os">> := #{<<"cpu_check_interval">> := <<"12s">>}}},
Res),
?assertMatch(#{sysmon := #{os := #{cpu_check_interval := 12000}}}, emqx:get_config(RootKey)),
ok.
t_get_raw_cluster_override_conf(_Config) ->
Raw0 = emqx_config:read_override_conf(#{override_to => cluster}),
Raw1 = emqx_config_handler:get_raw_cluster_override_conf(),
?assertEqual(Raw0, Raw1),
OldPid = erlang:whereis(emqx_config_handler),
OldInfo = emqx_config_handler:info(),
?assertEqual(ok, gen_server:call(emqx_config_handler, bad_call_msg)),
gen_server:cast(emqx_config_handler, bad_cast_msg),
erlang:send(emqx_config_handler, bad_info_msg),
NewPid = erlang:whereis(emqx_config_handler),
NewInfo = emqx_config_handler:info(),
?assertEqual(OldPid, NewPid),
?assertEqual(OldInfo, NewInfo),
ok.
t_save_config_failed(_Config) ->
ok.
pre_config_update([sysmon], UpdateReq, RawConf) ->
{ok, emqx_map_lib:deep_merge(RawConf, UpdateReq)};
pre_config_update([sysmon, os], UpdateReq, _RawConf) ->
{ok, UpdateReq};
pre_config_update([sysmon, os, cpu_check_interval], UpdateReq, _RawConf) ->
{ok, UpdateReq};
pre_config_update([sysmon, os, cpu_low_watermark], UpdateReq, _RawConf) ->
{ok, UpdateReq};
pre_config_update([sysmon, os, sysmem_high_watermark], UpdateReq, _RawConf) ->
{ok, UpdateReq};
pre_config_update([sysmon, os, mem_check_interval], _UpdateReq, _RawConf) ->
{error, pre_config_update_error}.
post_config_update([sysmon], _UpdateReq, _NewConf, _OldConf, _AppEnvs) ->
{ok, ok};
post_config_update([sysmon, os], _UpdateReq, _NewConf, _OldConf, _AppEnvs) ->
{ok, ok};
post_config_update([sysmon, os, cpu_check_interval], _UpdateReq, _NewConf, _OldConf, _AppEnvs) ->
{ok, ok};
post_config_update([sysmon, os, cpu_low_watermark], _UpdateReq, _NewConf, _OldConf, _AppEnvs) ->
ok;
post_config_update([sysmon, os, sysmem_high_watermark], _UpdateReq, _NewConf, _OldConf, _AppEnvs) ->
{error, post_config_update_error}.
wait_for_new_pid() ->
case erlang:whereis(emqx_config_handler) of
undefined ->
ct:sleep(10),
wait_for_new_pid();
Pid -> Pid
end.
callback_error(FailedPath, Update, Error) ->
Opts = #{rawconf_with_defaults => true},
ok = emqx_config_handler:add_handler(FailedPath, ?MODULE),
Old = emqx:get_raw_config(FailedPath),
?assertEqual(Error, emqx:update_config(FailedPath, Update, Opts)),
New = emqx:get_raw_config(FailedPath),
?assertEqual(Old, New),
ok = emqx_config_handler:remove_handler(FailedPath),
ok.

View File

@ -140,8 +140,7 @@ init([]) ->
Enable = emqx:get_config([slow_subs, enable]), Enable = emqx:get_config([slow_subs, enable]),
{ok, check_enable(Enable, InitState)}. {ok, check_enable(Enable, InitState)}.
handle_call({update_settings, #{enable := Enable} = Conf}, _From, State) -> handle_call({update_settings, #{enable := Enable}}, _From, State) ->
emqx_config:put([slow_subs], Conf),
State2 = check_enable(Enable, State), State2 = check_enable(Enable, State),
{reply, ok, State2}; {reply, ok, State2};