refactor(exhook): add mechanism to reload the failure server
This commit is contained in:
parent
4f72d2d0c5
commit
7e5be6ed6c
|
@ -1,16 +0,0 @@
|
||||||
##====================================================================
|
|
||||||
## EMQ X Hooks
|
|
||||||
##====================================================================
|
|
||||||
|
|
||||||
exhook: {
|
|
||||||
servers: [
|
|
||||||
# { name: "default"
|
|
||||||
# url: "http://127.0.0.1:9000"
|
|
||||||
# #ssl: {
|
|
||||||
# # cacertfile: "{{ platform_etc_dir }}/certs/cacert.pem"
|
|
||||||
# # certfile: "{{ platform_etc_dir }}/certs/cert.pem"
|
|
||||||
# # keyfile: "{{ platform_etc_dir }}/certs/key.pem"
|
|
||||||
# #}
|
|
||||||
# }
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -20,41 +20,22 @@
|
||||||
|
|
||||||
-include("emqx_exhook.hrl").
|
-include("emqx_exhook.hrl").
|
||||||
|
|
||||||
-define(CNTER, emqx_exhook_counter).
|
|
||||||
|
|
||||||
-export([ start/2
|
-export([ start/2
|
||||||
, stop/1
|
, stop/1
|
||||||
, prep_stop/1
|
, prep_stop/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Internal export
|
|
||||||
-export([ load_server/2
|
|
||||||
, unload_server/1
|
|
||||||
, unload_exhooks/0
|
|
||||||
, init_hooks_cnter/0
|
|
||||||
]).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Application callbacks
|
%% Application callbacks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
start(_StartType, _StartArgs) ->
|
start(_StartType, _StartArgs) ->
|
||||||
{ok, Sup} = emqx_exhook_sup:start_link(),
|
{ok, Sup} = emqx_exhook_sup:start_link(),
|
||||||
|
|
||||||
%% Init counter
|
|
||||||
init_hooks_cnter(),
|
|
||||||
|
|
||||||
%% Load all dirvers
|
|
||||||
load_all_servers(),
|
|
||||||
|
|
||||||
%% Register CLI
|
|
||||||
emqx_ctl:register_command(exhook, {emqx_exhook_cli, cli}, []),
|
emqx_ctl:register_command(exhook, {emqx_exhook_cli, cli}, []),
|
||||||
{ok, Sup}.
|
{ok, Sup}.
|
||||||
|
|
||||||
prep_stop(State) ->
|
prep_stop(State) ->
|
||||||
emqx_ctl:unregister_command(exhook),
|
emqx_ctl:unregister_command(exhook),
|
||||||
_ = unload_exhooks(),
|
|
||||||
ok = unload_all_servers(),
|
|
||||||
State.
|
State.
|
||||||
|
|
||||||
stop(_State) ->
|
stop(_State) ->
|
||||||
|
@ -63,35 +44,3 @@ stop(_State) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal funcs
|
%% Internal funcs
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
load_all_servers() ->
|
|
||||||
try
|
|
||||||
lists:foreach(fun(#{name := Name} = Options) ->
|
|
||||||
load_server(Name, maps:remove(name, Options))
|
|
||||||
end, emqx_config:get([exhook, servers]))
|
|
||||||
catch
|
|
||||||
_Class : _Reason ->
|
|
||||||
ok
|
|
||||||
end, ok.
|
|
||||||
|
|
||||||
unload_all_servers() ->
|
|
||||||
emqx_exhook:disable_all().
|
|
||||||
|
|
||||||
load_server(Name, Options) ->
|
|
||||||
emqx_exhook:enable(Name, Options).
|
|
||||||
|
|
||||||
unload_server(Name) ->
|
|
||||||
emqx_exhook:disable(Name).
|
|
||||||
|
|
||||||
unload_exhooks() ->
|
|
||||||
[emqx:unhook(Name, {M, F}) ||
|
|
||||||
{Name, {M, F, _A}} <- ?ENABLED_HOOKS].
|
|
||||||
|
|
||||||
init_hooks_cnter() ->
|
|
||||||
try
|
|
||||||
_ = ets:new(?CNTER, [named_table, public]), ok
|
|
||||||
catch
|
|
||||||
error:badarg:_ ->
|
|
||||||
ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @doc Manage the server status and reload strategy
|
||||||
|
-module(emqx_exhook_mngr).
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
-include("emqx_exhook.hrl").
|
||||||
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
|
%% APIs
|
||||||
|
-export([start_link/2]).
|
||||||
|
|
||||||
|
%% gen_server callbacks
|
||||||
|
-export([ init/1
|
||||||
|
, handle_call/3
|
||||||
|
, handle_cast/2
|
||||||
|
, handle_info/2
|
||||||
|
, terminate/2
|
||||||
|
, code_change/3
|
||||||
|
]).
|
||||||
|
|
||||||
|
-record(state, {
|
||||||
|
%% Running servers
|
||||||
|
running :: map(),
|
||||||
|
%% Wait to reload servers
|
||||||
|
waiting :: map(),
|
||||||
|
%% Marked stopped servers
|
||||||
|
stopped :: map(),
|
||||||
|
%% Auto reconnect timer interval
|
||||||
|
auto_reconnect :: false | non_neg_integer(),
|
||||||
|
%% Timer references
|
||||||
|
trefs :: map()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type servers() :: [{Name :: atom(), server_options()}].
|
||||||
|
|
||||||
|
-type server_options() :: [ {scheme, http | https}
|
||||||
|
| {host, string()}
|
||||||
|
| {port, inet:port_number()}
|
||||||
|
].
|
||||||
|
|
||||||
|
-define(CNTER, emqx_exhook_counter).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% APIs
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-spec start_link(servers(), false | non_neg_integer())
|
||||||
|
->ignore
|
||||||
|
| {ok, pid()}
|
||||||
|
| {error, any()}.
|
||||||
|
start_link(Servers, AutoReconnect) ->
|
||||||
|
gen_server:start_link(?MODULE, [Servers, AutoReconnect], []).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% gen_server callbacks
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
init([Servers, AutoReconnect]) ->
|
||||||
|
%% XXX: Due to the ExHook Module in the enterprise,
|
||||||
|
%% this process may start multiple times and they will share this table
|
||||||
|
try
|
||||||
|
_ = ets:new(?CNTER, [named_table, public]), ok
|
||||||
|
catch
|
||||||
|
error:badarg:_ ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
|
||||||
|
%% Load the hook servers
|
||||||
|
{Waiting, Running} = load_all_servers(Servers),
|
||||||
|
{ok, ensure_reload_timer(
|
||||||
|
#state{waiting = Waiting,
|
||||||
|
running = Running,
|
||||||
|
stopped = #{},
|
||||||
|
auto_reconnect = AutoReconnect,
|
||||||
|
trefs = #{}
|
||||||
|
}
|
||||||
|
)}.
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
load_all_servers(Servers) ->
|
||||||
|
load_all_servers(Servers, #{}, #{}).
|
||||||
|
load_all_servers([], Waiting, Running) ->
|
||||||
|
{Waiting, Running};
|
||||||
|
load_all_servers([{Name, Options}|More], Waiting, Running) ->
|
||||||
|
{NWaiting, NRunning} = case emqx_exhook:enable(Name, Options) of
|
||||||
|
ok ->
|
||||||
|
{Waiting, Running#{Name => Options}};
|
||||||
|
{error, _} ->
|
||||||
|
{Waiting#{Name => Options}, Running}
|
||||||
|
end,
|
||||||
|
load_all_servers(More, NWaiting, NRunning).
|
||||||
|
|
||||||
|
handle_call(_Request, _From, State) ->
|
||||||
|
Reply = ok,
|
||||||
|
{reply, Reply, State}.
|
||||||
|
|
||||||
|
handle_cast(_Msg, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
handle_info({timeout, _Ref, {reload, Name}},
|
||||||
|
State0 = #state{waiting = Waiting,
|
||||||
|
running = Running,
|
||||||
|
trefs = TRefs}) ->
|
||||||
|
State = State0#state{trefs = maps:remove(Name, TRefs)},
|
||||||
|
case maps:get(Name, Waiting, undefined) of
|
||||||
|
undefined ->
|
||||||
|
{noreply, State};
|
||||||
|
Options ->
|
||||||
|
case emqx_exhook:enable(Name, Options) of
|
||||||
|
ok ->
|
||||||
|
?LOG(warning, "Reconnect to exhook callback server "
|
||||||
|
"\"~s\" successfully!", [Name]),
|
||||||
|
{noreply, State#state{
|
||||||
|
running = maps:put(Name, Options, Running),
|
||||||
|
waiting = maps:remove(Name, Waiting)}
|
||||||
|
};
|
||||||
|
{error, _} ->
|
||||||
|
{noreply, ensure_reload_timer(State)}
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
|
||||||
|
handle_info(_Info, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
terminate(_Reason, _State) ->
|
||||||
|
_ = emqx_exhook:disable_all(),
|
||||||
|
_ = unload_exhooks(),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Internal funcs
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
unload_exhooks() ->
|
||||||
|
[emqx:unhook(Name, {M, F}) ||
|
||||||
|
{Name, {M, F, _A}} <- ?ENABLED_HOOKS].
|
||||||
|
|
||||||
|
ensure_reload_timer(State = #state{auto_reconnect = false}) ->
|
||||||
|
State;
|
||||||
|
ensure_reload_timer(State = #state{waiting = Waiting,
|
||||||
|
trefs = TRefs,
|
||||||
|
auto_reconnect = Intv}) ->
|
||||||
|
NRefs = maps:fold(fun(Name, _, AccIn) ->
|
||||||
|
case maps:get(Name, AccIn, undefined) of
|
||||||
|
undefined ->
|
||||||
|
Ref = erlang:start_timer(Intv, self(), {reload, Name}),
|
||||||
|
AccIn#{Name => Ref};
|
||||||
|
_HasRef ->
|
||||||
|
AccIn
|
||||||
|
end
|
||||||
|
end, TRefs, Waiting),
|
||||||
|
State#state{trefs = NRefs}.
|
|
@ -26,6 +26,13 @@
|
||||||
, stop_grpc_client_channel/1
|
, stop_grpc_client_channel/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-define(CHILD(Mod, Type, Args),
|
||||||
|
#{ id => Mod
|
||||||
|
, start => {Mod, start_link, Args}
|
||||||
|
, type => Type
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Supervisor APIs & Callbacks
|
%% Supervisor APIs & Callbacks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -34,7 +41,14 @@ start_link() ->
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
{ok, {{one_for_one, 10, 100}, []}}.
|
Mngr = ?CHILD(emqx_exhook_mngr, worker, [servers(), auto_reconnect()]),
|
||||||
|
{ok, {{one_for_one, 10, 100}, [Mngr]}}.
|
||||||
|
|
||||||
|
servers() ->
|
||||||
|
application:get_env(emqx_exhook, servers, []).
|
||||||
|
|
||||||
|
auto_reconnect() ->
|
||||||
|
application:get_env(emqx_exhook, auto_reconnect, 60000).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% APIs
|
%% APIs
|
||||||
|
|
Loading…
Reference in New Issue