feat(listeners): add config handler for listeners

This commit is contained in:
Shawn 2021-08-30 20:53:36 +08:00
parent e6306bccd8
commit 0d1bc6d689
2 changed files with 66 additions and 13 deletions

View File

@ -41,6 +41,10 @@
, parse_listener_id/1 , parse_listener_id/1
]). ]).
-export([post_config_update/4]).
-define(CONF_KEY_PATH, [listeners]).
%% @doc List configured listeners. %% @doc List configured listeners.
-spec(list() -> [{ListenerId :: atom(), ListenerConf :: map()}]). -spec(list() -> [{ListenerId :: atom(), ListenerConf :: map()}]).
list() -> list() ->
@ -88,6 +92,9 @@ is_running(quic, _ListenerId, _Conf)->
%% @doc Start all listeners. %% @doc Start all listeners.
-spec(start() -> ok). -spec(start() -> ok).
start() -> start() ->
%% The ?MODULE:start/0 will be called by emqx_app when emqx get started,
%% so we install the config handler here.
ok = emqx_config_handler:add_handler(?CONF_KEY_PATH, ?MODULE),
foreach_listeners(fun start_listener/3). foreach_listeners(fun start_listener/3).
-spec start_listener(atom()) -> ok | {error, term()}. -spec start_listener(atom()) -> ok | {error, term()}.
@ -102,7 +109,7 @@ start_listener(Type, ListenerName, #{bind := Bind} = Conf) ->
console_print("- Skip - starting listener ~s on ~s ~n due to ~p", console_print("- Skip - starting listener ~s on ~s ~n due to ~p",
[listener_id(Type, ListenerName), format_addr(Bind), Reason]); [listener_id(Type, ListenerName), format_addr(Bind), Reason]);
{ok, _} -> {ok, _} ->
console_print("Start listener ~s on ~s successfully.~n", console_print("Listener ~s on ~s started.~n",
[listener_id(Type, ListenerName), format_addr(Bind)]); [listener_id(Type, ListenerName), format_addr(Bind)]);
{error, {already_started, Pid}} -> {error, {already_started, Pid}} ->
{error, {already_started, Pid}}; {error, {already_started, Pid}};
@ -122,27 +129,47 @@ restart_listener(ListenerId) ->
apply_on_listener(ListenerId, fun restart_listener/3). apply_on_listener(ListenerId, fun restart_listener/3).
-spec(restart_listener(atom(), atom(), map()) -> ok | {error, term()}). -spec(restart_listener(atom(), atom(), map()) -> ok | {error, term()}).
restart_listener(Type, ListenerName, {OldConf, NewConf}) ->
restart_listener(Type, ListenerName, OldConf, NewConf);
restart_listener(Type, ListenerName, Conf) -> restart_listener(Type, ListenerName, Conf) ->
case stop_listener(Type, ListenerName, Conf) of restart_listener(Type, ListenerName, Conf, Conf).
ok -> start_listener(Type, ListenerName, Conf);
restart_listener(Type, ListenerName, OldConf, NewConf) ->
case stop_listener(Type, ListenerName, OldConf) of
ok -> start_listener(Type, ListenerName, NewConf);
Error -> Error Error -> Error
end. end.
%% @doc Stop all listeners. %% @doc Stop all listeners.
-spec(stop() -> ok). -spec(stop() -> ok).
stop() -> stop() ->
%% The ?MODULE:stop/0 will be called by emqx_app when emqx is going to shutdown,
%% so we uninstall the config handler here.
_ = emqx_config_handler:remove_handler(?CONF_KEY_PATH),
foreach_listeners(fun stop_listener/3). foreach_listeners(fun stop_listener/3).
-spec(stop_listener(atom()) -> ok | {error, term()}). -spec(stop_listener(atom()) -> ok | {error, term()}).
stop_listener(ListenerId) -> stop_listener(ListenerId) ->
apply_on_listener(ListenerId, fun stop_listener/3). apply_on_listener(ListenerId, fun stop_listener/3).
-spec(stop_listener(atom(), atom(), map()) -> ok | {error, term()}). stop_listener(Type, ListenerName, #{bind := Bind} = Conf) ->
stop_listener(Type, ListenerName, #{bind := ListenOn}) when Type == tcp; Type == ssl -> case do_stop_listener(Type, ListenerName, Conf) of
ok ->
console_print("Listener ~s on ~s stopped.~n",
[listener_id(Type, ListenerName), format_addr(Bind)]),
ok;
{error, Reason} ->
?ELOG("Failed to stop listener ~s on ~s: ~0p~n",
[listener_id(Type, ListenerName), format_addr(Bind), Reason]),
{error, Reason}
end.
-spec(do_stop_listener(atom(), atom(), map()) -> ok | {error, term()}).
do_stop_listener(Type, ListenerName, #{bind := ListenOn}) when Type == tcp; Type == ssl ->
esockd:close(listener_id(Type, ListenerName), ListenOn); esockd:close(listener_id(Type, ListenerName), ListenOn);
stop_listener(Type, ListenerName, _Conf) when Type == ws; Type == wss -> do_stop_listener(Type, ListenerName, _Conf) when Type == ws; Type == wss ->
cowboy:stop_listener(listener_id(Type, ListenerName)); cowboy:stop_listener(listener_id(Type, ListenerName));
stop_listener(quic, ListenerName, _Conf) -> do_stop_listener(quic, ListenerName, _Conf) ->
quicer:stop_listener(listener_id(quic, ListenerName)). quicer:stop_listener(listener_id(quic, ListenerName)).
-ifndef(TEST). -ifndef(TEST).
@ -201,6 +228,32 @@ do_start_listener(quic, ListenerName, #{bind := ListenOn} = Opts) ->
{ok, {skipped, quic_app_missing}} {ok, {skipped, quic_app_missing}}
end. end.
%% Update the listeners at runtime
post_config_update(_Req, NewListeners, OldListeners, _AppEnvs) ->
#{added := Added, removed := Removed, changed := Updated}
= diff_listeners(NewListeners, OldListeners),
perform_listener_changes(fun stop_listener/3, Removed),
perform_listener_changes(fun start_listener/3, Added),
perform_listener_changes(fun restart_listener/3, Updated).
perform_listener_changes(Action, MapConfs) ->
lists:foreach(fun
({Id, Conf}) ->
{Type, Name} = parse_listener_id(Id),
Action(Type, Name, Conf)
end, maps:to_list(MapConfs)).
diff_listeners(NewListeners, OldListeners) ->
emqx_map_lib:diff_maps(flatten_listeners(NewListeners), flatten_listeners(OldListeners)).
flatten_listeners(Conf0) ->
maps:from_list(
lists:append([do_flatten_listeners(Type, Conf)
|| {Type, Conf} <- maps:to_list(Conf0)])).
do_flatten_listeners(Type, Conf0) ->
[{listener_id(Type, Name), Conf} || {Name, Conf} <- maps:to_list(Conf0)].
esockd_opts(Type, Opts0) -> esockd_opts(Type, Opts0) ->
Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0), Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0),
Opts2 = case emqx_config:get_zone_conf(zone(Opts0), [rate_limit, max_conn_rate]) of Opts2 = case emqx_config:get_zone_conf(zone(Opts0), [rate_limit, max_conn_rate]) of

View File

@ -131,20 +131,20 @@ jsonable_map(Map, JsonableFun) ->
deep_convert(Map, fun binary_string_kv/3, [JsonableFun]). deep_convert(Map, fun binary_string_kv/3, [JsonableFun]).
-spec diff_maps(map(), map()) -> -spec diff_maps(map(), map()) ->
#{added := [map()], identical := [map()], removed := [map()], #{added := map(), identical := map(), removed := map(),
changed := [#{any() => {OldValue::any(), NewValue::any()}}]}. changed := #{any() => {OldValue::any(), NewValue::any()}}}.
diff_maps(NewMap, OldMap) -> diff_maps(NewMap, OldMap) ->
InitR = #{identical => [], changed => [], removed => []}, InitR = #{identical => #{}, changed => #{}, removed => #{}},
{Result, RemInNew} = {Result, RemInNew} =
lists:foldl(fun({OldK, OldV}, {Result0 = #{identical := I, changed := U, removed := D}, lists:foldl(fun({OldK, OldV}, {Result0 = #{identical := I, changed := U, removed := D},
RemNewMap}) -> RemNewMap}) ->
Result1 = case maps:find(OldK, NewMap) of Result1 = case maps:find(OldK, NewMap) of
error -> error ->
Result0#{removed => [#{OldK => OldV} | D]}; Result0#{removed => D#{OldK => OldV}};
{ok, NewV} when NewV == OldV -> {ok, NewV} when NewV == OldV ->
Result0#{identical => [#{OldK => OldV} | I]}; Result0#{identical => I#{OldK => OldV}};
{ok, NewV} -> {ok, NewV} ->
Result0#{changed => [#{OldK => {OldV, NewV}} | U]} Result0#{changed => U#{OldK => {OldV, NewV}}}
end, end,
{Result1, maps:remove(OldK, RemNewMap)} {Result1, maps:remove(OldK, RemNewMap)}
end, {InitR, NewMap}, maps:to_list(OldMap)), end, {InitR, NewMap}, maps:to_list(OldMap)),