Merge pull request #7279 from zhongwencool/hot-conf-global-zone

feat: Hot conf global zone
This commit is contained in:
zhongwencool 2022-03-11 18:03:35 +08:00 committed by GitHub
commit 59b10fc80b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 101 additions and 17 deletions

View File

@ -38,9 +38,7 @@
-type bar_separated_list() :: list(). -type bar_separated_list() :: list().
-type ip_port() :: tuple(). -type ip_port() :: tuple().
-type cipher() :: map(). -type cipher() :: map().
-type qos() :: integer().
-typerefl_from_string({qos/0, emqx_schema, to_qos}).
-typerefl_from_string({duration/0, emqx_schema, to_duration}). -typerefl_from_string({duration/0, emqx_schema, to_duration}).
-typerefl_from_string({duration_s/0, emqx_schema, to_duration_s}). -typerefl_from_string({duration_s/0, emqx_schema, to_duration_s}).
-typerefl_from_string({duration_ms/0, emqx_schema, to_duration_ms}). -typerefl_from_string({duration_ms/0, emqx_schema, to_duration_ms}).
@ -59,12 +57,14 @@
, validations/0 , validations/0
]). ]).
-export([ qos/0]).
% workaround: prevent being recognized as unused functions % workaround: prevent being recognized as unused functions
-export([to_duration/1, to_duration_s/1, to_duration_ms/1, -export([to_duration/1, to_duration_s/1, to_duration_ms/1,
mk_duration/2, to_bytesize/1, to_wordsize/1, mk_duration/2, to_bytesize/1, to_wordsize/1,
to_percent/1, to_comma_separated_list/1, to_percent/1, to_comma_separated_list/1,
to_bar_separated_list/1, to_ip_port/1, to_bar_separated_list/1, to_ip_port/1,
to_erl_cipher_suite/1, to_qos/1, to_erl_cipher_suite/1,
to_comma_separated_atoms/1 to_comma_separated_atoms/1
]). ]).
@ -73,7 +73,7 @@
-reflect_type([ duration/0, duration_s/0, duration_ms/0, -reflect_type([ duration/0, duration_s/0, duration_ms/0,
bytesize/0, wordsize/0, percent/0, file/0, bytesize/0, wordsize/0, percent/0, file/0,
comma_separated_list/0, bar_separated_list/0, ip_port/0, comma_separated_list/0, bar_separated_list/0, ip_port/0,
cipher/0, qos/0, cipher/0,
comma_separated_atoms/0 comma_separated_atoms/0
]). ]).
@ -1558,12 +1558,6 @@ to_comma_separated_atoms(Str) ->
to_bar_separated_list(Str) -> to_bar_separated_list(Str) ->
{ok, string:tokens(Str, "| ")}. {ok, string:tokens(Str, "| ")}.
to_qos(Str) ->
case string:to_integer(Str) of
{Num, []} when Num >= 0 andalso Num =< 2 -> {ok, Num};
_ -> {error, Str}
end.
to_ip_port(Str) -> to_ip_port(Str) ->
case string:tokens(Str, ": ") of case string:tokens(Str, ": ") of
[Ip, Port] -> [Ip, Port] ->
@ -1688,3 +1682,7 @@ When authenticating a login (username, client ID, etc.) the authenticators are c
in the configured order.<br> in the configured order.<br>
"""]) """])
}. }.
-spec qos() -> typerefl:type().
qos() ->
typerefl:alias("qos", typerefl:union([0, 1, 2])).

View File

