Просмотр исходного кода

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

feat: Hot conf global zone
zhongwencool 4 лет назад
Родитель
Сommit
59b10fc80b

+ 8 - 10
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.<br>
 """])
      }.
+
+-spec qos() -> typerefl:type().
+qos() ->
+    typerefl:alias("qos", typerefl:union([0, 1, 2])).

+ 1 - 1
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",

+ 63 - 6
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).

+ 29 - 0
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