From 3938aa127b1140faaa8ac994ef704793328ed1b3 Mon Sep 17 00:00:00 2001 From: Zhongwen Deng Date: Fri, 11 Mar 2022 13:57:47 +0800 Subject: [PATCH 1/2] feat: add /configs/global_zones API to aggregate emqx_zone_schema --- apps/emqx/src/emqx_zone_schema.erl | 2 +- .../src/emqx_mgmt_api_configs.erl | 69 +++++++++++++++++-- .../test/emqx_mgmt_api_configs_SUITE.erl | 29 ++++++++ 3 files changed, 93 insertions(+), 7 deletions(-) diff --git a/apps/emqx/src/emqx_zone_schema.erl b/apps/emqx/src/emqx_zone_schema.erl index ad5947051..44595573d 100644 --- a/apps/emqx/src/emqx_zone_schema.erl +++ b/apps/emqx/src/emqx_zone_schema.erl @@ -20,7 +20,7 @@ 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() -> ["mqtt", "stats", "flapping_detect", "force_shutdown", "conn_congestion", "force_gc", diff --git a/apps/emqx_management/src/emqx_mgmt_api_configs.erl b/apps/emqx_management/src/emqx_mgmt_api_configs.erl index 46746ed0d..6e745f863 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_configs.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_configs.erl @@ -17,12 +17,17 @@ -module(emqx_mgmt_api_configs). -include_lib("hocon/include/hoconsc.hrl"). +-include_lib("emqx/include/logger.hrl"). -behaviour(minirest_api). -export([api_spec/0, namespace/0]). -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]). @@ -51,7 +56,8 @@ <<"event_message">>, <<"prometheus">>, <<"telemetry">> -]). + ] ++ global_zone_roots() +). api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). @@ -59,7 +65,7 @@ api_spec() -> namespace() -> "configuration". 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()). 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) -> {RootKey, {_Root, Schema}} = find_schema(Path), #{ @@ -159,6 +184,28 @@ config(put, #{body := Body}, Req) -> {400, #{code => 'UPDATE_FAILED', message => ?ERR_MSG(Reason)}} 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) -> %% reset the config specified by the query string param 'conf_path' Path = conf_path_reset(Req) ++ conf_path_from_querystr(Req), @@ -192,9 +239,12 @@ conf_path_reset(Req) -> string:lexemes(Path, "/ "). get_full_config() -> - emqx_config:fill_defaults( - maps:without(?EXCLUDES, - emqx:get_raw_config([]))). + emqx_config:fill_defaults( + maps:without(?EXCLUDES, + emqx:get_raw_config([]))). + +get_config_with_default(Path) -> + emqx_config:fill_defaults(emqx:get_raw_config(Path)). conf_path_from_querystr(Req) -> case proplists:get_value(<<"conf_path">>, cowboy_req:parse_qs(Req)) of @@ -235,3 +285,10 @@ gen_schema(_Conf) -> with_default_value(Type, 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). diff --git a/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl index 3e748b4ef..741c0b86d 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_configs_SUITE.erl @@ -68,6 +68,35 @@ t_update(_Config) -> ?assertMatch(#{<<"vm">> := #{<<"busy_port">> := false}}, SysMon4), 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) -> Path = emqx_mgmt_api_test_util:api_path(["configs", Name]), case emqx_mgmt_api_test_util:request_api(get, Path) of From ab10163abf62f85f2a1f6feb94b2c07afe8562be Mon Sep 17 00:00:00 2001 From: Zhongwen Deng Date: Fri, 11 Mar 2022 14:48:28 +0800 Subject: [PATCH 2/2] chore: redefined qos --- apps/emqx/src/emqx_schema.erl | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index adb2620c3..592d6983a 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -38,9 +38,7 @@ -type bar_separated_list() :: list(). -type ip_port() :: tuple(). -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_s/0, emqx_schema, to_duration_s}). -typerefl_from_string({duration_ms/0, emqx_schema, to_duration_ms}). @@ -59,12 +57,14 @@ , validations/0 ]). +-export([ qos/0]). + % workaround: prevent being recognized as unused functions -export([to_duration/1, to_duration_s/1, to_duration_ms/1, mk_duration/2, to_bytesize/1, to_wordsize/1, to_percent/1, to_comma_separated_list/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 ]). @@ -73,7 +73,7 @@ -reflect_type([ duration/0, duration_s/0, duration_ms/0, bytesize/0, wordsize/0, percent/0, file/0, comma_separated_list/0, bar_separated_list/0, ip_port/0, - cipher/0, qos/0, + cipher/0, comma_separated_atoms/0 ]). @@ -1558,12 +1558,6 @@ to_comma_separated_atoms(Str) -> to_bar_separated_list(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) -> case string:tokens(Str, ": ") of [Ip, Port] -> @@ -1688,3 +1682,7 @@ When authenticating a login (username, client ID, etc.) the authenticators are c in the configured order.
"""]) }. + +-spec qos() -> typerefl:type(). +qos() -> + typerefl:alias("qos", typerefl:union([0, 1, 2])).