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