From bd29433997df051f4eeb593dfa955dac745e54a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=90=E6=96=87?= Date: Sun, 28 May 2023 23:16:37 +0800 Subject: [PATCH] feat: support emqx_conf:update([exhook],Conf) --- apps/emqx_exhook/src/emqx_exhook.app.src | 2 +- apps/emqx_exhook/src/emqx_exhook_app.erl | 7 +- apps/emqx_exhook/src/emqx_exhook_mgr.erl | 109 +++++++++++++--- apps/emqx_utils/src/emqx_utils.app.src | 2 +- apps/emqx_utils/src/emqx_utils.erl | 152 ++++++++++++++++++++++- 5 files changed, 243 insertions(+), 29 deletions(-) diff --git a/apps/emqx_exhook/src/emqx_exhook.app.src b/apps/emqx_exhook/src/emqx_exhook.app.src index 194c91206..92a70cf37 100644 --- a/apps/emqx_exhook/src/emqx_exhook.app.src +++ b/apps/emqx_exhook/src/emqx_exhook.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_exhook, [ {description, "EMQX Extension for Hook"}, - {vsn, "5.0.12"}, + {vsn, "5.0.13"}, {modules, []}, {registered, []}, {mod, {emqx_exhook_app, []}}, diff --git a/apps/emqx_exhook/src/emqx_exhook_app.erl b/apps/emqx_exhook/src/emqx_exhook_app.erl index 6e9bc5242..2fc562ae8 100644 --- a/apps/emqx_exhook/src/emqx_exhook_app.erl +++ b/apps/emqx_exhook/src/emqx_exhook_app.erl @@ -22,8 +22,7 @@ -export([ start/2, - stop/1, - prep_stop/1 + stop/1 ]). %%-------------------------------------------------------------------- @@ -34,10 +33,6 @@ start(_StartType, _StartArgs) -> {ok, Sup} = emqx_exhook_sup:start_link(), {ok, Sup}. -prep_stop(State) -> - emqx_ctl:unregister_command(exhook), - State. - stop(_State) -> ok. diff --git a/apps/emqx_exhook/src/emqx_exhook_mgr.erl b/apps/emqx_exhook/src/emqx_exhook_mgr.erl index 0647c80ea..573dded9e 100644 --- a/apps/emqx_exhook/src/emqx_exhook_mgr.erl +++ b/apps/emqx_exhook/src/emqx_exhook_mgr.erl @@ -181,10 +181,14 @@ pre_config_update(_, {enable, Name, Enable}, OldConf) -> of not_found -> throw(not_found); NewConf -> {ok, lists:map(fun maybe_write_certs/1, NewConf)} - end. + end; +pre_config_update(_, NewConf, _OldConf) when NewConf =:= #{} -> + {ok, NewConf#{<<"servers">> => []}}; +pre_config_update(_, NewConf = #{<<"servers">> := Servers}, _OldConf) -> + {ok, NewConf#{<<"servers">> => lists:map(fun maybe_write_certs/1, Servers)}}. post_config_update(_KeyPath, UpdateReq, NewConf, OldConf, _AppEnvs) -> - Result = call({update_config, UpdateReq, NewConf}), + Result = call({update_config, UpdateReq, NewConf, OldConf}), try_clear_ssl_files(UpdateReq, NewConf, OldConf), {ok, Result}. @@ -197,6 +201,7 @@ post_config_update(_KeyPath, UpdateReq, NewConf, OldConf, _AppEnvs) -> init([]) -> process_flag(trap_exit, true), emqx_conf:add_handler([exhook, servers], ?MODULE), + emqx_conf:add_handler([exhook], ?MODULE), ServerL = emqx:get_config([exhook, servers]), Servers = load_all_servers(ServerL), Servers2 = reorder(ServerL, Servers), @@ -222,22 +227,16 @@ handle_call( OrderServers = sort_name_by_order(Infos, Servers), {reply, OrderServers, State}; handle_call( - {update_config, {move, _Name, _Position}, NewConfL}, + {update_config, {move, _Name, _Position}, NewConfL, _}, _From, #{servers := Servers} = State ) -> Servers2 = reorder(NewConfL, Servers), {reply, ok, State#{servers := Servers2}}; -handle_call({update_config, {delete, ToDelete}, _}, _From, State) -> - emqx_exhook_metrics:on_server_deleted(ToDelete), - - #{servers := Servers} = State2 = do_unload_server(ToDelete, State), - - Servers2 = maps:remove(ToDelete, Servers), - - {reply, ok, update_servers(Servers2, State2)}; +handle_call({update_config, {delete, ToDelete}, _, _}, _From, State) -> + {reply, ok, remove_server(ToDelete, State)}; handle_call( - {update_config, {add, RawConf}, NewConfL}, + {update_config, {add, RawConf}, NewConfL, _}, _From, #{servers := Servers} = State ) -> @@ -246,14 +245,68 @@ handle_call( Servers2 = Servers#{Name => Server}, Servers3 = reorder(NewConfL, Servers2), {reply, Result, State#{servers := Servers3}}; +handle_call({update_config, {update, Name, _Conf}, NewConfL, _}, _From, State) -> + {Result, State2} = restart_server(Name, NewConfL, State), + {reply, Result, State2}; +handle_call({update_config, {enable, Name, _Enable}, NewConfL, _}, _From, State) -> + {Result, State2} = restart_server(Name, NewConfL, State), + {reply, Result, State2}; +handle_call({update_config, _, ConfL, ConfL}, _From, State) -> + {reply, ok, State}; +handle_call({update_config, _, #{servers := NewConfL}, #{servers := OldConfL}}, _From, State) -> + #{ + removed := Removed, + added := Added, + changed := Updated + } = emqx_utils:diff_lists(NewConfL, OldConfL, fun(#{name := Name}) -> Name end), + %% remove servers + State2 = lists:foldl( + fun(Conf, Acc) -> + ToDelete = maps:get(name, Conf), + remove_server(ToDelete, Acc) + end, + State, + Removed + ), + %% update servers + {UpdateRes, State3} = + lists:foldl( + fun({_Old, Conf}, {ResAcc, StateAcc}) -> + Name = maps:get(name, Conf), + case restart_server(Name, NewConfL, StateAcc) of + {ok, StateAcc1} -> {ResAcc, StateAcc1}; + {Err, StateAcc1} -> {[Err | ResAcc], StateAcc1} + end + end, + {[], State2}, + Updated + ), + %% Add servers + {AddRes, State4} = + lists:foldl( + fun(Conf, {ResAcc, StateAcc}) -> + case do_load_server(options_to_server(Conf)) of + {ok, Server} -> + #{servers := Servers} = StateAcc, + Name = maps:get(name, Conf), + Servers2 = Servers#{Name => Server}, + {ResAcc, update_servers(Servers2, StateAcc)}; + {Err, StateAcc1} -> + {[Err | ResAcc], StateAcc1} + end + end, + {[], State3}, + Added + ), + %% update order + #{servers := Servers4} = State4, + State5 = State4#{servers => reorder(NewConfL, Servers4)}, + case lists:append([UpdateRes, AddRes]) of + [] -> {reply, ok, State5}; + _ -> {reply, {error, #{added => AddRes, updated => UpdateRes}}, State5} + end; handle_call({lookup, Name}, _From, State) -> {reply, where_is_server(Name, State), State}; -handle_call({update_config, {update, Name, _Conf}, NewConfL}, _From, State) -> - {Result, State2} = restart_server(Name, NewConfL, State), - {reply, Result, State2}; -handle_call({update_config, {enable, Name, _Enable}, NewConfL}, _From, State) -> - {Result, State2} = restart_server(Name, NewConfL, State), - {reply, Result, State2}; handle_call({server_info, Name}, _From, State) -> case where_is_server(Name, State) of not_found -> @@ -287,6 +340,12 @@ handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. +remove_server(ToDelete, State) -> + emqx_exhook_metrics:on_server_deleted(ToDelete), + #{servers := Servers} = State2 = do_unload_server(ToDelete, State), + Servers2 = maps:remove(ToDelete, Servers), + update_servers(Servers2, State2). + handle_cast(_Msg, State) -> {noreply, State}. @@ -310,6 +369,8 @@ terminate(Reason, State = #{servers := Servers}) -> Servers ), ?tp(info, exhook_mgr_terminated, #{reason => Reason, servers => Servers}), + emqx_conf:remove_handler([exhook, servers]), + emqx_conf:remove_handler([exhook]), ok. code_change(_OldVsn, State, _Extra) -> @@ -612,8 +673,16 @@ try_clear_ssl_files({Op, Name, _}, NewConfs, OldConfs) when NewSSL = find_server_ssl_cfg(Name, NewConfs), OldSSL = find_server_ssl_cfg(Name, OldConfs), emqx_tls_lib:delete_ssl_files(ssl_file_path(Name), NewSSL, OldSSL); -try_clear_ssl_files(_Req, _NewConf, _OldConf) -> - ok. +%% replace the whole config from the file +try_clear_ssl_files(_Req, #{servers := NewServers}, #{servers := OldServers}) -> + lists:foreach( + fun(#{name := Name} = Conf) -> + NewSSL = find_server_ssl_cfg(Name, NewServers), + OldSSL = maps:get(ssl, Conf, undefined), + emqx_tls_lib:delete_ssl_files(ssl_file_path(Name), NewSSL, OldSSL) + end, + OldServers + ). search_server_cfg(Name, Confs) -> lists:search( diff --git a/apps/emqx_utils/src/emqx_utils.app.src b/apps/emqx_utils/src/emqx_utils.app.src index 605093875..0b172565a 100644 --- a/apps/emqx_utils/src/emqx_utils.app.src +++ b/apps/emqx_utils/src/emqx_utils.app.src @@ -2,7 +2,7 @@ {application, emqx_utils, [ {description, "Miscellaneous utilities for EMQX apps"}, % strict semver, bump manually! - {vsn, "5.0.2"}, + {vsn, "5.0.3"}, {modules, [ emqx_utils, emqx_utils_api, diff --git a/apps/emqx_utils/src/emqx_utils.erl b/apps/emqx_utils/src/emqx_utils.erl index 2c6ddd9c1..edc76a1e6 100644 --- a/apps/emqx_utils/src/emqx_utils.erl +++ b/apps/emqx_utils/src/emqx_utils.erl @@ -54,7 +54,8 @@ safe_to_existing_atom/1, safe_to_existing_atom/2, pub_props_to_packet/1, - safe_filename/1 + safe_filename/1, + diff_lists/3 ]). -export([ @@ -748,3 +749,152 @@ safe_filename(Filename) when is_binary(Filename) -> binary:replace(Filename, <<":">>, <<"-">>, [global]); safe_filename(Filename) when is_list(Filename) -> lists:flatten(string:replace(Filename, ":", "-", all)). + +%% @doc Compares two lists of maps and returns the differences between them in a +%% map containing four keys – 'removed', 'added', 'identical', and 'changed' – +%% each holding a list of maps. Elements are compared using key function KeyFunc +%% to extract the comparison key used for matching. +%% +%% The return value is a map with the following keys and the list of maps as its values: +%% * 'removed' – a list of maps that were present in the Old list, but not found in the New list. +%% * 'added' – a list of maps that were present in the New list, but not found in the Old list. +%% * 'identical' – a list of maps that were present in both lists and have the same comparison key value. +%% * 'changed' – a list of pairs of maps representing the changes between maps present in the New and Old lists. +%% The first map in the pair represents the map in the Old list, and the second map +%% represents the potential modification in the New list. + +%% The KeyFunc parameter is a function that extracts the comparison key used +%% for matching from each map. The function should return a comparable term, +%% such as an atom, a number, or a string. This is used to determine if each +%% element is the same in both lists. + +-spec diff_lists(list(T), list(T), Func) -> + #{ + added := list(T), + identical := list(T), + removed := list(T), + changed := list({Old :: T, New :: T}) + } +when + Func :: fun((T) -> any()), + T :: any(). + +diff_lists(New, Old, KeyFunc) when is_list(New) andalso is_list(Old) -> + Removed = + lists:foldl( + fun(E, RemovedAcc) -> + case search(KeyFunc(E), KeyFunc, New) of + false -> [E | RemovedAcc]; + _ -> RemovedAcc + end + end, + [], + Old + ), + {Added, Identical, Changed} = + lists:foldl( + fun(E, Acc) -> + {Added0, Identical0, Changed0} = Acc, + case search(KeyFunc(E), KeyFunc, Old) of + false -> + {[E | Added0], Identical0, Changed0}; + E -> + {Added0, [E | Identical0], Changed0}; + E1 -> + {Added0, Identical0, [{E1, E} | Changed0]} + end + end, + {[], [], []}, + New + ), + #{ + removed => lists:reverse(Removed), + added => lists:reverse(Added), + identical => lists:reverse(Identical), + changed => lists:reverse(Changed) + }. + +search(_ExpectValue, _KeyFunc, []) -> + false; +search(ExpectValue, KeyFunc, [Item | List]) -> + case KeyFunc(Item) =:= ExpectValue of + true -> Item; + false -> search(ExpectValue, KeyFunc, List) + end. + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +diff_lists_test() -> + KeyFunc = fun(#{name := Name}) -> Name end, + ?assertEqual( + #{ + removed => [], + added => [], + identical => [], + changed => [] + }, + diff_lists([], [], KeyFunc) + ), + %% test removed list + ?assertEqual( + #{ + removed => [#{name => a, value => 1}], + added => [], + identical => [], + changed => [] + }, + diff_lists([], [#{name => a, value => 1}], KeyFunc) + ), + %% test added list + ?assertEqual( + #{ + removed => [], + added => [#{name => a, value => 1}], + identical => [], + changed => [] + }, + diff_lists([#{name => a, value => 1}], [], KeyFunc) + ), + %% test identical list + ?assertEqual( + #{ + removed => [], + added => [], + identical => [#{name => a, value => 1}], + changed => [] + }, + diff_lists([#{name => a, value => 1}], [#{name => a, value => 1}], KeyFunc) + ), + Old = [ + #{name => a, value => 1}, + #{name => b, value => 4}, + #{name => e, value => 2}, + #{name => d, value => 4} + ], + New = [ + #{name => a, value => 1}, + #{name => b, value => 2}, + #{name => e, value => 2}, + #{name => c, value => 3} + ], + Diff = diff_lists(New, Old, KeyFunc), + ?assertEqual( + #{ + added => [ + #{name => c, value => 3} + ], + identical => [ + #{name => a, value => 1}, + #{name => e, value => 2} + ], + removed => [ + #{name => d, value => 4} + ], + changed => [{#{name => b, value => 4}, #{name => b, value => 2}}] + }, + Diff + ), + ok. + +-endif.