feat: Make sure that Specify Key and ?WKEY cannot be on the same level.

This commit is contained in:
Zhongwen Deng 2022-03-07 21:48:31 +08:00
parent 6ff2db6180
commit 9b00f2756b
5 changed files with 78 additions and 27 deletions

View File

@ -18,6 +18,7 @@
-module(emqx_config_handler). -module(emqx_config_handler).
-include("logger.hrl"). -include("logger.hrl").
-include_lib("hocon/include/hoconsc.hrl").
-behaviour(gen_server). -behaviour(gen_server).
@ -75,7 +76,7 @@ update_config(SchemaModule, ConfKeyPath, UpdateArgs) ->
AtomKeyPath = [atom(Key) || Key <- ConfKeyPath], AtomKeyPath = [atom(Key) || Key <- ConfKeyPath],
gen_server:call(?MODULE, {change_config, SchemaModule, AtomKeyPath, UpdateArgs}, infinity). gen_server:call(?MODULE, {change_config, SchemaModule, AtomKeyPath, UpdateArgs}, infinity).
-spec add_handler(emqx_config:config_key_path(), handler_name()) -> ok. -spec add_handler(emqx_config:config_key_path(), handler_name()) -> ok | {error, {conflict, list()}}.
add_handler(ConfKeyPath, HandlerName) -> add_handler(ConfKeyPath, HandlerName) ->
assert_callback_function(HandlerName), assert_callback_function(HandlerName),
gen_server:call(?MODULE, {add_handler, ConfKeyPath, HandlerName}). gen_server:call(?MODULE, {add_handler, ConfKeyPath, HandlerName}).
@ -100,8 +101,10 @@ init(_) ->
{ok, #{handlers => Handlers#{?MOD => ?MODULE}}}. {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}) ->
{ok, NewHandlers} = deep_put_handler(ConfKeyPath, Handlers, HandlerName), case deep_put_handler(ConfKeyPath, Handlers, HandlerName) of
{reply, ok, State#{handlers => NewHandlers}}; {ok, NewHandlers} -> {reply, ok, State#{handlers => NewHandlers}};
{error, _Reason} = 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) ->
@ -131,12 +134,36 @@ terminate(_Reason, #{handlers := Handlers}) ->
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
deep_put_handler([], Handlers, Mod) when is_map(Handlers) -> deep_put_handler([], Handlers, Mod) ->
{ok, Handlers#{?MOD => Mod}}; {ok, Handlers#{?MOD => Mod}};
deep_put_handler([Key | KeyPath], Handlers, Mod) -> deep_put_handler([Key | KeyPath], Handlers, Mod) ->
SubHandlers = maps:get(Key, Handlers, #{}), SubHandlers = maps:get(Key, Handlers, #{}),
{ok, SubHandlers1} = deep_put_handler(KeyPath, SubHandlers, Mod), case deep_put_handler(KeyPath, SubHandlers, Mod) of
{ok, Handlers#{Key => SubHandlers1}}. {ok, NewSubHandlers} ->
NewHandlers = Handlers#{Key => NewSubHandlers},
case check_handler_conflict(NewHandlers) of
ok -> {ok, NewHandlers};
{error, Reason} -> {error, Reason}
end;
{error, _Reason} = Error -> Error
end.
%% Make sure that Specify Key and ?WKEY cannot be on the same level.
check_handler_conflict(Handlers) ->
Keys = filter_top_level_handlers(Handlers),
case lists:member(?WKEY, Keys) of
true when length(Keys) =:= 1 -> ok;
true -> {error, {conflict, Keys}};
false -> ok
end.
filter_top_level_handlers(Handlers) ->
maps:fold(
fun
(K, #{?MOD := _}, Acc) -> [K | Acc];
(_K, #{}, Acc) -> Acc;
(?MOD, _, Acc) -> Acc
end, [], Handlers).
handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs) -> handle_update_request(SchemaModule, ConfKeyPath, Handlers, UpdateArgs) ->
try try
@ -366,8 +393,12 @@ assert_callback_function(Mod) ->
schema(SchemaModule, [RootKey | _]) -> schema(SchemaModule, [RootKey | _]) ->
Roots = hocon_schema:roots(SchemaModule), Roots = hocon_schema:roots(SchemaModule),
{_, Fields} = lists:keyfind(bin(RootKey), 1, Roots), Field =
#{roots => [root], fields => #{root => [Fields]}}. case lists:keyfind(bin(RootKey), 1, Roots) of
{_, {Ref, ?REF(Ref)}} -> {Ref, ?R_REF(SchemaModule, Ref)};
{_, Field0} -> Field0
end,
#{roots => [root], fields => #{root => [Field]}}.
load_prev_handlers() -> load_prev_handlers() ->
Handlers = application:get_env(emqx, ?MODULE, #{}), Handlers = application:get_env(emqx, ?MODULE, #{}),

View File

@ -77,6 +77,23 @@ t_handler(_Config) ->
ok = emqx_config_handler:remove_handler(Wildcard1), ok = emqx_config_handler:remove_handler(Wildcard1),
ok. ok.
t_conflict_handler(_Config) ->
ok = emqx_config_handler:add_handler([sysmon, '?', '?'], ?MODULE),
?assertMatch({error, {conflict, _}},
emqx_config_handler:add_handler([sysmon, '?', cpu_check_interval], ?MODULE)),
ok = emqx_config_handler:remove_handler([sysmon, '?', '?']),
ok = emqx_config_handler:add_handler([sysmon, '?', cpu_check_interval], ?MODULE),
?assertMatch({error, {conflict, _}},
emqx_config_handler:add_handler([sysmon, '?', '?'], ?MODULE)),
ok = emqx_config_handler:remove_handler([sysmon, '?', cpu_check_interval]),
%% override
ok = emqx_config_handler:add_handler([sysmon], emqx_logger),
?assertMatch(#{handlers := #{sysmon := #{{mod} := emqx_logger}}},
emqx_config_handler:info()),
ok.
t_root_key_update(_Config) -> t_root_key_update(_Config) ->
PathKey = [sysmon], PathKey = [sysmon],
Opts = #{rawconf_with_defaults => true}, Opts = #{rawconf_with_defaults => true},
@ -246,7 +263,7 @@ t_update_sub(_Config) ->
?assertEqual({ok,#{config => 0.81, ?assertEqual({ok,#{config => 0.81,
post_config_update => #{?MODULE => ok}, post_config_update => #{?MODULE => ok},
raw_config => <<"81%">>}}, raw_config => <<"81%">>}},
emqx:update_config(SubKey, "81%", Opts#{merge => shallow})), emqx:update_config(SubKey, "81%", Opts)),
?assertEqual(0.81, emqx:get_config(SubKey)), ?assertEqual(0.81, emqx:get_config(SubKey)),
?assertEqual("81%", emqx:get_raw_config(SubKey)), ?assertEqual("81%", emqx:get_raw_config(SubKey)),

View File

@ -29,8 +29,8 @@ start(_StartType, _StartArgs) ->
{ok, Sup} = emqx_bridge_sup:start_link(), {ok, Sup} = emqx_bridge_sup:start_link(),
ok = emqx_bridge:load(), ok = emqx_bridge:load(),
ok = emqx_bridge:load_hook(), ok = emqx_bridge:load_hook(),
emqx_config_handler:add_handler(?LEAF_NODE_HDLR_PATH, ?MODULE), ok = emqx_config_handler:add_handler(?LEAF_NODE_HDLR_PATH, ?MODULE),
emqx_config_handler:add_handler(?TOP_LELVE_HDLR_PATH, emqx_bridge), ok = emqx_config_handler:add_handler(?TOP_LELVE_HDLR_PATH, emqx_bridge),
{ok, Sup}. {ok, Sup}.
stop(_State) -> stop(_State) ->

View File

@ -45,7 +45,7 @@
]). ]).
api_spec() -> api_spec() ->
emqx_dashboard_swagger:spec(?MODULE). emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
namespace() -> "configuration". namespace() -> "configuration".
@ -53,7 +53,6 @@ paths() ->
["/configs", "/configs_reset/:rootname"] ++ ["/configs", "/configs_reset/:rootname"] ++
lists:map(fun({Name, _Type}) -> ?PREFIX ++ to_list(Name) end, config_list(?EXCLUDES)). lists:map(fun({Name, _Type}) -> ?PREFIX ++ to_list(Name) end, config_list(?EXCLUDES)).
schema("/configs") -> schema("/configs") ->
#{ #{
'operationId' => configs, 'operationId' => configs,
@ -156,7 +155,7 @@ config(put, #{body := Body}, Req) ->
Path = conf_path(Req), Path = conf_path(Req),
case emqx:update_config(Path, Body, #{rawconf_with_defaults => true}) of case emqx:update_config(Path, Body, #{rawconf_with_defaults => true}) of
{ok, #{raw_config := RawConf}} -> {ok, #{raw_config := RawConf}} ->
{200, emqx_map_lib:jsonable_map(RawConf)}; {200, RawConf};
{error, Reason} -> {error, Reason} ->
{400, #{code => 'UPDATE_FAILED', message => ?ERR_MSG(Reason)}} {400, #{code => 'UPDATE_FAILED', message => ?ERR_MSG(Reason)}}
end. end.
@ -194,8 +193,7 @@ conf_path_reset(Req) ->
string:lexemes(Path, "/ "). string:lexemes(Path, "/ ").
get_full_config() -> get_full_config() ->
emqx_map_lib:jsonable_map( emqx_config:fill_defaults(emqx:get_raw_config([])).
emqx_config:fill_defaults(emqx:get_raw_config([]))).
conf_path_from_querystr(Req) -> conf_path_from_querystr(Req) ->
case proplists:get_value(<<"conf_path">>, cowboy_req:parse_qs(Req)) of case proplists:get_value(<<"conf_path">>, cowboy_req:parse_qs(Req)) of

View File

@ -160,23 +160,28 @@ t_update_disable(_Config) ->
t_update_re_failed(_Config) -> t_update_re_failed(_Config) ->
ok = emqx_common_test_helpers:load_config(emqx_modules_schema, ?REWRITE), ok = emqx_common_test_helpers:load_config(emqx_modules_schema, ?REWRITE),
Re = <<"*^test/*">>,
Rules = [#{ Rules = [#{
<<"source_topic">> => <<"test/#">>, <<"source_topic">> => <<"test/#">>,
<<"re">> => <<"*^test/*">>, <<"re">> => Re,
<<"dest_topic">> => <<"test1/$2">>, <<"dest_topic">> => <<"test1/$2">>,
<<"action">> => <<"publish">> <<"action">> => <<"publish">>
}], }],
Error = {badmatch, ?assertError({badmatch,
{error, {error,
{#{fields => {_,
#{root => [{"rewrite", [
{array, {ref,emqx_modules_schema,"rewrite"}}}]}, {validation_error,
roots => [root]}, #{
[{validation_error, path := "root.rewrite.1.re",
#{path => "root.rewrite.1.re", reason := {Re, {"nothing to repeat", 0}},
reason => {<<"*^test/*">>,{"nothing to repeat",0}}, value := Re
value => <<"*^test/*">>}}]}}}, }
?assertError(Error, emqx_rewrite:update(Rules)), }
]
}
}
}, emqx_rewrite:update(Rules)),
ok. ok.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------