@ -20,7 +20,7 @@
namespace() -> zone. namespace() -> zone.
%% this shcema module is not used at root level. %% this schema module is not used at root level.
%% roots are added only for document generation. %% roots are added only for document generation.
roots() -> ["mqtt", "stats", "flapping_detect", "force_shutdown", roots() -> ["mqtt", "stats", "flapping_detect", "force_shutdown",
"conn_congestion", "force_gc", "conn_congestion", "force_gc",

View File

@ -17,12 +17,17 @@
-module(emqx_mgmt_api_configs). -module(emqx_mgmt_api_configs).
-include_lib("hocon/include/hoconsc.hrl"). -include_lib("hocon/include/hoconsc.hrl").
-include_lib("emqx/include/logger.hrl").
-behaviour(minirest_api). -behaviour(minirest_api).
-export([api_spec/0, namespace/0]). -export([api_spec/0, namespace/0]).
-export([paths/0, schema/1, fields/1]). -export([paths/0, schema/1, fields/1]).
-export([config/3, config_reset/3, configs/3, get_full_config/0]). -export([ config/3
, config_reset/3
, configs/3
, get_full_config/0
, global_zone_configs/3]).
-export([gen_schema/1]). -export([gen_schema/1]).
@ -51,7 +56,8 @@
<<"event_message">>, <<"event_message">>,
<<"prometheus">>, <<"prometheus">>,
<<"telemetry">> <<"telemetry">>
]). ] ++ global_zone_roots()
).
api_spec() -> api_spec() ->
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
@ -59,7 +65,7 @@ api_spec() ->
namespace() -> "configuration". namespace() -> "configuration".
paths() -> paths() ->
["/configs", "/configs_reset/:rootname"] ++ ["/configs", "/configs_reset/:rootname", "/configs/global_zone"] ++
lists:map(fun({Name, _Type}) -> ?PREFIX ++ binary_to_list(Name) end, config_list()). lists:map(fun({Name, _Type}) -> ?PREFIX ++ binary_to_list(Name) end, config_list()).
schema("/configs") -> schema("/configs") ->
@ -104,6 +110,25 @@ schema("/configs_reset/:rootname") ->
} }
} }
}; };
schema("/configs/global_zone") ->
Schema = global_zone_schema(),
#{
'operationId' => global_zone_configs,
get => #{
tags => [conf],
description => <<"Get the global zone configs">>,
responses => #{200 => Schema}
},
put => #{
tags => [conf],
description => <<"Update globbal zone configs">>,
'requestBody' => Schema,
responses => #{
200 => Schema,
400 => emqx_dashboard_swagger:error_codes(['UPDATE_FAILED'])
}
}
};
schema(Path) -> schema(Path) ->
{RootKey, {_Root, Schema}} = find_schema(Path), {RootKey, {_Root, Schema}} = find_schema(Path),
#{ #{
@ -159,6 +184,28 @@ config(put, #{body := Body}, Req) ->
{400, #{code => 'UPDATE_FAILED', message => ?ERR_MSG(Reason)}} {400, #{code => 'UPDATE_FAILED', message => ?ERR_MSG(Reason)}}
end. end.
global_zone_configs(get, _Params, _Req) ->
Paths = global_zone_roots(),
Zones = lists:foldl(fun(Path, Acc) -> Acc#{Path => get_config_with_default([Path])} end,
#{}, Paths),
{200, Zones};
global_zone_configs(put, #{body := Body}, _Req) ->
Res =
maps:fold(fun(Path, Value, Acc) ->
case emqx:update_config([Path], Value, #{rawconf_with_defaults => true}) of
{ok, #{raw_config := RawConf}} ->
Acc#{Path => RawConf};
{error, Reason} ->
?SLOG(error, #{msg => "update global zone failed", reason => Reason,
path => Path, value => Value}),
Acc
end
end, #{}, Body),
case maps:size(Res) =:= maps:size(Body) of
true -> {200, Res};
false -> {400, #{code => 'UPDATE_FAILED'}}
end.
config_reset(post, _Params, Req) -> config_reset(post, _Params, Req) ->
%% reset the config specified by the query string param 'conf_path' %% reset the config specified by the query string param 'conf_path'
Path = conf_path_reset(Req) ++ conf_path_from_querystr(Req), Path = conf_path_reset(Req) ++ conf_path_from_querystr(Req),
@ -192,9 +239,12 @@ conf_path_reset(Req) ->
string:lexemes(Path, "/ "). string:lexemes(Path, "/ ").
get_full_config() -> get_full_config() ->
emqx_config:fill_defaults( emqx_config:fill_defaults(
maps:without(?EXCLUDES, maps:without(?EXCLUDES,
emqx:get_raw_config([]))). emqx:get_raw_config([]))).
get_config_with_default(Path) ->
emqx_config:fill_defaults(emqx:get_raw_config(Path)).
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
@ -235,3 +285,10 @@ gen_schema(_Conf) ->
with_default_value(Type, Value) -> with_default_value(Type, Value) ->
Type#{example => emqx_map_lib:binary_string(Value)}. Type#{example => emqx_map_lib:binary_string(Value)}.
global_zone_roots() ->
lists:map(fun({K, _}) -> K end, global_zone_schema()).
global_zone_schema() ->
Roots = hocon_schema:roots(emqx_zone_schema),
lists:map(fun({RootKey, {_Root, Schema}}) -> {RootKey, Schema} end, Roots).

View File

@ -68,6 +68,35 @@ t_update(_Config) ->
?assertMatch(#{<<"vm">> := #{<<"busy_port">> := false}}, SysMon4), ?assertMatch(#{<<"vm">> := #{<<"busy_port">> := false}}, SysMon4),
ok. ok.
t_zones(_Config) ->
{ok, Zones} = get_zones(),
ZonesKeys = lists:map(fun({K, _}) -> K end, hocon_schema:roots(emqx_zone_schema)),
?assertEqual(lists:usort(ZonesKeys), lists:usort(maps:keys(Zones))),
?assertEqual(emqx_config:get_zone_conf(no_default, [mqtt, max_qos_allowed]),
emqx_map_lib:deep_get([<<"mqtt">>, <<"max_qos_allowed">>], Zones)),
NewZones = emqx_map_lib:deep_put([<<"mqtt">>, <<"max_qos_allowed">>], Zones, 1),
{ok, #{}} = update_zones(NewZones),
?assertEqual(1, emqx_config:get_zone_conf(no_default, [mqtt, max_qos_allowed])),
BadZones = emqx_map_lib:deep_put([<<"mqtt">>, <<"max_qos_allowed">>], Zones, 3),
?assertMatch({error, {"HTTP/1.1", 400, _}}, update_zones(BadZones)),
ok.
get_zones() ->
Path = emqx_mgmt_api_test_util:api_path(["configs", "global_zone"]),
case emqx_mgmt_api_test_util:request_api(get, Path) of
{ok, Res} -> {ok, emqx_json:decode(Res, [return_maps])};
Error -> Error
end.
update_zones(Change) ->
AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
UpdatePath = emqx_mgmt_api_test_util:api_path(["configs", "global_zone"]),
case emqx_mgmt_api_test_util:request_api(put, UpdatePath, "", AuthHeader, Change) of
{ok, Update} -> {ok, emqx_json:decode(Update, [return_maps])};
Error -> Error
end.
get_config(Name) -> get_config(Name) ->
Path = emqx_mgmt_api_test_util:api_path(["configs", Name]), Path = emqx_mgmt_api_test_util:api_path(["configs", Name]),
case emqx_mgmt_api_test_util:request_api(get, Path) of case emqx_mgmt_api_test_util:request_api(get, Path) of