chore(exhook): reformat exhook codes
This commit is contained in:
parent
cfb6c4b0be
commit
1a4afabe9f
|
@ -21,27 +21,27 @@
|
||||||
-define(HOOKS_REF_COUNTER, emqx_exhook_ref_counter).
|
-define(HOOKS_REF_COUNTER, emqx_exhook_ref_counter).
|
||||||
-define(HOOKS_METRICS, emqx_exhook_metrics).
|
-define(HOOKS_METRICS, emqx_exhook_metrics).
|
||||||
|
|
||||||
-define(ENABLED_HOOKS,
|
-define(ENABLED_HOOKS, [
|
||||||
[ {'client.connect', {emqx_exhook_handler, on_client_connect, []}}
|
{'client.connect', {emqx_exhook_handler, on_client_connect, []}},
|
||||||
, {'client.connack', {emqx_exhook_handler, on_client_connack, []}}
|
{'client.connack', {emqx_exhook_handler, on_client_connack, []}},
|
||||||
, {'client.connected', {emqx_exhook_handler, on_client_connected, []}}
|
{'client.connected', {emqx_exhook_handler, on_client_connected, []}},
|
||||||
, {'client.disconnected', {emqx_exhook_handler, on_client_disconnected, []}}
|
{'client.disconnected', {emqx_exhook_handler, on_client_disconnected, []}},
|
||||||
, {'client.authenticate', {emqx_exhook_handler, on_client_authenticate, []}}
|
{'client.authenticate', {emqx_exhook_handler, on_client_authenticate, []}},
|
||||||
, {'client.authorize', {emqx_exhook_handler, on_client_authorize, []}}
|
{'client.authorize', {emqx_exhook_handler, on_client_authorize, []}},
|
||||||
, {'client.subscribe', {emqx_exhook_handler, on_client_subscribe, []}}
|
{'client.subscribe', {emqx_exhook_handler, on_client_subscribe, []}},
|
||||||
, {'client.unsubscribe', {emqx_exhook_handler, on_client_unsubscribe, []}}
|
{'client.unsubscribe', {emqx_exhook_handler, on_client_unsubscribe, []}},
|
||||||
, {'session.created', {emqx_exhook_handler, on_session_created, []}}
|
{'session.created', {emqx_exhook_handler, on_session_created, []}},
|
||||||
, {'session.subscribed', {emqx_exhook_handler, on_session_subscribed, []}}
|
{'session.subscribed', {emqx_exhook_handler, on_session_subscribed, []}},
|
||||||
, {'session.unsubscribed',{emqx_exhook_handler, on_session_unsubscribed, []}}
|
{'session.unsubscribed', {emqx_exhook_handler, on_session_unsubscribed, []}},
|
||||||
, {'session.resumed', {emqx_exhook_handler, on_session_resumed, []}}
|
{'session.resumed', {emqx_exhook_handler, on_session_resumed, []}},
|
||||||
, {'session.discarded', {emqx_exhook_handler, on_session_discarded, []}}
|
{'session.discarded', {emqx_exhook_handler, on_session_discarded, []}},
|
||||||
, {'session.takenover', {emqx_exhook_handler, on_session_takenover, []}}
|
{'session.takenover', {emqx_exhook_handler, on_session_takenover, []}},
|
||||||
, {'session.terminated', {emqx_exhook_handler, on_session_terminated, []}}
|
{'session.terminated', {emqx_exhook_handler, on_session_terminated, []}},
|
||||||
, {'message.publish', {emqx_exhook_handler, on_message_publish, []}}
|
{'message.publish', {emqx_exhook_handler, on_message_publish, []}},
|
||||||
, {'message.delivered', {emqx_exhook_handler, on_message_delivered, []}}
|
{'message.delivered', {emqx_exhook_handler, on_message_delivered, []}},
|
||||||
, {'message.acked', {emqx_exhook_handler, on_message_acked, []}}
|
{'message.acked', {emqx_exhook_handler, on_message_acked, []}},
|
||||||
, {'message.dropped', {emqx_exhook_handler, on_message_dropped, []}}
|
{'message.dropped', {emqx_exhook_handler, on_message_dropped, []}}
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
|
|
@ -1,42 +1,57 @@
|
||||||
%%-*- mode: erlang -*-
|
%%-*- mode: erlang -*-
|
||||||
{plugins,
|
{plugins, [
|
||||||
[rebar3_proper,
|
rebar3_proper,
|
||||||
{grpc_plugin, {git, "https://github.com/HJianBo/grpc_plugin", {tag, "v0.10.2"}}}
|
{grpc_plugin, {git, "https://github.com/HJianBo/grpc_plugin", {tag, "v0.10.2"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{deps,
|
{deps, [
|
||||||
[ {emqx, {path, "../emqx"}}
|
{emqx, {path, "../emqx"}},
|
||||||
, {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.4"}}}
|
{grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.4"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{grpc,
|
{grpc, [
|
||||||
[{protos, ["priv/protos"]},
|
{protos, ["priv/protos"]},
|
||||||
{gpb_opts, [{module_name_prefix, "emqx_"},
|
{gpb_opts, [
|
||||||
{module_name_suffix, "_pb"}]}
|
{module_name_prefix, "emqx_"},
|
||||||
|
{module_name_suffix, "_pb"}
|
||||||
|
]}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{provider_hooks,
|
{provider_hooks, [
|
||||||
[{pre, [{compile, {grpc, gen}},
|
{pre, [
|
||||||
{clean, {grpc, clean}}]}
|
{compile, {grpc, gen}},
|
||||||
|
{clean, {grpc, clean}}
|
||||||
|
]}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{edoc_opts, [{preprocess, true}]}.
|
{edoc_opts, [{preprocess, true}]}.
|
||||||
|
|
||||||
{erl_opts, [warn_unused_vars,
|
{erl_opts, [
|
||||||
warn_shadow_vars,
|
warn_unused_vars,
|
||||||
warn_unused_import,
|
warn_shadow_vars,
|
||||||
warn_obsolete_guard,
|
warn_unused_import,
|
||||||
debug_info,
|
warn_obsolete_guard,
|
||||||
{parse_transform}]}.
|
debug_info,
|
||||||
|
{parse_transform}
|
||||||
|
]}.
|
||||||
|
|
||||||
{xref_checks, [undefined_function_calls, undefined_functions,
|
{xref_checks, [
|
||||||
locals_not_used, deprecated_function_calls,
|
undefined_function_calls,
|
||||||
warnings_as_errors, deprecated_functions]}.
|
undefined_functions,
|
||||||
|
locals_not_used,
|
||||||
|
deprecated_function_calls,
|
||||||
|
warnings_as_errors,
|
||||||
|
deprecated_functions
|
||||||
|
]}.
|
||||||
{xref_ignores, [emqx_exhook_pb]}.
|
{xref_ignores, [emqx_exhook_pb]}.
|
||||||
|
|
||||||
{cover_enabled, true}.
|
{cover_enabled, true}.
|
||||||
{cover_opts, [verbose]}.
|
{cover_opts, [verbose]}.
|
||||||
{cover_export_enabled, true}.
|
{cover_export_enabled, true}.
|
||||||
{cover_excl_mods, [emqx_exhook_pb,
|
{cover_excl_mods, [
|
||||||
emqx_exhook_v_1_hook_provider_bhvr,
|
emqx_exhook_pb,
|
||||||
emqx_exhook_v_1_hook_provider_client]}.
|
emqx_exhook_v_1_hook_provider_bhvr,
|
||||||
|
emqx_exhook_v_1_hook_provider_client
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{project_plugins, [erlfmt]}.
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{application, emqx_exhook,
|
{application, emqx_exhook, [
|
||||||
[{description, "EMQX Extension for Hook"},
|
{description, "EMQX Extension for Hook"},
|
||||||
{vsn, "5.0.0"},
|
{vsn, "5.0.0"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{mod, {emqx_exhook_app, []}},
|
{mod, {emqx_exhook_app, []}},
|
||||||
{applications, [kernel,stdlib,grpc,emqx]},
|
{applications, [kernel, stdlib, grpc, emqx]},
|
||||||
{env,[]},
|
{env, []},
|
||||||
{licenses, ["Apache-2.0"]},
|
{licenses, ["Apache-2.0"]},
|
||||||
{maintainers, ["EMQX Team <contact@emqx.io>"]},
|
{maintainers, ["EMQX Team <contact@emqx.io>"]},
|
||||||
{links, [{"Homepage", "https://emqx.io/"}]}
|
{links, [{"Homepage", "https://emqx.io/"}]}
|
||||||
]}.
|
]}.
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{VSN,
|
{VSN,
|
||||||
[
|
[
|
||||||
{<<".*">>, []}
|
{<<".*">>, []}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{<<".*">>, []}
|
{<<".*">>, []}
|
||||||
]
|
]}.
|
||||||
}.
|
|
||||||
|
|
|
@ -19,9 +19,10 @@
|
||||||
-include("emqx_exhook.hrl").
|
-include("emqx_exhook.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
-export([ cast/2
|
-export([
|
||||||
, call_fold/3
|
cast/2,
|
||||||
]).
|
call_fold/3
|
||||||
|
]).
|
||||||
|
|
||||||
%% exported for `emqx_telemetry'
|
%% exported for `emqx_telemetry'
|
||||||
-export([get_basic_usage_info/0]).
|
-export([get_basic_usage_info/0]).
|
||||||
|
@ -38,12 +39,16 @@ cast(_, _, []) ->
|
||||||
ok;
|
ok;
|
||||||
cast(Hookpoint, Req, [ServerName | More]) ->
|
cast(Hookpoint, Req, [ServerName | More]) ->
|
||||||
%% XXX: Need a real asynchronous running
|
%% XXX: Need a real asynchronous running
|
||||||
_ = emqx_exhook_server:call(Hookpoint, Req,
|
_ = emqx_exhook_server:call(
|
||||||
emqx_exhook_mgr:server(ServerName)),
|
Hookpoint,
|
||||||
|
Req,
|
||||||
|
emqx_exhook_mgr:server(ServerName)
|
||||||
|
),
|
||||||
cast(Hookpoint, Req, More).
|
cast(Hookpoint, Req, More).
|
||||||
|
|
||||||
-spec call_fold(atom(), term(), function()) -> {ok, term()}
|
-spec call_fold(atom(), term(), function()) ->
|
||||||
| {stop, term()}.
|
{ok, term()}
|
||||||
|
| {stop, term()}.
|
||||||
call_fold(Hookpoint, Req, AccFun) ->
|
call_fold(Hookpoint, Req, AccFun) ->
|
||||||
case emqx_exhook_mgr:running() of
|
case emqx_exhook_mgr:running() of
|
||||||
[] ->
|
[] ->
|
||||||
|
@ -90,33 +95,43 @@ deny_action_result('message.publish', Msg) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec get_basic_usage_info() ->
|
-spec get_basic_usage_info() ->
|
||||||
#{ num_servers => non_neg_integer()
|
#{
|
||||||
, servers =>
|
num_servers => non_neg_integer(),
|
||||||
[#{ driver => Driver
|
servers =>
|
||||||
, hooks => [emqx_exhook_server:hookpoint()]
|
[
|
||||||
}]
|
#{
|
||||||
} when Driver :: grpc.
|
driver => Driver,
|
||||||
|
hooks => [emqx_exhook_server:hookpoint()]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
when
|
||||||
|
Driver :: grpc.
|
||||||
get_basic_usage_info() ->
|
get_basic_usage_info() ->
|
||||||
try
|
try
|
||||||
Servers = emqx_exhook_mgr:running(),
|
Servers = emqx_exhook_mgr:running(),
|
||||||
NumServers = length(Servers),
|
NumServers = length(Servers),
|
||||||
ServerInfo =
|
ServerInfo =
|
||||||
lists:map(
|
lists:map(
|
||||||
fun(ServerName) ->
|
fun(ServerName) ->
|
||||||
Hooks = emqx_exhook_mgr:hooks(ServerName),
|
Hooks = emqx_exhook_mgr:hooks(ServerName),
|
||||||
HookNames = lists:map(fun(#{name := Name}) -> Name end, Hooks),
|
HookNames = lists:map(fun(#{name := Name}) -> Name end, Hooks),
|
||||||
#{ hooks => HookNames
|
#{
|
||||||
, %% currently, only grpc driver exists.
|
hooks => HookNames,
|
||||||
driver => grpc
|
%% currently, only grpc driver exists.
|
||||||
}
|
driver => grpc
|
||||||
end,
|
}
|
||||||
Servers),
|
end,
|
||||||
#{ num_servers => NumServers
|
Servers
|
||||||
, servers => ServerInfo
|
),
|
||||||
}
|
#{
|
||||||
|
num_servers => NumServers,
|
||||||
|
servers => ServerInfo
|
||||||
|
}
|
||||||
catch
|
catch
|
||||||
_:_ ->
|
_:_ ->
|
||||||
#{ num_servers => 0
|
#{
|
||||||
, servers => []
|
num_servers => 0,
|
||||||
}
|
servers => []
|
||||||
|
}
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -33,10 +33,13 @@
|
||||||
-define(BAD_REQUEST, 'BAD_REQUEST').
|
-define(BAD_REQUEST, 'BAD_REQUEST').
|
||||||
-define(BAD_RPC, 'BAD_RPC').
|
-define(BAD_RPC, 'BAD_RPC').
|
||||||
|
|
||||||
-dialyzer([{nowarn_function, [ fill_cluster_server_info/5
|
-dialyzer([
|
||||||
, nodes_server_info/5
|
{nowarn_function, [
|
||||||
, fill_server_hooks_info/4
|
fill_cluster_server_info/5,
|
||||||
]}]).
|
nodes_server_info/5,
|
||||||
|
fill_server_hooks_info/4
|
||||||
|
]}
|
||||||
|
]).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% schema
|
%% schema
|
||||||
|
@ -50,142 +53,174 @@ paths() -> ["/exhooks", "/exhooks/:name", "/exhooks/:name/move", "/exhooks/:name
|
||||||
|
|
||||||
schema(("/exhooks")) ->
|
schema(("/exhooks")) ->
|
||||||
#{
|
#{
|
||||||
'operationId' => exhooks,
|
'operationId' => exhooks,
|
||||||
get => #{tags => ?TAGS,
|
get => #{
|
||||||
desc => <<"List all servers">>,
|
tags => ?TAGS,
|
||||||
responses => #{200 => mk(array(ref(detail_server_info)), #{})}
|
desc => <<"List all servers">>,
|
||||||
},
|
responses => #{200 => mk(array(ref(detail_server_info)), #{})}
|
||||||
post => #{tags => ?TAGS,
|
},
|
||||||
desc => <<"Add a servers">>,
|
post => #{
|
||||||
'requestBody' => server_conf_schema(),
|
tags => ?TAGS,
|
||||||
responses => #{201 => mk(ref(detail_server_info), #{}),
|
desc => <<"Add a servers">>,
|
||||||
500 => error_codes([?BAD_RPC], <<"Bad RPC">>)
|
'requestBody' => server_conf_schema(),
|
||||||
}
|
responses => #{
|
||||||
}
|
201 => mk(ref(detail_server_info), #{}),
|
||||||
};
|
500 => error_codes([?BAD_RPC], <<"Bad RPC">>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
schema("/exhooks/:name") ->
|
schema("/exhooks/:name") ->
|
||||||
#{'operationId' => action_with_name,
|
#{
|
||||||
get => #{tags => ?TAGS,
|
'operationId' => action_with_name,
|
||||||
desc => <<"Get the detail information of server">>,
|
get => #{
|
||||||
parameters => params_server_name_in_path(),
|
tags => ?TAGS,
|
||||||
responses => #{200 => mk(ref(detail_server_info), #{}),
|
desc => <<"Get the detail information of server">>,
|
||||||
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
|
parameters => params_server_name_in_path(),
|
||||||
}
|
responses => #{
|
||||||
},
|
200 => mk(ref(detail_server_info), #{}),
|
||||||
put => #{tags => ?TAGS,
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
|
||||||
desc => <<"Update the server">>,
|
}
|
||||||
parameters => params_server_name_in_path(),
|
},
|
||||||
'requestBody' => server_conf_schema(),
|
put => #{
|
||||||
responses => #{200 => <<>>,
|
tags => ?TAGS,
|
||||||
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
desc => <<"Update the server">>,
|
||||||
500 => error_codes([?BAD_RPC], <<"Bad RPC">>)
|
parameters => params_server_name_in_path(),
|
||||||
}
|
'requestBody' => server_conf_schema(),
|
||||||
},
|
responses => #{
|
||||||
delete => #{tags => ?TAGS,
|
200 => <<>>,
|
||||||
desc => <<"Delete the server">>,
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
||||||
parameters => params_server_name_in_path(),
|
500 => error_codes([?BAD_RPC], <<"Bad RPC">>)
|
||||||
responses => #{204 => <<>>,
|
}
|
||||||
500 => error_codes([?BAD_RPC], <<"Bad RPC">>)
|
},
|
||||||
}
|
delete => #{
|
||||||
}
|
tags => ?TAGS,
|
||||||
};
|
desc => <<"Delete the server">>,
|
||||||
|
parameters => params_server_name_in_path(),
|
||||||
|
responses => #{
|
||||||
|
204 => <<>>,
|
||||||
|
500 => error_codes([?BAD_RPC], <<"Bad RPC">>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
schema("/exhooks/:name/hooks") ->
|
schema("/exhooks/:name/hooks") ->
|
||||||
#{'operationId' => server_hooks,
|
#{
|
||||||
get => #{tags => ?TAGS,
|
'operationId' => server_hooks,
|
||||||
desc => <<"Get the hooks information of server">>,
|
get => #{
|
||||||
parameters => params_server_name_in_path(),
|
tags => ?TAGS,
|
||||||
responses => #{200 => mk(array(ref(list_hook_info)), #{}),
|
desc => <<"Get the hooks information of server">>,
|
||||||
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
|
parameters => params_server_name_in_path(),
|
||||||
}
|
responses => #{
|
||||||
}
|
200 => mk(array(ref(list_hook_info)), #{}),
|
||||||
};
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
schema("/exhooks/:name/move") ->
|
schema("/exhooks/:name/move") ->
|
||||||
#{'operationId' => move,
|
#{
|
||||||
post => #{tags => ?TAGS,
|
'operationId' => move,
|
||||||
desc =>
|
post => #{
|
||||||
<<"Move the server.\n",
|
tags => ?TAGS,
|
||||||
"NOTE: The position should be \"front|rear|before:{name}|after:{name}\"\n">>,
|
desc =>
|
||||||
parameters => params_server_name_in_path(),
|
<<"Move the server.\n",
|
||||||
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
"NOTE: The position should be \"front|rear|before:{name}|after:{name}\"\n">>,
|
||||||
ref(move_req),
|
parameters => params_server_name_in_path(),
|
||||||
position_example()),
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
||||||
responses => #{204 => <<"No Content">>,
|
ref(move_req),
|
||||||
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
position_example()
|
||||||
500 => error_codes([?BAD_RPC], <<"Bad RPC">>)
|
),
|
||||||
}
|
responses => #{
|
||||||
}
|
204 => <<"No Content">>,
|
||||||
}.
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
||||||
|
500 => error_codes([?BAD_RPC], <<"Bad RPC">>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.
|
||||||
|
|
||||||
fields(move_req) ->
|
fields(move_req) ->
|
||||||
[{position, mk(string(), #{ desc => <<"The target position to be moved.">>
|
[
|
||||||
, example => <<"front">>})}];
|
{position,
|
||||||
|
mk(string(), #{
|
||||||
|
desc => <<"The target position to be moved.">>,
|
||||||
|
example => <<"front">>
|
||||||
|
})}
|
||||||
|
];
|
||||||
fields(detail_server_info) ->
|
fields(detail_server_info) ->
|
||||||
[ {metrics, mk(ref(metrics), #{})}
|
[
|
||||||
, {node_metrics, mk(array(ref(node_metrics)), #{})}
|
{metrics, mk(ref(metrics), #{})},
|
||||||
, {node_status, mk(array(ref(node_status)), #{})}
|
{node_metrics, mk(array(ref(node_metrics)), #{})},
|
||||||
, {hooks, mk(array(ref(hook_info)), #{})}
|
{node_status, mk(array(ref(node_status)), #{})},
|
||||||
|
{hooks, mk(array(ref(hook_info)), #{})}
|
||||||
] ++ emqx_exhook_schema:server_config();
|
] ++ emqx_exhook_schema:server_config();
|
||||||
|
|
||||||
fields(list_hook_info) ->
|
fields(list_hook_info) ->
|
||||||
[ {name, mk(binary(), #{desc => <<"The hook's name">>})}
|
[
|
||||||
, {params, mk(map(name, binary()),
|
{name, mk(binary(), #{desc => <<"The hook's name">>})},
|
||||||
#{desc => <<"The parameters used when the hook is registered">>})}
|
{params,
|
||||||
, {metrics, mk(ref(metrics), #{})}
|
mk(
|
||||||
, {node_metrics, mk(array(ref(node_metrics)), #{})}
|
map(name, binary()),
|
||||||
|
#{desc => <<"The parameters used when the hook is registered">>}
|
||||||
|
)},
|
||||||
|
{metrics, mk(ref(metrics), #{})},
|
||||||
|
{node_metrics, mk(array(ref(node_metrics)), #{})}
|
||||||
];
|
];
|
||||||
|
|
||||||
fields(node_metrics) ->
|
fields(node_metrics) ->
|
||||||
[ {node, mk(string(), #{})}
|
[
|
||||||
, {metrics, mk(ref(metrics), #{})}
|
{node, mk(string(), #{})},
|
||||||
|
{metrics, mk(ref(metrics), #{})}
|
||||||
];
|
];
|
||||||
|
|
||||||
fields(node_status) ->
|
fields(node_status) ->
|
||||||
[ {node, mk(string(), #{})}
|
[
|
||||||
, {status, mk(enum([running, waiting, stopped, error]), #{})}
|
{node, mk(string(), #{})},
|
||||||
|
{status, mk(enum([running, waiting, stopped, error]), #{})}
|
||||||
];
|
];
|
||||||
|
|
||||||
fields(hook_info) ->
|
fields(hook_info) ->
|
||||||
[ {name, mk(binary(), #{desc => <<"The hook's name">>})}
|
[
|
||||||
, {params, mk(map(name, binary()),
|
{name, mk(binary(), #{desc => <<"The hook's name">>})},
|
||||||
#{desc => <<"The parameters used when the hook is registered">>})}
|
{params,
|
||||||
|
mk(
|
||||||
|
map(name, binary()),
|
||||||
|
#{desc => <<"The parameters used when the hook is registered">>}
|
||||||
|
)}
|
||||||
];
|
];
|
||||||
|
|
||||||
fields(metrics) ->
|
fields(metrics) ->
|
||||||
[ {succeed, mk(integer(), #{})}
|
[
|
||||||
, {failed, mk(integer(), #{})}
|
{succeed, mk(integer(), #{})},
|
||||||
, {rate, mk(integer(), #{})}
|
{failed, mk(integer(), #{})},
|
||||||
, {max_rate, mk(integer(), #{})}
|
{rate, mk(integer(), #{})},
|
||||||
|
{max_rate, mk(integer(), #{})}
|
||||||
];
|
];
|
||||||
|
|
||||||
fields(server_config) ->
|
fields(server_config) ->
|
||||||
emqx_exhook_schema:server_config().
|
emqx_exhook_schema:server_config().
|
||||||
|
|
||||||
params_server_name_in_path() ->
|
params_server_name_in_path() ->
|
||||||
[{name, mk(string(), #{in => path,
|
[
|
||||||
required => true,
|
{name,
|
||||||
example => <<"default">>})}
|
mk(string(), #{
|
||||||
|
in => path,
|
||||||
|
required => true,
|
||||||
|
example => <<"default">>
|
||||||
|
})}
|
||||||
].
|
].
|
||||||
|
|
||||||
server_conf_schema() ->
|
server_conf_schema() ->
|
||||||
SSL = #{ enable => false
|
SSL = #{
|
||||||
, cacertfile => emqx:cert_file(<<"cacert.pem">>)
|
enable => false,
|
||||||
, certfile => emqx:cert_file(<<"cert.pem">>)
|
cacertfile => emqx:cert_file(<<"cacert.pem">>),
|
||||||
, keyfile => emqx:cert_file(<<"key.pem">>)
|
certfile => emqx:cert_file(<<"cert.pem">>),
|
||||||
},
|
keyfile => emqx:cert_file(<<"key.pem">>)
|
||||||
schema_with_example(ref(server_config),
|
},
|
||||||
#{ name => "default"
|
schema_with_example(
|
||||||
, enable => true
|
ref(server_config),
|
||||||
, url => <<"http://127.0.0.1:8081">>
|
#{
|
||||||
, request_timeout => "5s"
|
name => "default",
|
||||||
, failed_action => deny
|
enable => true,
|
||||||
, auto_reconnect => "60s"
|
url => <<"http://127.0.0.1:8081">>,
|
||||||
, pool_size => 8
|
request_timeout => "5s",
|
||||||
, ssl => SSL
|
failed_action => deny,
|
||||||
}).
|
auto_reconnect => "60s",
|
||||||
|
pool_size => 8,
|
||||||
|
ssl => SSL
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
|
@ -194,7 +229,6 @@ exhooks(get, _) ->
|
||||||
Confs = emqx:get_config([exhook, servers]),
|
Confs = emqx:get_config([exhook, servers]),
|
||||||
Infos = nodes_all_server_info(Confs),
|
Infos = nodes_all_server_info(Confs),
|
||||||
{200, Infos};
|
{200, Infos};
|
||||||
|
|
||||||
exhooks(post, #{body := Body}) ->
|
exhooks(post, #{body := Body}) ->
|
||||||
{ok, _} = emqx_exhook_mgr:update_config([exhook, servers], {add, Body}),
|
{ok, _} = emqx_exhook_mgr:update_config([exhook, servers], {add, Body}),
|
||||||
#{<<"name">> := Name} = Body,
|
#{<<"name">> := Name} = Body,
|
||||||
|
@ -202,67 +236,86 @@ exhooks(post, #{body := Body}) ->
|
||||||
|
|
||||||
action_with_name(get, #{bindings := #{name := Name}}) ->
|
action_with_name(get, #{bindings := #{name := Name}}) ->
|
||||||
get_nodes_server_info(Name);
|
get_nodes_server_info(Name);
|
||||||
|
|
||||||
action_with_name(put, #{bindings := #{name := Name}, body := Body}) ->
|
action_with_name(put, #{bindings := #{name := Name}, body := Body}) ->
|
||||||
case emqx_exhook_mgr:update_config([exhook, servers],
|
case
|
||||||
{update, Name, Body}) of
|
emqx_exhook_mgr:update_config(
|
||||||
|
[exhook, servers],
|
||||||
|
{update, Name, Body}
|
||||||
|
)
|
||||||
|
of
|
||||||
{ok, not_found} ->
|
{ok, not_found} ->
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{
|
||||||
message => <<"Server not found">>
|
code => <<"BAD_REQUEST">>,
|
||||||
}};
|
message => <<"Server not found">>
|
||||||
|
}};
|
||||||
{ok, {error, Reason}} ->
|
{ok, {error, Reason}} ->
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{
|
||||||
message => unicode:characters_to_binary(
|
code => <<"BAD_REQUEST">>,
|
||||||
io_lib:format("Error Reason:~p~n", [Reason]))
|
message => unicode:characters_to_binary(
|
||||||
}};
|
io_lib:format("Error Reason:~p~n", [Reason])
|
||||||
|
)
|
||||||
|
}};
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
{200};
|
{200};
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
{500, #{code => <<"BAD_RPC">>,
|
{500, #{
|
||||||
message => Error
|
code => <<"BAD_RPC">>,
|
||||||
}}
|
message => Error
|
||||||
|
}}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
action_with_name(delete, #{bindings := #{name := Name}}) ->
|
action_with_name(delete, #{bindings := #{name := Name}}) ->
|
||||||
case emqx_exhook_mgr:update_config([exhook, servers],
|
case
|
||||||
{delete, Name}) of
|
emqx_exhook_mgr:update_config(
|
||||||
|
[exhook, servers],
|
||||||
|
{delete, Name}
|
||||||
|
)
|
||||||
|
of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
{200};
|
{200};
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
{500, #{code => <<"BAD_RPC">>,
|
{500, #{
|
||||||
message => Error
|
code => <<"BAD_RPC">>,
|
||||||
}}
|
message => Error
|
||||||
|
}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
move(post, #{bindings := #{name := Name}, body := #{<<"position">> := RawPosition}}) ->
|
move(post, #{bindings := #{name := Name}, body := #{<<"position">> := RawPosition}}) ->
|
||||||
case parse_position(RawPosition) of
|
case parse_position(RawPosition) of
|
||||||
{ok, Position} ->
|
{ok, Position} ->
|
||||||
case emqx_exhook_mgr:update_config([exhook, servers],
|
case
|
||||||
{move, Name, Position}) of
|
emqx_exhook_mgr:update_config(
|
||||||
|
[exhook, servers],
|
||||||
|
{move, Name, Position}
|
||||||
|
)
|
||||||
|
of
|
||||||
{ok, ok} ->
|
{ok, ok} ->
|
||||||
{204};
|
{204};
|
||||||
{ok, not_found} ->
|
{ok, not_found} ->
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{
|
||||||
message => <<"Server not found">>
|
code => <<"BAD_REQUEST">>,
|
||||||
}};
|
message => <<"Server not found">>
|
||||||
|
}};
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
{500, #{code => <<"BAD_RPC">>,
|
{500, #{
|
||||||
message => Error
|
code => <<"BAD_RPC">>,
|
||||||
}}
|
message => Error
|
||||||
|
}}
|
||||||
end;
|
end;
|
||||||
{error, invalid_position} ->
|
{error, invalid_position} ->
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{
|
||||||
message => <<"Invalid Position">>
|
code => <<"BAD_REQUEST">>,
|
||||||
}}
|
message => <<"Invalid Position">>
|
||||||
|
}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
server_hooks(get, #{bindings := #{name := Name}}) ->
|
server_hooks(get, #{bindings := #{name := Name}}) ->
|
||||||
Confs = emqx:get_config([exhook, servers]),
|
Confs = emqx:get_config([exhook, servers]),
|
||||||
case lists:search(fun(#{name := CfgName}) -> CfgName =:= Name end, Confs) of
|
case lists:search(fun(#{name := CfgName}) -> CfgName =:= Name end, Confs) of
|
||||||
false ->
|
false ->
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{
|
||||||
message => <<"Server not found">>
|
code => <<"BAD_REQUEST">>,
|
||||||
}};
|
message => <<"Server not found">>
|
||||||
|
}};
|
||||||
_ ->
|
_ ->
|
||||||
Info = get_nodes_server_hooks_info(Name),
|
Info = get_nodes_server_hooks_info(Name),
|
||||||
{200, Info}
|
{200, Info}
|
||||||
|
@ -272,9 +325,10 @@ get_nodes_server_info(Name) ->
|
||||||
Confs = emqx:get_config([exhook, servers]),
|
Confs = emqx:get_config([exhook, servers]),
|
||||||
case lists:search(fun(#{name := CfgName}) -> CfgName =:= Name end, Confs) of
|
case lists:search(fun(#{name := CfgName}) -> CfgName =:= Name end, Confs) of
|
||||||
false ->
|
false ->
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{
|
||||||
message => <<"Server not found">>
|
code => <<"BAD_REQUEST">>,
|
||||||
}};
|
message => <<"Server not found">>
|
||||||
|
}};
|
||||||
{value, Conf} ->
|
{value, Conf} ->
|
||||||
NodeStatus = nodes_server_info(Name),
|
NodeStatus = nodes_server_info(Name),
|
||||||
{200, maps:merge(Conf, NodeStatus)}
|
{200, maps:merge(Conf, NodeStatus)}
|
||||||
|
@ -292,33 +346,34 @@ node_all_server_info([#{name := ServerName} = Conf | T], AllInfos, Default, Acc)
|
||||||
Info = fill_cluster_server_info(AllInfos, [], [], ServerName, Default),
|
Info = fill_cluster_server_info(AllInfos, [], [], ServerName, Default),
|
||||||
AllInfo = maps:merge(Conf, Info),
|
AllInfo = maps:merge(Conf, Info),
|
||||||
node_all_server_info(T, AllInfos, Default, [AllInfo | Acc]);
|
node_all_server_info(T, AllInfos, Default, [AllInfo | Acc]);
|
||||||
|
|
||||||
node_all_server_info([], _, _, Acc) ->
|
node_all_server_info([], _, _, Acc) ->
|
||||||
lists:reverse(Acc).
|
lists:reverse(Acc).
|
||||||
|
|
||||||
fill_cluster_server_info([{Node, {error, _}} | T], StatusL, MetricsL, ServerName, Default) ->
|
fill_cluster_server_info([{Node, {error, _}} | T], StatusL, MetricsL, ServerName, Default) ->
|
||||||
fill_cluster_server_info(T,
|
fill_cluster_server_info(
|
||||||
[#{node => Node, status => error} | StatusL],
|
T,
|
||||||
[#{node => Node, metrics => Default} | MetricsL],
|
[#{node => Node, status => error} | StatusL],
|
||||||
ServerName,
|
[#{node => Node, metrics => Default} | MetricsL],
|
||||||
Default);
|
ServerName,
|
||||||
|
Default
|
||||||
|
);
|
||||||
fill_cluster_server_info([{Node, Result} | T], StatusL, MetricsL, ServerName, Default) ->
|
fill_cluster_server_info([{Node, Result} | T], StatusL, MetricsL, ServerName, Default) ->
|
||||||
#{status := Status, metrics := Metrics} = Result,
|
#{status := Status, metrics := Metrics} = Result,
|
||||||
fill_cluster_server_info(
|
fill_cluster_server_info(
|
||||||
T,
|
T,
|
||||||
[#{node => Node, status => maps:get(ServerName, Status, error)} | StatusL],
|
[#{node => Node, status => maps:get(ServerName, Status, error)} | StatusL],
|
||||||
[#{node => Node, metrics => maps:get(ServerName, Metrics, Default)} | MetricsL],
|
[#{node => Node, metrics => maps:get(ServerName, Metrics, Default)} | MetricsL],
|
||||||
ServerName,
|
ServerName,
|
||||||
Default);
|
Default
|
||||||
|
);
|
||||||
fill_cluster_server_info([], StatusL, MetricsL, ServerName, _) ->
|
fill_cluster_server_info([], StatusL, MetricsL, ServerName, _) ->
|
||||||
Metrics = emqx_exhook_metrics:metrics_aggregate_by_key(metrics, MetricsL),
|
Metrics = emqx_exhook_metrics:metrics_aggregate_by_key(metrics, MetricsL),
|
||||||
#{metrics => Metrics,
|
#{
|
||||||
node_metrics => MetricsL,
|
metrics => Metrics,
|
||||||
node_status => StatusL,
|
node_metrics => MetricsL,
|
||||||
hooks => emqx_exhook_mgr:hooks(ServerName)
|
node_status => StatusL,
|
||||||
}.
|
hooks => emqx_exhook_mgr:hooks(ServerName)
|
||||||
|
}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% GET /exhooks/{name}
|
%% GET /exhooks/{name}
|
||||||
|
@ -329,39 +384,41 @@ nodes_server_info(Name) ->
|
||||||
nodes_server_info(InfoL, Name, Default, [], []).
|
nodes_server_info(InfoL, Name, Default, [], []).
|
||||||
|
|
||||||
nodes_server_info([{Node, {error, _}} | T], Name, Default, StatusL, MetricsL) ->
|
nodes_server_info([{Node, {error, _}} | T], Name, Default, StatusL, MetricsL) ->
|
||||||
nodes_server_info(T,
|
nodes_server_info(
|
||||||
Name,
|
T,
|
||||||
Default,
|
Name,
|
||||||
[#{node => Node, status => error} | StatusL],
|
Default,
|
||||||
[#{node => Node, metrics => Default} | MetricsL]
|
[#{node => Node, status => error} | StatusL],
|
||||||
);
|
[#{node => Node, metrics => Default} | MetricsL]
|
||||||
|
);
|
||||||
nodes_server_info([{Node, Result} | T], Name, Default, StatusL, MetricsL) ->
|
nodes_server_info([{Node, Result} | T], Name, Default, StatusL, MetricsL) ->
|
||||||
#{status := Status, metrics := Metrics} = Result,
|
#{status := Status, metrics := Metrics} = Result,
|
||||||
nodes_server_info(T,
|
nodes_server_info(
|
||||||
Name,
|
T,
|
||||||
Default,
|
Name,
|
||||||
[#{node => Node, status => Status} | StatusL],
|
Default,
|
||||||
[#{node => Node, metrics => Metrics} | MetricsL]
|
[#{node => Node, status => Status} | StatusL],
|
||||||
);
|
[#{node => Node, metrics => Metrics} | MetricsL]
|
||||||
|
);
|
||||||
nodes_server_info([], Name, _, StatusL, MetricsL) ->
|
nodes_server_info([], Name, _, StatusL, MetricsL) ->
|
||||||
#{metrics => emqx_exhook_metrics:metrics_aggregate_by_key(metrics, MetricsL),
|
#{
|
||||||
node_status => StatusL,
|
metrics => emqx_exhook_metrics:metrics_aggregate_by_key(metrics, MetricsL),
|
||||||
node_metrics => MetricsL,
|
node_status => StatusL,
|
||||||
hooks => emqx_exhook_mgr:hooks(Name)
|
node_metrics => MetricsL,
|
||||||
}.
|
hooks => emqx_exhook_mgr:hooks(Name)
|
||||||
|
}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% GET /exhooks/{name}/hooks
|
%% GET /exhooks/{name}/hooks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
get_nodes_server_hooks_info(Name) ->
|
get_nodes_server_hooks_info(Name) ->
|
||||||
case emqx_exhook_mgr:hooks(Name) of
|
case emqx_exhook_mgr:hooks(Name) of
|
||||||
[] -> [];
|
[] ->
|
||||||
|
[];
|
||||||
Hooks ->
|
Hooks ->
|
||||||
AllInfos = call_cluster(fun(Nodes) ->
|
AllInfos = call_cluster(fun(Nodes) ->
|
||||||
emqx_exhook_proto_v1:server_hooks_metrics(Nodes, Name)
|
emqx_exhook_proto_v1:server_hooks_metrics(Nodes, Name)
|
||||||
end),
|
end),
|
||||||
Default = emqx_exhook_metrics:new_metrics_info(),
|
Default = emqx_exhook_metrics:new_metrics_info(),
|
||||||
get_nodes_server_hooks_info(Hooks, AllInfos, Default, [])
|
get_nodes_server_hooks_info(Hooks, AllInfos, Default, [])
|
||||||
end.
|
end.
|
||||||
|
@ -370,18 +427,15 @@ get_nodes_server_hooks_info([#{name := Name} = Spec | T], AllInfos, Default, Acc
|
||||||
Info = fill_server_hooks_info(AllInfos, Name, Default, []),
|
Info = fill_server_hooks_info(AllInfos, Name, Default, []),
|
||||||
AllInfo = maps:merge(Spec, Info),
|
AllInfo = maps:merge(Spec, Info),
|
||||||
get_nodes_server_hooks_info(T, AllInfos, Default, [AllInfo | Acc]);
|
get_nodes_server_hooks_info(T, AllInfos, Default, [AllInfo | Acc]);
|
||||||
|
|
||||||
get_nodes_server_hooks_info([], _, _, Acc) ->
|
get_nodes_server_hooks_info([], _, _, Acc) ->
|
||||||
Acc.
|
Acc.
|
||||||
|
|
||||||
fill_server_hooks_info([{_, {error, _}} | T], Name, Default, MetricsL) ->
|
fill_server_hooks_info([{_, {error, _}} | T], Name, Default, MetricsL) ->
|
||||||
fill_server_hooks_info(T, Name, Default, MetricsL);
|
fill_server_hooks_info(T, Name, Default, MetricsL);
|
||||||
|
|
||||||
fill_server_hooks_info([{Node, MetricsMap} | T], Name, Default, MetricsL) ->
|
fill_server_hooks_info([{Node, MetricsMap} | T], Name, Default, MetricsL) ->
|
||||||
Metrics = maps:get(Name, MetricsMap, Default),
|
Metrics = maps:get(Name, MetricsMap, Default),
|
||||||
NodeMetrics = #{node => Node, metrics => Metrics},
|
NodeMetrics = #{node => Node, metrics => Metrics},
|
||||||
fill_server_hooks_info(T, Name, Default, [NodeMetrics | MetricsL]);
|
fill_server_hooks_info(T, Name, Default, [NodeMetrics | MetricsL]);
|
||||||
|
|
||||||
fill_server_hooks_info([], _Name, _Default, MetricsL) ->
|
fill_server_hooks_info([], _Name, _Default, MetricsL) ->
|
||||||
Metrics = emqx_exhook_metrics:metrics_aggregate_by_key(metrics, MetricsL),
|
Metrics = emqx_exhook_metrics:metrics_aggregate_by_key(metrics, MetricsL),
|
||||||
#{metrics => Metrics, node_metrics => MetricsL}.
|
#{metrics => Metrics, node_metrics => MetricsL}.
|
||||||
|
@ -391,31 +445,39 @@ fill_server_hooks_info([], _Name, _Default, MetricsL) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec call_cluster(fun(([node()]) -> emqx_rpc:erpc_multicall(A))) ->
|
-spec call_cluster(fun(([node()]) -> emqx_rpc:erpc_multicall(A))) ->
|
||||||
[{node(), A | {error, _Err}}].
|
[{node(), A | {error, _Err}}].
|
||||||
call_cluster(Fun) ->
|
call_cluster(Fun) ->
|
||||||
Nodes = mria_mnesia:running_nodes(),
|
Nodes = mria_mnesia:running_nodes(),
|
||||||
Ret = Fun(Nodes),
|
Ret = Fun(Nodes),
|
||||||
lists:zip(Nodes, lists:map(fun emqx_rpc:unwrap_erpc/1, Ret)).
|
lists:zip(Nodes, lists:map(fun emqx_rpc:unwrap_erpc/1, Ret)).
|
||||||
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal Funcs
|
%% Internal Funcs
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
position_example() ->
|
position_example() ->
|
||||||
#{ front =>
|
#{
|
||||||
#{ summary => <<"absolute position 'front'">>
|
front =>
|
||||||
, value => #{<<"position">> => <<"front">>}}
|
#{
|
||||||
, rear =>
|
summary => <<"absolute position 'front'">>,
|
||||||
#{ summary => <<"absolute position 'rear'">>
|
value => #{<<"position">> => <<"front">>}
|
||||||
, value => #{<<"position">> => <<"rear">>}}
|
},
|
||||||
, related_before =>
|
rear =>
|
||||||
#{ summary => <<"relative position 'before'">>
|
#{
|
||||||
, value => #{<<"position">> => <<"before:default">>}}
|
summary => <<"absolute position 'rear'">>,
|
||||||
, related_after =>
|
value => #{<<"position">> => <<"rear">>}
|
||||||
#{ summary => <<"relative position 'after'">>
|
},
|
||||||
, value => #{<<"position">> => <<"after:default">>}}
|
related_before =>
|
||||||
}.
|
#{
|
||||||
|
summary => <<"relative position 'before'">>,
|
||||||
|
value => #{<<"position">> => <<"before:default">>}
|
||||||
|
},
|
||||||
|
related_after =>
|
||||||
|
#{
|
||||||
|
summary => <<"relative position 'after'">>,
|
||||||
|
value => #{<<"position">> => <<"after:default">>}
|
||||||
|
}
|
||||||
|
}.
|
||||||
|
|
||||||
parse_position(<<"front">>) ->
|
parse_position(<<"front">>) ->
|
||||||
{ok, ?CMD_MOVE_FRONT};
|
{ok, ?CMD_MOVE_FRONT};
|
||||||
|
|
|
@ -20,10 +20,11 @@
|
||||||
|
|
||||||
-include("emqx_exhook.hrl").
|
-include("emqx_exhook.hrl").
|
||||||
|
|
||||||
-export([ start/2
|
-export([
|
||||||
, stop/1
|
start/2,
|
||||||
, prep_stop/1
|
stop/1,
|
||||||
]).
|
prep_stop/1
|
||||||
|
]).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Application callbacks
|
%% Application callbacks
|
||||||
|
|
|
@ -20,47 +20,53 @@
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
|
-export([
|
||||||
-export([ on_client_connect/2
|
on_client_connect/2,
|
||||||
, on_client_connack/3
|
on_client_connack/3,
|
||||||
, on_client_connected/2
|
on_client_connected/2,
|
||||||
, on_client_disconnected/3
|
on_client_disconnected/3,
|
||||||
, on_client_authenticate/2
|
on_client_authenticate/2,
|
||||||
, on_client_authorize/4
|
on_client_authorize/4,
|
||||||
, on_client_subscribe/3
|
on_client_subscribe/3,
|
||||||
, on_client_unsubscribe/3
|
on_client_unsubscribe/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Session Lifecircle Hooks
|
%% Session Lifecircle Hooks
|
||||||
-export([ on_session_created/2
|
-export([
|
||||||
, on_session_subscribed/3
|
on_session_created/2,
|
||||||
, on_session_unsubscribed/3
|
on_session_subscribed/3,
|
||||||
, on_session_resumed/2
|
on_session_unsubscribed/3,
|
||||||
, on_session_discarded/2
|
on_session_resumed/2,
|
||||||
, on_session_takenover/2
|
on_session_discarded/2,
|
||||||
, on_session_terminated/3
|
on_session_takenover/2,
|
||||||
]).
|
on_session_terminated/3
|
||||||
|
]).
|
||||||
|
|
||||||
-export([ on_message_publish/1
|
-export([
|
||||||
, on_message_dropped/3
|
on_message_publish/1,
|
||||||
, on_message_delivered/2
|
on_message_dropped/3,
|
||||||
, on_message_acked/2
|
on_message_delivered/2,
|
||||||
]).
|
on_message_acked/2
|
||||||
|
]).
|
||||||
|
|
||||||
%% Utils
|
%% Utils
|
||||||
-export([ message/1
|
-export([
|
||||||
, headers/1
|
message/1,
|
||||||
, stringfy/1
|
headers/1,
|
||||||
, merge_responsed_bool/2
|
stringfy/1,
|
||||||
, merge_responsed_message/2
|
merge_responsed_bool/2,
|
||||||
, assign_to_message/2
|
merge_responsed_message/2,
|
||||||
, clientinfo/1
|
assign_to_message/2,
|
||||||
]).
|
clientinfo/1
|
||||||
|
]).
|
||||||
|
|
||||||
-import(emqx_exhook,
|
-import(
|
||||||
[ cast/2
|
emqx_exhook,
|
||||||
, call_fold/3
|
[
|
||||||
]).
|
cast/2,
|
||||||
|
call_fold/3
|
||||||
|
]
|
||||||
|
).
|
||||||
|
|
||||||
-elvis([{elvis_style, god_modules, disable}]).
|
-elvis([{elvis_style, god_modules, disable}]).
|
||||||
|
|
||||||
|
@ -69,15 +75,18 @@
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
on_client_connect(ConnInfo, Props) ->
|
on_client_connect(ConnInfo, Props) ->
|
||||||
Req = #{conninfo => conninfo(ConnInfo),
|
Req = #{
|
||||||
props => properties(Props)
|
conninfo => conninfo(ConnInfo),
|
||||||
},
|
props => properties(Props)
|
||||||
|
},
|
||||||
cast('client.connect', Req).
|
cast('client.connect', Req).
|
||||||
|
|
||||||
on_client_connack(ConnInfo, Rc, Props) ->
|
on_client_connack(ConnInfo, Rc, Props) ->
|
||||||
Req = #{conninfo => conninfo(ConnInfo),
|
Req = #{
|
||||||
result_code => stringfy(Rc),
|
conninfo => conninfo(ConnInfo),
|
||||||
props => properties(Props)},
|
result_code => stringfy(Rc),
|
||||||
|
props => properties(Props)
|
||||||
|
},
|
||||||
cast('client.connack', Req).
|
cast('client.connack', Req).
|
||||||
|
|
||||||
on_client_connected(ClientInfo, _ConnInfo) ->
|
on_client_connected(ClientInfo, _ConnInfo) ->
|
||||||
|
@ -85,9 +94,10 @@ on_client_connected(ClientInfo, _ConnInfo) ->
|
||||||
cast('client.connected', Req).
|
cast('client.connected', Req).
|
||||||
|
|
||||||
on_client_disconnected(ClientInfo, Reason, _ConnInfo) ->
|
on_client_disconnected(ClientInfo, Reason, _ConnInfo) ->
|
||||||
Req = #{clientinfo => clientinfo(ClientInfo),
|
Req = #{
|
||||||
reason => stringfy(Reason)
|
clientinfo => clientinfo(ClientInfo),
|
||||||
},
|
reason => stringfy(Reason)
|
||||||
|
},
|
||||||
cast('client.disconnected', Req).
|
cast('client.disconnected', Req).
|
||||||
|
|
||||||
on_client_authenticate(ClientInfo, AuthResult) ->
|
on_client_authenticate(ClientInfo, AuthResult) ->
|
||||||
|
@ -98,14 +108,24 @@ on_client_authenticate(ClientInfo, AuthResult) ->
|
||||||
%% detailed info too.
|
%% detailed info too.
|
||||||
%%
|
%%
|
||||||
Bool = AuthResult == ok,
|
Bool = AuthResult == ok,
|
||||||
Req = #{clientinfo => clientinfo(ClientInfo),
|
Req = #{
|
||||||
result => Bool
|
clientinfo => clientinfo(ClientInfo),
|
||||||
},
|
result => Bool
|
||||||
|
},
|
||||||
|
|
||||||
case call_fold('client.authenticate', Req,
|
case
|
||||||
fun merge_responsed_bool/2) of
|
call_fold(
|
||||||
|
'client.authenticate',
|
||||||
|
Req,
|
||||||
|
fun merge_responsed_bool/2
|
||||||
|
)
|
||||||
|
of
|
||||||
{StopOrOk, #{result := Result0}} when is_boolean(Result0) ->
|
{StopOrOk, #{result := Result0}} when is_boolean(Result0) ->
|
||||||
Result = case Result0 of true -> ok; _ -> {error, not_authorized} end,
|
Result =
|
||||||
|
case Result0 of
|
||||||
|
true -> ok;
|
||||||
|
_ -> {error, not_authorized}
|
||||||
|
end,
|
||||||
{StopOrOk, Result};
|
{StopOrOk, Result};
|
||||||
_ ->
|
_ ->
|
||||||
{ok, AuthResult}
|
{ok, AuthResult}
|
||||||
|
@ -113,35 +133,49 @@ on_client_authenticate(ClientInfo, AuthResult) ->
|
||||||
|
|
||||||
on_client_authorize(ClientInfo, PubSub, Topic, Result) ->
|
on_client_authorize(ClientInfo, PubSub, Topic, Result) ->
|
||||||
Bool = Result == allow,
|
Bool = Result == allow,
|
||||||
Type = case PubSub of
|
Type =
|
||||||
publish -> 'PUBLISH';
|
case PubSub of
|
||||||
subscribe -> 'SUBSCRIBE'
|
publish -> 'PUBLISH';
|
||||||
end,
|
subscribe -> 'SUBSCRIBE'
|
||||||
Req = #{clientinfo => clientinfo(ClientInfo),
|
end,
|
||||||
type => Type,
|
Req = #{
|
||||||
topic => Topic,
|
clientinfo => clientinfo(ClientInfo),
|
||||||
result => Bool
|
type => Type,
|
||||||
},
|
topic => Topic,
|
||||||
case call_fold('client.authorize', Req,
|
result => Bool
|
||||||
fun merge_responsed_bool/2) of
|
},
|
||||||
|
case
|
||||||
|
call_fold(
|
||||||
|
'client.authorize',
|
||||||
|
Req,
|
||||||
|
fun merge_responsed_bool/2
|
||||||
|
)
|
||||||
|
of
|
||||||
{StopOrOk, #{result := Result0}} when is_boolean(Result0) ->
|
{StopOrOk, #{result := Result0}} when is_boolean(Result0) ->
|
||||||
NResult = case Result0 of true -> allow; _ -> deny end,
|
NResult =
|
||||||
|
case Result0 of
|
||||||
|
true -> allow;
|
||||||
|
_ -> deny
|
||||||
|
end,
|
||||||
{StopOrOk, NResult};
|
{StopOrOk, NResult};
|
||||||
_ -> {ok, Result}
|
_ ->
|
||||||
|
{ok, Result}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
on_client_subscribe(ClientInfo, Props, TopicFilters) ->
|
on_client_subscribe(ClientInfo, Props, TopicFilters) ->
|
||||||
Req = #{clientinfo => clientinfo(ClientInfo),
|
Req = #{
|
||||||
props => properties(Props),
|
clientinfo => clientinfo(ClientInfo),
|
||||||
topic_filters => topicfilters(TopicFilters)
|
props => properties(Props),
|
||||||
},
|
topic_filters => topicfilters(TopicFilters)
|
||||||
|
},
|
||||||
cast('client.subscribe', Req).
|
cast('client.subscribe', Req).
|
||||||
|
|
||||||
on_client_unsubscribe(ClientInfo, Props, TopicFilters) ->
|
on_client_unsubscribe(ClientInfo, Props, TopicFilters) ->
|
||||||
Req = #{clientinfo => clientinfo(ClientInfo),
|
Req = #{
|
||||||
props => properties(Props),
|
clientinfo => clientinfo(ClientInfo),
|
||||||
topic_filters => topicfilters(TopicFilters)
|
props => properties(Props),
|
||||||
},
|
topic_filters => topicfilters(TopicFilters)
|
||||||
|
},
|
||||||
cast('client.unsubscribe', Req).
|
cast('client.unsubscribe', Req).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -153,16 +187,18 @@ on_session_created(ClientInfo, _SessInfo) ->
|
||||||
cast('session.created', Req).
|
cast('session.created', Req).
|
||||||
|
|
||||||
on_session_subscribed(ClientInfo, Topic, SubOpts) ->
|
on_session_subscribed(ClientInfo, Topic, SubOpts) ->
|
||||||
Req = #{clientinfo => clientinfo(ClientInfo),
|
Req = #{
|
||||||
topic => Topic,
|
clientinfo => clientinfo(ClientInfo),
|
||||||
subopts => maps:with([qos, share, rh, rap, nl], SubOpts)
|
topic => Topic,
|
||||||
},
|
subopts => maps:with([qos, share, rh, rap, nl], SubOpts)
|
||||||
|
},
|
||||||
cast('session.subscribed', Req).
|
cast('session.subscribed', Req).
|
||||||
|
|
||||||
on_session_unsubscribed(ClientInfo, Topic, _SubOpts) ->
|
on_session_unsubscribed(ClientInfo, Topic, _SubOpts) ->
|
||||||
Req = #{clientinfo => clientinfo(ClientInfo),
|
Req = #{
|
||||||
topic => Topic
|
clientinfo => clientinfo(ClientInfo),
|
||||||
},
|
topic => Topic
|
||||||
|
},
|
||||||
cast('session.unsubscribed', Req).
|
cast('session.unsubscribed', Req).
|
||||||
|
|
||||||
on_session_resumed(ClientInfo, _SessInfo) ->
|
on_session_resumed(ClientInfo, _SessInfo) ->
|
||||||
|
@ -178,8 +214,10 @@ on_session_takenover(ClientInfo, _SessInfo) ->
|
||||||
cast('session.takenover', Req).
|
cast('session.takenover', Req).
|
||||||
|
|
||||||
on_session_terminated(ClientInfo, Reason, _SessInfo) ->
|
on_session_terminated(ClientInfo, Reason, _SessInfo) ->
|
||||||
Req = #{clientinfo => clientinfo(ClientInfo),
|
Req = #{
|
||||||
reason => stringfy(Reason)},
|
clientinfo => clientinfo(ClientInfo),
|
||||||
|
reason => stringfy(Reason)
|
||||||
|
},
|
||||||
cast('session.terminated', Req).
|
cast('session.terminated', Req).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -190,110 +228,159 @@ on_message_publish(#message{topic = <<"$SYS/", _/binary>>}) ->
|
||||||
ok;
|
ok;
|
||||||
on_message_publish(Message) ->
|
on_message_publish(Message) ->
|
||||||
Req = #{message => message(Message)},
|
Req = #{message => message(Message)},
|
||||||
case call_fold('message.publish', Req,
|
case
|
||||||
fun emqx_exhook_handler:merge_responsed_message/2) of
|
call_fold(
|
||||||
|
'message.publish',
|
||||||
|
Req,
|
||||||
|
fun emqx_exhook_handler:merge_responsed_message/2
|
||||||
|
)
|
||||||
|
of
|
||||||
{StopOrOk, #{message := NMessage}} ->
|
{StopOrOk, #{message := NMessage}} ->
|
||||||
{StopOrOk, assign_to_message(NMessage, Message)};
|
{StopOrOk, assign_to_message(NMessage, Message)};
|
||||||
_ -> {ok, Message}
|
_ ->
|
||||||
|
{ok, Message}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
on_message_dropped(#message{topic = <<"$SYS/", _/binary>>}, _By, _Reason) ->
|
on_message_dropped(#message{topic = <<"$SYS/", _/binary>>}, _By, _Reason) ->
|
||||||
ok;
|
ok;
|
||||||
on_message_dropped(Message, _By, Reason) ->
|
on_message_dropped(Message, _By, Reason) ->
|
||||||
Req = #{message => message(Message),
|
Req = #{
|
||||||
reason => stringfy(Reason)
|
message => message(Message),
|
||||||
},
|
reason => stringfy(Reason)
|
||||||
|
},
|
||||||
cast('message.dropped', Req).
|
cast('message.dropped', Req).
|
||||||
|
|
||||||
on_message_delivered(_ClientInfo, #message{topic = <<"$SYS/", _/binary>>}) ->
|
on_message_delivered(_ClientInfo, #message{topic = <<"$SYS/", _/binary>>}) ->
|
||||||
ok;
|
ok;
|
||||||
on_message_delivered(ClientInfo, Message) ->
|
on_message_delivered(ClientInfo, Message) ->
|
||||||
Req = #{clientinfo => clientinfo(ClientInfo),
|
Req = #{
|
||||||
message => message(Message)
|
clientinfo => clientinfo(ClientInfo),
|
||||||
},
|
message => message(Message)
|
||||||
|
},
|
||||||
cast('message.delivered', Req).
|
cast('message.delivered', Req).
|
||||||
|
|
||||||
on_message_acked(_ClientInfo, #message{topic = <<"$SYS/", _/binary>>}) ->
|
on_message_acked(_ClientInfo, #message{topic = <<"$SYS/", _/binary>>}) ->
|
||||||
ok;
|
ok;
|
||||||
on_message_acked(ClientInfo, Message) ->
|
on_message_acked(ClientInfo, Message) ->
|
||||||
Req = #{clientinfo => clientinfo(ClientInfo),
|
Req = #{
|
||||||
message => message(Message)
|
clientinfo => clientinfo(ClientInfo),
|
||||||
},
|
message => message(Message)
|
||||||
|
},
|
||||||
cast('message.acked', Req).
|
cast('message.acked', Req).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Types
|
%% Types
|
||||||
|
|
||||||
properties(undefined) -> [];
|
properties(undefined) ->
|
||||||
|
[];
|
||||||
properties(M) when is_map(M) ->
|
properties(M) when is_map(M) ->
|
||||||
maps:fold(fun(K, V, Acc) ->
|
maps:fold(
|
||||||
[#{name => stringfy(K),
|
fun(K, V, Acc) ->
|
||||||
value => stringfy(V)} | Acc]
|
[
|
||||||
end, [], M).
|
#{
|
||||||
|
name => stringfy(K),
|
||||||
|
value => stringfy(V)
|
||||||
|
}
|
||||||
|
| Acc
|
||||||
|
]
|
||||||
|
end,
|
||||||
|
[],
|
||||||
|
M
|
||||||
|
).
|
||||||
|
|
||||||
conninfo(ConnInfo =
|
conninfo(
|
||||||
#{clientid := ClientId,
|
ConnInfo =
|
||||||
peername := {Peerhost, _},
|
#{
|
||||||
sockname := {_, SockPort}}) ->
|
clientid := ClientId,
|
||||||
|
peername := {Peerhost, _},
|
||||||
|
sockname := {_, SockPort}
|
||||||
|
}
|
||||||
|
) ->
|
||||||
Username = maps:get(username, ConnInfo, undefined),
|
Username = maps:get(username, ConnInfo, undefined),
|
||||||
ProtoName = maps:get(proto_name, ConnInfo, undefined),
|
ProtoName = maps:get(proto_name, ConnInfo, undefined),
|
||||||
ProtoVer = maps:get(proto_ver, ConnInfo, undefined),
|
ProtoVer = maps:get(proto_ver, ConnInfo, undefined),
|
||||||
Keepalive = maps:get(keepalive, ConnInfo, 0),
|
Keepalive = maps:get(keepalive, ConnInfo, 0),
|
||||||
#{node => stringfy(node()),
|
#{
|
||||||
clientid => ClientId,
|
node => stringfy(node()),
|
||||||
username => maybe(Username),
|
clientid => ClientId,
|
||||||
peerhost => ntoa(Peerhost),
|
username => maybe(Username),
|
||||||
sockport => SockPort,
|
peerhost => ntoa(Peerhost),
|
||||||
proto_name => ProtoName,
|
sockport => SockPort,
|
||||||
proto_ver => stringfy(ProtoVer),
|
proto_name => ProtoName,
|
||||||
keepalive => Keepalive}.
|
proto_ver => stringfy(ProtoVer),
|
||||||
|
keepalive => Keepalive
|
||||||
|
}.
|
||||||
|
|
||||||
clientinfo(ClientInfo =
|
clientinfo(
|
||||||
#{clientid := ClientId, username := Username, peerhost := PeerHost,
|
ClientInfo =
|
||||||
sockport := SockPort, protocol := Protocol, mountpoint := Mountpoiont}) ->
|
#{
|
||||||
#{node => stringfy(node()),
|
clientid := ClientId,
|
||||||
clientid => ClientId,
|
username := Username,
|
||||||
username => maybe(Username),
|
peerhost := PeerHost,
|
||||||
password => maybe(maps:get(password, ClientInfo, undefined)),
|
sockport := SockPort,
|
||||||
peerhost => ntoa(PeerHost),
|
protocol := Protocol,
|
||||||
sockport => SockPort,
|
mountpoint := Mountpoiont
|
||||||
protocol => stringfy(Protocol),
|
}
|
||||||
mountpoint => maybe(Mountpoiont),
|
) ->
|
||||||
is_superuser => maps:get(is_superuser, ClientInfo, false),
|
#{
|
||||||
anonymous => maps:get(anonymous, ClientInfo, true),
|
node => stringfy(node()),
|
||||||
cn => maybe(maps:get(cn, ClientInfo, undefined)),
|
clientid => ClientId,
|
||||||
dn => maybe(maps:get(dn, ClientInfo, undefined))}.
|
username => maybe(Username),
|
||||||
|
password => maybe(maps:get(password, ClientInfo, undefined)),
|
||||||
|
peerhost => ntoa(PeerHost),
|
||||||
|
sockport => SockPort,
|
||||||
|
protocol => stringfy(Protocol),
|
||||||
|
mountpoint => maybe(Mountpoiont),
|
||||||
|
is_superuser => maps:get(is_superuser, ClientInfo, false),
|
||||||
|
anonymous => maps:get(anonymous, ClientInfo, true),
|
||||||
|
cn => maybe(maps:get(cn, ClientInfo, undefined)),
|
||||||
|
dn => maybe(maps:get(dn, ClientInfo, undefined))
|
||||||
|
}.
|
||||||
|
|
||||||
message(#message{id = Id, qos = Qos, from = From, topic = Topic,
|
message(#message{
|
||||||
payload = Payload, timestamp = Ts, headers = Headers}) ->
|
id = Id,
|
||||||
#{node => stringfy(node()),
|
qos = Qos,
|
||||||
id => emqx_guid:to_hexstr(Id),
|
from = From,
|
||||||
qos => Qos,
|
topic = Topic,
|
||||||
from => stringfy(From),
|
payload = Payload,
|
||||||
topic => Topic,
|
timestamp = Ts,
|
||||||
payload => Payload,
|
headers = Headers
|
||||||
timestamp => Ts,
|
}) ->
|
||||||
headers => headers(Headers)
|
#{
|
||||||
}.
|
node => stringfy(node()),
|
||||||
|
id => emqx_guid:to_hexstr(Id),
|
||||||
|
qos => Qos,
|
||||||
|
from => stringfy(From),
|
||||||
|
topic => Topic,
|
||||||
|
payload => Payload,
|
||||||
|
timestamp => Ts,
|
||||||
|
headers => headers(Headers)
|
||||||
|
}.
|
||||||
|
|
||||||
headers(Headers) ->
|
headers(Headers) ->
|
||||||
Ls = [username, protocol, peerhost, allow_publish],
|
Ls = [username, protocol, peerhost, allow_publish],
|
||||||
maps:fold(
|
maps:fold(
|
||||||
fun
|
fun
|
||||||
(_, undefined, Acc) ->
|
(_, undefined, Acc) ->
|
||||||
Acc; %% Ignore undefined value
|
%% Ignore undefined value
|
||||||
(K, V, Acc) ->
|
Acc;
|
||||||
case lists:member(K, Ls) of
|
(K, V, Acc) ->
|
||||||
true ->
|
case lists:member(K, Ls) of
|
||||||
Acc#{atom_to_binary(K) => bin(K, V)};
|
true ->
|
||||||
_ ->
|
Acc#{atom_to_binary(K) => bin(K, V)};
|
||||||
Acc
|
_ ->
|
||||||
end
|
Acc
|
||||||
end, #{}, Headers).
|
end
|
||||||
|
end,
|
||||||
|
#{},
|
||||||
|
Headers
|
||||||
|
).
|
||||||
|
|
||||||
bin(K, V) when K == username;
|
bin(K, V) when
|
||||||
K == protocol;
|
K == username;
|
||||||
K == allow_publish ->
|
K == protocol;
|
||||||
|
K == allow_publish
|
||||||
|
->
|
||||||
bin(V);
|
bin(V);
|
||||||
bin(peerhost, V) ->
|
bin(peerhost, V) ->
|
||||||
bin(inet:ntoa(V)).
|
bin(inet:ntoa(V)).
|
||||||
|
@ -302,8 +389,14 @@ bin(V) when is_binary(V) -> V;
|
||||||
bin(V) when is_atom(V) -> atom_to_binary(V);
|
bin(V) when is_atom(V) -> atom_to_binary(V);
|
||||||
bin(V) when is_list(V) -> iolist_to_binary(V).
|
bin(V) when is_list(V) -> iolist_to_binary(V).
|
||||||
|
|
||||||
assign_to_message(InMessage = #{qos := Qos, topic := Topic,
|
assign_to_message(
|
||||||
payload := Payload}, Message) ->
|
InMessage = #{
|
||||||
|
qos := Qos,
|
||||||
|
topic := Topic,
|
||||||
|
payload := Payload
|
||||||
|
},
|
||||||
|
Message
|
||||||
|
) ->
|
||||||
NMsg = Message#message{qos = Qos, topic = Topic, payload = Payload},
|
NMsg = Message#message{qos = Qos, topic = Topic, payload = Payload},
|
||||||
enrich_header(maps:get(headers, InMessage, #{}), NMsg).
|
enrich_header(maps:get(headers, InMessage, #{}), NMsg).
|
||||||
|
|
||||||
|
@ -320,7 +413,7 @@ enrich_header(Headers, Message) ->
|
||||||
topicfilters(Tfs) when is_list(Tfs) ->
|
topicfilters(Tfs) when is_list(Tfs) ->
|
||||||
[#{name => Topic, qos => Qos} || {Topic, #{qos := Qos}} <- Tfs].
|
[#{name => Topic, qos => Qos} || {Topic, #{qos := Qos}} <- Tfs].
|
||||||
|
|
||||||
ntoa({0,0,0,0,0,16#ffff,AB,CD}) ->
|
ntoa({0, 0, 0, 0, 0, 16#ffff, AB, CD}) ->
|
||||||
list_to_binary(inet_parse:ntoa({AB bsr 8, AB rem 256, CD bsr 8, CD rem 256}));
|
list_to_binary(inet_parse:ntoa({AB bsr 8, AB rem 256, CD bsr 8, CD rem 256}));
|
||||||
ntoa(IP) ->
|
ntoa(IP) ->
|
||||||
list_to_binary(inet_parse:ntoa(IP)).
|
list_to_binary(inet_parse:ntoa(IP)).
|
||||||
|
@ -344,8 +437,9 @@ stringfy(Term) ->
|
||||||
%% see exhook.proto
|
%% see exhook.proto
|
||||||
merge_responsed_bool(_Req, #{type := 'IGNORE'}) ->
|
merge_responsed_bool(_Req, #{type := 'IGNORE'}) ->
|
||||||
ignore;
|
ignore;
|
||||||
merge_responsed_bool(Req, #{type := Type, value := {bool_result, NewBool}})
|
merge_responsed_bool(Req, #{type := Type, value := {bool_result, NewBool}}) when
|
||||||
when is_boolean(NewBool) ->
|
is_boolean(NewBool)
|
||||||
|
->
|
||||||
{ret(Type), Req#{result => NewBool}};
|
{ret(Type), Req#{result => NewBool}};
|
||||||
merge_responsed_bool(_Req, Resp) ->
|
merge_responsed_bool(_Req, Resp) ->
|
||||||
?SLOG(warning, #{msg => "unknown_responsed_value", resp => Resp}),
|
?SLOG(warning, #{msg => "unknown_responsed_value", resp => Resp}),
|
||||||
|
|
|
@ -19,33 +19,43 @@
|
||||||
-include("emqx_exhook.hrl").
|
-include("emqx_exhook.hrl").
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([ init/0, succeed/2, failed/2
|
-export([
|
||||||
, update/1, new_metrics_info/0, servers_metrics/0
|
init/0,
|
||||||
, on_server_deleted/1, server_metrics/1, hooks_metrics/1
|
succeed/2,
|
||||||
, metrics_aggregate/1, metrics_aggregate_by_key/2
|
failed/2,
|
||||||
, metrics_aggregate_by/2
|
update/1,
|
||||||
]).
|
new_metrics_info/0,
|
||||||
|
servers_metrics/0,
|
||||||
|
on_server_deleted/1,
|
||||||
|
server_metrics/1,
|
||||||
|
hooks_metrics/1,
|
||||||
|
metrics_aggregate/1,
|
||||||
|
metrics_aggregate_by_key/2,
|
||||||
|
metrics_aggregate_by/2
|
||||||
|
]).
|
||||||
|
|
||||||
-record(metrics, { index :: index()
|
-record(metrics, {
|
||||||
, succeed = 0 :: non_neg_integer()
|
index :: index(),
|
||||||
, failed = 0 :: non_neg_integer()
|
succeed = 0 :: non_neg_integer(),
|
||||||
, rate = 0 :: non_neg_integer()
|
failed = 0 :: non_neg_integer(),
|
||||||
, max_rate = 0 :: non_neg_integer()
|
rate = 0 :: non_neg_integer(),
|
||||||
, window_rate :: integer()
|
max_rate = 0 :: non_neg_integer(),
|
||||||
}).
|
window_rate :: integer()
|
||||||
|
}).
|
||||||
|
|
||||||
-type metrics() :: #metrics{}.
|
-type metrics() :: #metrics{}.
|
||||||
-type server_name() :: emqx_exhook_mgr:server_name().
|
-type server_name() :: emqx_exhook_mgr:server_name().
|
||||||
-type hookpoint() :: emqx_exhook_server:hookpoint().
|
-type hookpoint() :: emqx_exhook_server:hookpoint().
|
||||||
-type index() :: {server_name(), hookpoint()}.
|
-type index() :: {server_name(), hookpoint()}.
|
||||||
-type hooks_metrics() :: #{hookpoint() => metrics_info()}.
|
-type hooks_metrics() :: #{hookpoint() => metrics_info()}.
|
||||||
-type servers_metrics() :: #{server_name() => metrics_info()}.
|
-type servers_metrics() :: #{server_name() => metrics_info()}.
|
||||||
|
|
||||||
-type metrics_info() :: #{ succeed := non_neg_integer()
|
-type metrics_info() :: #{
|
||||||
, failed := non_neg_integer()
|
succeed := non_neg_integer(),
|
||||||
, rate := number()
|
failed := non_neg_integer(),
|
||||||
, max_rate := number()
|
rate := number(),
|
||||||
}.
|
max_rate := number()
|
||||||
|
}.
|
||||||
|
|
||||||
-define(INDEX(ServerName, HookPoint), {ServerName, HookPoint}).
|
-define(INDEX(ServerName, HookPoint), {ServerName, HookPoint}).
|
||||||
-export_type([metrics_info/0, servers_metrics/0, hooks_metrics/0]).
|
-export_type([metrics_info/0, servers_metrics/0, hooks_metrics/0]).
|
||||||
|
@ -54,77 +64,113 @@
|
||||||
%%% API
|
%%% API
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
init() ->
|
init() ->
|
||||||
_ = ets:new(?HOOKS_METRICS,
|
_ = ets:new(
|
||||||
[ set, named_table, public
|
?HOOKS_METRICS,
|
||||||
, {keypos, #metrics.index}, {write_concurrency, true}
|
[
|
||||||
, {read_concurrency, true}
|
set,
|
||||||
]),
|
named_table,
|
||||||
|
public,
|
||||||
|
{keypos, #metrics.index},
|
||||||
|
{write_concurrency, true},
|
||||||
|
{read_concurrency, true}
|
||||||
|
]
|
||||||
|
),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
-spec new_metric_info() -> metrics_info().
|
-spec new_metric_info() -> metrics_info().
|
||||||
new_metric_info() ->
|
new_metric_info() ->
|
||||||
#{succeed => 0,
|
#{
|
||||||
failed => 0,
|
succeed => 0,
|
||||||
rate => 0,
|
failed => 0,
|
||||||
max_rate => 0
|
rate => 0,
|
||||||
}.
|
max_rate => 0
|
||||||
|
}.
|
||||||
|
|
||||||
-spec succeed(server_name(), hookpoint()) -> ok.
|
-spec succeed(server_name(), hookpoint()) -> ok.
|
||||||
succeed(Server, Hook) ->
|
succeed(Server, Hook) ->
|
||||||
inc(Server, Hook, #metrics.succeed,
|
inc(
|
||||||
#metrics{ index = {Server, Hook}
|
Server,
|
||||||
, window_rate = 0
|
Hook,
|
||||||
, succeed = 0
|
#metrics.succeed,
|
||||||
}).
|
#metrics{
|
||||||
|
index = {Server, Hook},
|
||||||
|
window_rate = 0,
|
||||||
|
succeed = 0
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
-spec failed(server_name(), hookpoint()) -> ok.
|
-spec failed(server_name(), hookpoint()) -> ok.
|
||||||
failed(Server, Hook) ->
|
failed(Server, Hook) ->
|
||||||
inc(Server, Hook, #metrics.failed,
|
inc(
|
||||||
#metrics{ index = {Server, Hook}
|
Server,
|
||||||
, window_rate = 0
|
Hook,
|
||||||
, failed = 0
|
#metrics.failed,
|
||||||
}).
|
#metrics{
|
||||||
|
index = {Server, Hook},
|
||||||
|
window_rate = 0,
|
||||||
|
failed = 0
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
-spec update(pos_integer()) -> true.
|
-spec update(pos_integer()) -> true.
|
||||||
update(Interval) ->
|
update(Interval) ->
|
||||||
Fun = fun(#metrics{rate = Rate,
|
Fun = fun(
|
||||||
window_rate = WindowRate,
|
#metrics{
|
||||||
max_rate = MaxRate} = Metrics,
|
rate = Rate,
|
||||||
_) ->
|
window_rate = WindowRate,
|
||||||
case calc_metric(WindowRate, Interval) of
|
max_rate = MaxRate
|
||||||
Rate -> true;
|
} = Metrics,
|
||||||
NewRate ->
|
_
|
||||||
MaxRate2 = erlang:max(MaxRate, NewRate),
|
) ->
|
||||||
Metrics2 = Metrics#metrics{rate = NewRate,
|
case calc_metric(WindowRate, Interval) of
|
||||||
window_rate = 0,
|
Rate ->
|
||||||
max_rate = MaxRate2},
|
true;
|
||||||
ets:insert(?HOOKS_METRICS, Metrics2)
|
NewRate ->
|
||||||
end
|
MaxRate2 = erlang:max(MaxRate, NewRate),
|
||||||
end,
|
Metrics2 = Metrics#metrics{
|
||||||
|
rate = NewRate,
|
||||||
|
window_rate = 0,
|
||||||
|
max_rate = MaxRate2
|
||||||
|
},
|
||||||
|
ets:insert(?HOOKS_METRICS, Metrics2)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
ets:foldl(Fun, true, ?HOOKS_METRICS).
|
ets:foldl(Fun, true, ?HOOKS_METRICS).
|
||||||
|
|
||||||
-spec on_server_deleted(server_name()) -> true.
|
-spec on_server_deleted(server_name()) -> true.
|
||||||
on_server_deleted(Name) ->
|
on_server_deleted(Name) ->
|
||||||
ets:match_delete(?HOOKS_METRICS,
|
ets:match_delete(
|
||||||
{metrics, {Name, '_'}, '_', '_', '_', '_', '_'}).
|
?HOOKS_METRICS,
|
||||||
|
{metrics, {Name, '_'}, '_', '_', '_', '_', '_'}
|
||||||
|
).
|
||||||
|
|
||||||
-spec server_metrics(server_name()) -> metrics_info().
|
-spec server_metrics(server_name()) -> metrics_info().
|
||||||
server_metrics(SvrName) ->
|
server_metrics(SvrName) ->
|
||||||
Hooks = ets:match_object(?HOOKS_METRICS,
|
Hooks = ets:match_object(
|
||||||
{metrics, {SvrName, '_'}, '_', '_', '_', '_', '_'}),
|
?HOOKS_METRICS,
|
||||||
|
{metrics, {SvrName, '_'}, '_', '_', '_', '_', '_'}
|
||||||
|
),
|
||||||
|
|
||||||
Fold = fun(#metrics{succeed = Succeed,
|
Fold = fun(
|
||||||
failed = Failed,
|
#metrics{
|
||||||
rate = Rate,
|
succeed = Succeed,
|
||||||
max_rate = MaxRate},
|
failed = Failed,
|
||||||
Acc) ->
|
rate = Rate,
|
||||||
[#{ succeed => Succeed
|
max_rate = MaxRate
|
||||||
, failed => Failed
|
},
|
||||||
, rate => Rate
|
Acc
|
||||||
, max_rate => MaxRate
|
) ->
|
||||||
} | Acc]
|
[
|
||||||
end,
|
#{
|
||||||
|
succeed => Succeed,
|
||||||
|
failed => Failed,
|
||||||
|
rate => Rate,
|
||||||
|
max_rate => MaxRate
|
||||||
|
}
|
||||||
|
| Acc
|
||||||
|
]
|
||||||
|
end,
|
||||||
|
|
||||||
AllMetrics = lists:foldl(Fold, [], Hooks),
|
AllMetrics = lists:foldl(Fold, [], Hooks),
|
||||||
metrics_aggregate(AllMetrics).
|
metrics_aggregate(AllMetrics).
|
||||||
|
@ -133,21 +179,25 @@ server_metrics(SvrName) ->
|
||||||
servers_metrics() ->
|
servers_metrics() ->
|
||||||
AllMetrics = ets:tab2list(?HOOKS_METRICS),
|
AllMetrics = ets:tab2list(?HOOKS_METRICS),
|
||||||
|
|
||||||
GroupFun = fun(#metrics{index = ?INDEX(ServerName, _),
|
GroupFun = fun(
|
||||||
succeed = Succeed,
|
#metrics{
|
||||||
failed = Failed,
|
index = ?INDEX(ServerName, _),
|
||||||
rate = Rate,
|
succeed = Succeed,
|
||||||
max_rate = MaxRate
|
failed = Failed,
|
||||||
},
|
rate = Rate,
|
||||||
Acc) ->
|
max_rate = MaxRate
|
||||||
SvrGroup = maps:get(ServerName, Acc, []),
|
},
|
||||||
Metrics = #{ succeed => Succeed
|
Acc
|
||||||
, failed => Failed
|
) ->
|
||||||
, rate => Rate
|
SvrGroup = maps:get(ServerName, Acc, []),
|
||||||
, max_rate => MaxRate
|
Metrics = #{
|
||||||
},
|
succeed => Succeed,
|
||||||
Acc#{ServerName => [Metrics | SvrGroup]}
|
failed => Failed,
|
||||||
end,
|
rate => Rate,
|
||||||
|
max_rate => MaxRate
|
||||||
|
},
|
||||||
|
Acc#{ServerName => [Metrics | SvrGroup]}
|
||||||
|
end,
|
||||||
|
|
||||||
GroupBySever = lists:foldl(GroupFun, #{}, AllMetrics),
|
GroupBySever = lists:foldl(GroupFun, #{}, AllMetrics),
|
||||||
|
|
||||||
|
@ -156,21 +206,30 @@ servers_metrics() ->
|
||||||
|
|
||||||
-spec hooks_metrics(server_name()) -> hooks_metrics().
|
-spec hooks_metrics(server_name()) -> hooks_metrics().
|
||||||
hooks_metrics(SvrName) ->
|
hooks_metrics(SvrName) ->
|
||||||
Hooks = ets:match_object(?HOOKS_METRICS,
|
Hooks = ets:match_object(
|
||||||
{metrics, {SvrName, '_'}, '_', '_', '_', '_', '_'}),
|
?HOOKS_METRICS,
|
||||||
|
{metrics, {SvrName, '_'}, '_', '_', '_', '_', '_'}
|
||||||
|
),
|
||||||
|
|
||||||
Fold = fun(#metrics{index = ?INDEX(_, HookPoint),
|
Fold = fun(
|
||||||
succeed = Succeed,
|
#metrics{
|
||||||
failed = Failed,
|
index = ?INDEX(_, HookPoint),
|
||||||
rate = Rate,
|
succeed = Succeed,
|
||||||
max_rate = MaxRate},
|
failed = Failed,
|
||||||
Acc) ->
|
rate = Rate,
|
||||||
Acc#{HookPoint => #{ succeed => Succeed
|
max_rate = MaxRate
|
||||||
, failed => Failed
|
},
|
||||||
, rate => Rate
|
Acc
|
||||||
, max_rate => MaxRate
|
) ->
|
||||||
}}
|
Acc#{
|
||||||
end,
|
HookPoint => #{
|
||||||
|
succeed => Succeed,
|
||||||
|
failed => Failed,
|
||||||
|
rate => Rate,
|
||||||
|
max_rate => MaxRate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
lists:foldl(Fold, #{}, Hooks).
|
lists:foldl(Fold, #{}, Hooks).
|
||||||
|
|
||||||
|
@ -178,12 +237,14 @@ hooks_metrics(SvrName) ->
|
||||||
metrics_aggregate(MetricsL) ->
|
metrics_aggregate(MetricsL) ->
|
||||||
metrics_aggregate_by(fun(X) -> X end, MetricsL).
|
metrics_aggregate_by(fun(X) -> X end, MetricsL).
|
||||||
|
|
||||||
-spec metrics_aggregate_by_key(Key, list(HasMetrics)) -> metrics_info()
|
-spec metrics_aggregate_by_key(Key, list(HasMetrics)) -> metrics_info() when
|
||||||
when Key :: any(),
|
Key :: any(),
|
||||||
HasMetrics :: #{Key => metrics_info()}.
|
HasMetrics :: #{Key => metrics_info()}.
|
||||||
metrics_aggregate_by_key(Key, MetricsL) ->
|
metrics_aggregate_by_key(Key, MetricsL) ->
|
||||||
metrics_aggregate_by(fun(X) -> maps:get(Key, X, new_metrics_info()) end,
|
metrics_aggregate_by(
|
||||||
MetricsL).
|
fun(X) -> maps:get(Key, X, new_metrics_info()) end,
|
||||||
|
MetricsL
|
||||||
|
).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
|
@ -191,19 +252,22 @@ metrics_aggregate_by_key(Key, MetricsL) ->
|
||||||
-spec inc(server_name(), hookpoint(), pos_integer(), metrics()) -> ok.
|
-spec inc(server_name(), hookpoint(), pos_integer(), metrics()) -> ok.
|
||||||
inc(Server, Hook, Pos, Default) ->
|
inc(Server, Hook, Pos, Default) ->
|
||||||
Index = {Server, Hook},
|
Index = {Server, Hook},
|
||||||
_ = ets:update_counter(?HOOKS_METRICS,
|
_ = ets:update_counter(
|
||||||
Index,
|
?HOOKS_METRICS,
|
||||||
[{#metrics.window_rate, 1}, {Pos, 1}],
|
Index,
|
||||||
Default),
|
[{#metrics.window_rate, 1}, {Pos, 1}],
|
||||||
|
Default
|
||||||
|
),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
-spec new_metrics_info() -> metrics_info().
|
-spec new_metrics_info() -> metrics_info().
|
||||||
new_metrics_info() ->
|
new_metrics_info() ->
|
||||||
#{ succeed => 0
|
#{
|
||||||
, failed => 0
|
succeed => 0,
|
||||||
, rate => 0
|
failed => 0,
|
||||||
, max_rate => 0
|
rate => 0,
|
||||||
}.
|
max_rate => 0
|
||||||
|
}.
|
||||||
|
|
||||||
-spec calc_metric(non_neg_integer(), non_neg_integer()) -> non_neg_integer().
|
-spec calc_metric(non_neg_integer(), non_neg_integer()) -> non_neg_integer().
|
||||||
calc_metric(Val, Interval) ->
|
calc_metric(Val, Interval) ->
|
||||||
|
@ -211,26 +275,31 @@ calc_metric(Val, Interval) ->
|
||||||
erlang:ceil(Val * 1000 / Interval).
|
erlang:ceil(Val * 1000 / Interval).
|
||||||
|
|
||||||
-spec metrics_add(metrics_info(), metrics_info()) -> metrics_info().
|
-spec metrics_add(metrics_info(), metrics_info()) -> metrics_info().
|
||||||
metrics_add(#{succeed := S1, failed := F1, rate := R1, max_rate := M1}
|
metrics_add(
|
||||||
, #{succeed := S2, failed := F2, rate := R2, max_rate := M2} = Acc) ->
|
#{succeed := S1, failed := F1, rate := R1, max_rate := M1},
|
||||||
Acc#{ succeed := S1 + S2
|
#{succeed := S2, failed := F2, rate := R2, max_rate := M2} = Acc
|
||||||
, failed := F1 + F2
|
) ->
|
||||||
, rate := R1 + R2
|
Acc#{
|
||||||
, max_rate := M1 + M2
|
succeed := S1 + S2,
|
||||||
}.
|
failed := F1 + F2,
|
||||||
|
rate := R1 + R2,
|
||||||
|
max_rate := M1 + M2
|
||||||
|
}.
|
||||||
|
|
||||||
-spec metrics_aggregate_by(fun((X) -> metrics_info()), list(X)) -> metrics_info()
|
-spec metrics_aggregate_by(fun((X) -> metrics_info()), list(X)) -> metrics_info() when
|
||||||
when X :: any().
|
X :: any().
|
||||||
metrics_aggregate_by(_, []) ->
|
metrics_aggregate_by(_, []) ->
|
||||||
new_metric_info();
|
new_metric_info();
|
||||||
|
|
||||||
metrics_aggregate_by(Fun, MetricsL) ->
|
metrics_aggregate_by(Fun, MetricsL) ->
|
||||||
Fold = fun(E, Acc) -> metrics_add(Fun(E), Acc) end,
|
Fold = fun(E, Acc) -> metrics_add(Fun(E), Acc) end,
|
||||||
#{rate := Rate,
|
#{
|
||||||
max_rate := MaxRate} = Result = lists:foldl(Fold, new_metric_info(), MetricsL),
|
rate := Rate,
|
||||||
|
max_rate := MaxRate
|
||||||
|
} = Result = lists:foldl(Fold, new_metric_info(), MetricsL),
|
||||||
|
|
||||||
Len = erlang:length(MetricsL),
|
Len = erlang:length(MetricsL),
|
||||||
|
|
||||||
Result#{rate := Rate div Len,
|
Result#{
|
||||||
max_rate := MaxRate div Len
|
rate := Rate div Len,
|
||||||
}.
|
max_rate := MaxRate div Len
|
||||||
|
}.
|
||||||
|
|
|
@ -26,66 +26,73 @@
|
||||||
-export([start_link/0]).
|
-export([start_link/0]).
|
||||||
|
|
||||||
%% Mgmt API
|
%% Mgmt API
|
||||||
-export([ list/0
|
-export([
|
||||||
, lookup/1
|
list/0,
|
||||||
, enable/1
|
lookup/1,
|
||||||
, disable/1
|
enable/1,
|
||||||
, server_info/1
|
disable/1,
|
||||||
, all_servers_info/0
|
server_info/1,
|
||||||
, server_hooks_metrics/1
|
all_servers_info/0,
|
||||||
]).
|
server_hooks_metrics/1
|
||||||
|
]).
|
||||||
|
|
||||||
%% Helper funcs
|
%% Helper funcs
|
||||||
-export([ running/0
|
-export([
|
||||||
, server/1
|
running/0,
|
||||||
, hooks/1
|
server/1,
|
||||||
, init_ref_counter_table/0
|
hooks/1,
|
||||||
]).
|
init_ref_counter_table/0
|
||||||
|
]).
|
||||||
|
|
||||||
-export([ update_config/2
|
-export([
|
||||||
, pre_config_update/3
|
update_config/2,
|
||||||
, post_config_update/5
|
pre_config_update/3,
|
||||||
]).
|
post_config_update/5
|
||||||
|
]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
-export([ init/1
|
-export([
|
||||||
, handle_call/3
|
init/1,
|
||||||
, handle_cast/2
|
handle_call/3,
|
||||||
, handle_info/2
|
handle_cast/2,
|
||||||
, terminate/2
|
handle_info/2,
|
||||||
, code_change/3
|
terminate/2,
|
||||||
]).
|
code_change/3
|
||||||
|
]).
|
||||||
|
|
||||||
-export([roots/0]).
|
-export([roots/0]).
|
||||||
|
|
||||||
-type state() :: #{%% Running servers
|
%% Running servers
|
||||||
running := servers(),
|
-type state() :: #{
|
||||||
%% Wait to reload servers
|
running := servers(),
|
||||||
waiting := servers(),
|
%% Wait to reload servers
|
||||||
%% Marked stopped servers
|
waiting := servers(),
|
||||||
stopped := servers(),
|
%% Marked stopped servers
|
||||||
%% Timer references
|
stopped := servers(),
|
||||||
trefs := map(),
|
%% Timer references
|
||||||
orders := orders()
|
trefs := map(),
|
||||||
}.
|
orders := orders()
|
||||||
|
}.
|
||||||
|
|
||||||
-type server_name() :: binary().
|
-type server_name() :: binary().
|
||||||
-type servers() :: #{server_name() => server()}.
|
-type servers() :: #{server_name() => server()}.
|
||||||
-type server() :: server_options().
|
-type server() :: server_options().
|
||||||
-type server_options() :: map().
|
-type server_options() :: map().
|
||||||
|
|
||||||
-type position() :: front
|
-type position() ::
|
||||||
| rear
|
front
|
||||||
| {before, binary()}
|
| rear
|
||||||
| {'after', binary()}.
|
| {before, binary()}
|
||||||
|
| {'after', binary()}.
|
||||||
|
|
||||||
-type orders() :: #{server_name() => integer()}.
|
-type orders() :: #{server_name() => integer()}.
|
||||||
|
|
||||||
-type server_info() :: #{name := server_name(),
|
-type server_info() :: #{
|
||||||
status := running | waiting | stopped,
|
name := server_name(),
|
||||||
|
status := running | waiting | stopped,
|
||||||
|
|
||||||
atom() => term()
|
atom() => term()
|
||||||
}.
|
}.
|
||||||
|
|
||||||
-define(DEFAULT_TIMEOUT, 60000).
|
-define(DEFAULT_TIMEOUT, 60000).
|
||||||
-define(REFRESH_INTERVAL, timer:seconds(5)).
|
-define(REFRESH_INTERVAL, timer:seconds(5)).
|
||||||
|
@ -96,9 +103,10 @@
|
||||||
%% APIs
|
%% APIs
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec start_link() -> ignore
|
-spec start_link() ->
|
||||||
| {ok, pid()}
|
ignore
|
||||||
| {error, any()}.
|
| {ok, pid()}
|
||||||
|
| {error, any()}.
|
||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
@ -137,7 +145,7 @@ roots() ->
|
||||||
|
|
||||||
update_config(KeyPath, UpdateReq) ->
|
update_config(KeyPath, UpdateReq) ->
|
||||||
case emqx_conf:update(KeyPath, UpdateReq, #{override_to => cluster}) of
|
case emqx_conf:update(KeyPath, UpdateReq, #{override_to => cluster}) of
|
||||||
{ok, UpdateResult} ->
|
{ok, UpdateResult} ->
|
||||||
#{post_config_update := #{?MODULE := Result}} = UpdateResult,
|
#{post_config_update := #{?MODULE := Result}} = UpdateResult,
|
||||||
{ok, Result};
|
{ok, Result};
|
||||||
Error ->
|
Error ->
|
||||||
|
@ -146,26 +154,30 @@ update_config(KeyPath, UpdateReq) ->
|
||||||
|
|
||||||
pre_config_update(_, {add, Conf}, OldConf) ->
|
pre_config_update(_, {add, Conf}, OldConf) ->
|
||||||
{ok, OldConf ++ [Conf]};
|
{ok, OldConf ++ [Conf]};
|
||||||
|
|
||||||
pre_config_update(_, {update, Name, Conf}, OldConf) ->
|
pre_config_update(_, {update, Name, Conf}, OldConf) ->
|
||||||
case replace_conf(Name, fun(_) -> Conf end, OldConf) of
|
case replace_conf(Name, fun(_) -> Conf end, OldConf) of
|
||||||
not_found -> {error, not_found};
|
not_found -> {error, not_found};
|
||||||
NewConf -> {ok, NewConf}
|
NewConf -> {ok, NewConf}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
pre_config_update(_, {delete, ToDelete}, OldConf) ->
|
pre_config_update(_, {delete, ToDelete}, OldConf) ->
|
||||||
{ok, lists:dropwhile(fun(#{<<"name">> := Name}) -> Name =:= ToDelete end,
|
{ok,
|
||||||
OldConf)};
|
lists:dropwhile(
|
||||||
|
fun(#{<<"name">> := Name}) -> Name =:= ToDelete end,
|
||||||
|
OldConf
|
||||||
|
)};
|
||||||
pre_config_update(_, {move, Name, Position}, OldConf) ->
|
pre_config_update(_, {move, Name, Position}, OldConf) ->
|
||||||
case do_move(Name, Position, OldConf) of
|
case do_move(Name, Position, OldConf) of
|
||||||
not_found -> {error, not_found};
|
not_found -> {error, not_found};
|
||||||
NewConf -> {ok, NewConf}
|
NewConf -> {ok, NewConf}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
pre_config_update(_, {enable, Name, Enable}, OldConf) ->
|
pre_config_update(_, {enable, Name, Enable}, OldConf) ->
|
||||||
case replace_conf(Name,
|
case
|
||||||
fun(Conf) -> Conf#{<<"enable">> => Enable} end, OldConf) of
|
replace_conf(
|
||||||
|
Name,
|
||||||
|
fun(Conf) -> Conf#{<<"enable">> => Enable} end,
|
||||||
|
OldConf
|
||||||
|
)
|
||||||
|
of
|
||||||
not_found -> {error, not_found};
|
not_found -> {error, not_found};
|
||||||
NewConf -> {ok, NewConf}
|
NewConf -> {ok, NewConf}
|
||||||
end.
|
end.
|
||||||
|
@ -186,13 +198,16 @@ init([]) ->
|
||||||
{Waiting, Running, Stopped} = load_all_servers(ServerL),
|
{Waiting, Running, Stopped} = load_all_servers(ServerL),
|
||||||
Orders = reorder(ServerL),
|
Orders = reorder(ServerL),
|
||||||
refresh_tick(),
|
refresh_tick(),
|
||||||
{ok, ensure_reload_timer(
|
{ok,
|
||||||
#{waiting => Waiting,
|
ensure_reload_timer(
|
||||||
running => Running,
|
#{
|
||||||
stopped => Stopped,
|
waiting => Waiting,
|
||||||
trefs => #{},
|
running => Running,
|
||||||
orders => Orders
|
stopped => Stopped,
|
||||||
})}.
|
trefs => #{},
|
||||||
|
orders => Orders
|
||||||
|
}
|
||||||
|
)}.
|
||||||
|
|
||||||
-spec load_all_servers(list(server_options())) -> {servers(), servers(), servers()}.
|
-spec load_all_servers(list(server_options())) -> {servers(), servers(), servers()}.
|
||||||
load_all_servers(ServerL) ->
|
load_all_servers(ServerL) ->
|
||||||
|
@ -208,15 +223,19 @@ load_all_servers([#{name := Name} = Options | More], Waiting, Running, Stopped)
|
||||||
disable ->
|
disable ->
|
||||||
load_all_servers(More, Waiting, Running, Stopped#{Name => Options})
|
load_all_servers(More, Waiting, Running, Stopped#{Name => Options})
|
||||||
end;
|
end;
|
||||||
|
|
||||||
load_all_servers([], Waiting, Running, Stopped) ->
|
load_all_servers([], Waiting, Running, Stopped) ->
|
||||||
{Waiting, Running, Stopped}.
|
{Waiting, Running, Stopped}.
|
||||||
|
|
||||||
handle_call(list, _From, State = #{running := Running,
|
handle_call(
|
||||||
waiting := Waiting,
|
list,
|
||||||
stopped := Stopped,
|
_From,
|
||||||
orders := Orders}) ->
|
State = #{
|
||||||
|
running := Running,
|
||||||
|
waiting := Waiting,
|
||||||
|
stopped := Stopped,
|
||||||
|
orders := Orders
|
||||||
|
}
|
||||||
|
) ->
|
||||||
R = get_servers_info(running, Running),
|
R = get_servers_info(running, Running),
|
||||||
W = get_servers_info(waiting, Waiting),
|
W = get_servers_info(waiting, Waiting),
|
||||||
S = get_servers_info(stopped, Stopped),
|
S = get_servers_info(stopped, Stopped),
|
||||||
|
@ -225,29 +244,33 @@ handle_call(list, _From, State = #{running := Running,
|
||||||
OrderServers = sort_name_by_order(Servers, Orders),
|
OrderServers = sort_name_by_order(Servers, Orders),
|
||||||
|
|
||||||
{reply, OrderServers, State};
|
{reply, OrderServers, State};
|
||||||
|
handle_call(
|
||||||
handle_call({update_config, {move, _Name, _Position}, NewConfL},
|
{update_config, {move, _Name, _Position}, NewConfL},
|
||||||
_From,
|
_From,
|
||||||
State) ->
|
State
|
||||||
|
) ->
|
||||||
Orders = reorder(NewConfL),
|
Orders = reorder(NewConfL),
|
||||||
{reply, ok, State#{orders := Orders}};
|
{reply, ok, State#{orders := Orders}};
|
||||||
|
|
||||||
handle_call({update_config, {delete, ToDelete}, _}, _From, State) ->
|
handle_call({update_config, {delete, ToDelete}, _}, _From, State) ->
|
||||||
{ok, #{orders := Orders,
|
{ok,
|
||||||
stopped := Stopped
|
#{
|
||||||
} = State2} = do_unload_server(ToDelete, State),
|
orders := Orders,
|
||||||
|
stopped := Stopped
|
||||||
|
} = State2} = do_unload_server(ToDelete, State),
|
||||||
|
|
||||||
State3 = State2#{stopped := maps:remove(ToDelete, Stopped),
|
State3 = State2#{
|
||||||
orders := maps:remove(ToDelete, Orders)
|
stopped := maps:remove(ToDelete, Stopped),
|
||||||
},
|
orders := maps:remove(ToDelete, Orders)
|
||||||
|
},
|
||||||
|
|
||||||
emqx_exhook_metrics:on_server_deleted(ToDelete),
|
emqx_exhook_metrics:on_server_deleted(ToDelete),
|
||||||
|
|
||||||
{reply, ok, State3};
|
{reply, ok, State3};
|
||||||
|
handle_call(
|
||||||
handle_call({update_config, {add, RawConf}, NewConfL},
|
{update_config, {add, RawConf}, NewConfL},
|
||||||
_From,
|
_From,
|
||||||
#{running := Running, waiting := Waitting, stopped := Stopped} = State) ->
|
#{running := Running, waiting := Waitting, stopped := Stopped} = State
|
||||||
|
) ->
|
||||||
{_, #{name := Name} = Conf} = emqx_config:check_config(?MODULE, RawConf),
|
{_, #{name := Name} = Conf} = emqx_config:check_config(?MODULE, RawConf),
|
||||||
|
|
||||||
case emqx_exhook_server:load(Name, Conf) of
|
case emqx_exhook_server:load(Name, Conf) of
|
||||||
|
@ -262,7 +285,6 @@ handle_call({update_config, {add, RawConf}, NewConfL},
|
||||||
end,
|
end,
|
||||||
Orders = reorder(NewConfL),
|
Orders = reorder(NewConfL),
|
||||||
{reply, ok, State2#{orders := Orders}};
|
{reply, ok, State2#{orders := Orders}};
|
||||||
|
|
||||||
handle_call({lookup, Name}, _From, State) ->
|
handle_call({lookup, Name}, _From, State) ->
|
||||||
case where_is_server(Name, State) of
|
case where_is_server(Name, State) of
|
||||||
not_found ->
|
not_found ->
|
||||||
|
@ -271,51 +293,57 @@ handle_call({lookup, Name}, _From, State) ->
|
||||||
Result = maps:merge(Conf, #{status => Where})
|
Result = maps:merge(Conf, #{status => Where})
|
||||||
end,
|
end,
|
||||||
{reply, Result, State};
|
{reply, Result, State};
|
||||||
|
|
||||||
handle_call({update_config, {update, Name, _Conf}, NewConfL}, _From, State) ->
|
handle_call({update_config, {update, Name, _Conf}, NewConfL}, _From, State) ->
|
||||||
{Result, State2} = restart_server(Name, NewConfL, State),
|
{Result, State2} = restart_server(Name, NewConfL, State),
|
||||||
{reply, Result, State2};
|
{reply, Result, State2};
|
||||||
|
|
||||||
handle_call({update_config, {enable, Name, _Enable}, NewConfL}, _From, State) ->
|
handle_call({update_config, {enable, Name, _Enable}, NewConfL}, _From, State) ->
|
||||||
{Result, State2} = restart_server(Name, NewConfL, State),
|
{Result, State2} = restart_server(Name, NewConfL, State),
|
||||||
{reply, Result, State2};
|
{reply, Result, State2};
|
||||||
|
|
||||||
handle_call({server_info, Name}, _From, State) ->
|
handle_call({server_info, Name}, _From, State) ->
|
||||||
case where_is_server(Name, State) of
|
case where_is_server(Name, State) of
|
||||||
not_found ->
|
not_found ->
|
||||||
Result = not_found;
|
Result = not_found;
|
||||||
{Status, _} ->
|
{Status, _} ->
|
||||||
HooksMetrics = emqx_exhook_metrics:server_metrics(Name),
|
HooksMetrics = emqx_exhook_metrics:server_metrics(Name),
|
||||||
Result = #{ status => Status
|
Result = #{
|
||||||
, metrics => HooksMetrics
|
status => Status,
|
||||||
}
|
metrics => HooksMetrics
|
||||||
|
}
|
||||||
end,
|
end,
|
||||||
{reply, Result, State};
|
{reply, Result, State};
|
||||||
|
handle_call(
|
||||||
handle_call(all_servers_info, _From, #{running := Running,
|
all_servers_info,
|
||||||
waiting := Waiting,
|
_From,
|
||||||
stopped := Stopped} = State) ->
|
#{
|
||||||
|
running := Running,
|
||||||
|
waiting := Waiting,
|
||||||
|
stopped := Stopped
|
||||||
|
} = State
|
||||||
|
) ->
|
||||||
MakeStatus = fun(Status, Servers, Acc) ->
|
MakeStatus = fun(Status, Servers, Acc) ->
|
||||||
lists:foldl(fun(Name, IAcc) -> IAcc#{Name => Status} end,
|
lists:foldl(
|
||||||
Acc,
|
fun(Name, IAcc) -> IAcc#{Name => Status} end,
|
||||||
maps:keys(Servers))
|
Acc,
|
||||||
end,
|
maps:keys(Servers)
|
||||||
Status = lists:foldl(fun({Status, Servers}, Acc) -> MakeStatus(Status, Servers, Acc) end,
|
)
|
||||||
#{},
|
end,
|
||||||
[{running, Running}, {waiting, Waiting}, {stopped, Stopped}]),
|
Status = lists:foldl(
|
||||||
|
fun({Status, Servers}, Acc) -> MakeStatus(Status, Servers, Acc) end,
|
||||||
|
#{},
|
||||||
|
[{running, Running}, {waiting, Waiting}, {stopped, Stopped}]
|
||||||
|
),
|
||||||
|
|
||||||
Metrics = emqx_exhook_metrics:servers_metrics(),
|
Metrics = emqx_exhook_metrics:servers_metrics(),
|
||||||
|
|
||||||
Result = #{ status => Status
|
Result = #{
|
||||||
, metrics => Metrics
|
status => Status,
|
||||||
},
|
metrics => Metrics
|
||||||
|
},
|
||||||
|
|
||||||
{reply, Result, State};
|
{reply, Result, State};
|
||||||
|
|
||||||
handle_call({server_hooks_metrics, Name}, _From, State) ->
|
handle_call({server_hooks_metrics, Name}, _From, State) ->
|
||||||
Result = emqx_exhook_metrics:hooks_metrics(Name),
|
Result = emqx_exhook_metrics:hooks_metrics(Name),
|
||||||
{reply, Result, State};
|
{reply, Result, State};
|
||||||
|
|
||||||
handle_call(_Request, _From, State) ->
|
handle_call(_Request, _From, State) ->
|
||||||
Reply = ok,
|
Reply = ok,
|
||||||
{reply, Reply, State}.
|
{reply, Reply, State}.
|
||||||
|
@ -331,26 +359,32 @@ handle_info({timeout, _Ref, {reload, Name}}, State) ->
|
||||||
{error, not_found} ->
|
{error, not_found} ->
|
||||||
{noreply, NState};
|
{noreply, NState};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(warning,
|
?SLOG(
|
||||||
#{msg => "failed_to_reload_exhook_callback_server",
|
warning,
|
||||||
|
#{
|
||||||
|
msg => "failed_to_reload_exhook_callback_server",
|
||||||
reason => Reason,
|
reason => Reason,
|
||||||
name => Name}),
|
name => Name
|
||||||
|
}
|
||||||
|
),
|
||||||
{noreply, ensure_reload_timer(NState)}
|
{noreply, ensure_reload_timer(NState)}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_info(refresh_tick, State) ->
|
handle_info(refresh_tick, State) ->
|
||||||
refresh_tick(),
|
refresh_tick(),
|
||||||
emqx_exhook_metrics:update(?REFRESH_INTERVAL),
|
emqx_exhook_metrics:update(?REFRESH_INTERVAL),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
handle_info(_Info, State) ->
|
handle_info(_Info, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
terminate(_Reason, State = #{running := Running}) ->
|
terminate(_Reason, State = #{running := Running}) ->
|
||||||
_ = maps:fold(fun(Name, _, AccIn) ->
|
_ = maps:fold(
|
||||||
{ok, NAccIn} = do_unload_server(Name, AccIn),
|
fun(Name, _, AccIn) ->
|
||||||
NAccIn
|
{ok, NAccIn} = do_unload_server(Name, AccIn),
|
||||||
end, State, Running),
|
NAccIn
|
||||||
|
end,
|
||||||
|
State,
|
||||||
|
Running
|
||||||
|
),
|
||||||
_ = unload_exhooks(),
|
_ = unload_exhooks(),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
@ -362,12 +396,15 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
unload_exhooks() ->
|
unload_exhooks() ->
|
||||||
[emqx:unhook(Name, {M, F}) ||
|
[
|
||||||
{Name, {M, F, _A}} <- ?ENABLED_HOOKS].
|
emqx:unhook(Name, {M, F})
|
||||||
|
|| {Name, {M, F, _A}} <- ?ENABLED_HOOKS
|
||||||
|
].
|
||||||
|
|
||||||
-spec do_load_server(server_name(), state()) -> {{error, not_found}, state()}
|
-spec do_load_server(server_name(), state()) ->
|
||||||
| {{error, already_started}, state()}
|
{{error, not_found}, state()}
|
||||||
| {ok, state()}.
|
| {{error, already_started}, state()}
|
||||||
|
| {ok, state()}.
|
||||||
do_load_server(Name, State = #{orders := Orders}) ->
|
do_load_server(Name, State = #{orders := Orders}) ->
|
||||||
case where_is_server(Name, State) of
|
case where_is_server(Name, State) of
|
||||||
not_found ->
|
not_found ->
|
||||||
|
@ -378,14 +415,18 @@ do_load_server(Name, State = #{orders := Orders}) ->
|
||||||
State2 = clean_reload_timer(Name, State),
|
State2 = clean_reload_timer(Name, State),
|
||||||
{Options, Map2} = maps:take(Name, Map),
|
{Options, Map2} = maps:take(Name, Map),
|
||||||
State3 = State2#{Where := Map2},
|
State3 = State2#{Where := Map2},
|
||||||
#{running := Running,
|
#{
|
||||||
stopped := Stopped} = State3,
|
running := Running,
|
||||||
|
stopped := Stopped
|
||||||
|
} = State3,
|
||||||
case emqx_exhook_server:load(Name, Options) of
|
case emqx_exhook_server:load(Name, Options) of
|
||||||
{ok, ServerState} ->
|
{ok, ServerState} ->
|
||||||
save(Name, ServerState),
|
save(Name, ServerState),
|
||||||
update_order(Orders),
|
update_order(Orders),
|
||||||
?SLOG(info, #{msg => "load_exhook_callback_server_ok",
|
?SLOG(info, #{
|
||||||
name => Name}),
|
msg => "load_exhook_callback_server_ok",
|
||||||
|
name => Name
|
||||||
|
}),
|
||||||
{ok, State3#{running := maps:put(Name, Options, Running)}};
|
{ok, State3#{running := maps:put(Name, Options, Running)}};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{{error, Reason}, State};
|
{{error, Reason}, State};
|
||||||
|
@ -397,45 +438,58 @@ do_load_server(Name, State = #{orders := Orders}) ->
|
||||||
-spec do_unload_server(server_name(), state()) -> {ok, state()}.
|
-spec do_unload_server(server_name(), state()) -> {ok, state()}.
|
||||||
do_unload_server(Name, #{stopped := Stopped} = State) ->
|
do_unload_server(Name, #{stopped := Stopped} = State) ->
|
||||||
case where_is_server(Name, State) of
|
case where_is_server(Name, State) of
|
||||||
{stopped, _} -> {ok, State};
|
{stopped, _} ->
|
||||||
|
{ok, State};
|
||||||
{waiting, Waiting} ->
|
{waiting, Waiting} ->
|
||||||
{Options, Waiting2} = maps:take(Name, Waiting),
|
{Options, Waiting2} = maps:take(Name, Waiting),
|
||||||
{ok, clean_reload_timer(Name,
|
{ok,
|
||||||
State#{waiting := Waiting2,
|
clean_reload_timer(
|
||||||
stopped := maps:put(Name, Options, Stopped)
|
Name,
|
||||||
}
|
State#{
|
||||||
)};
|
waiting := Waiting2,
|
||||||
|
stopped := maps:put(Name, Options, Stopped)
|
||||||
|
}
|
||||||
|
)};
|
||||||
{running, Running} ->
|
{running, Running} ->
|
||||||
Service = server(Name),
|
Service = server(Name),
|
||||||
ok = unsave(Name),
|
ok = unsave(Name),
|
||||||
ok = emqx_exhook_server:unload(Service),
|
ok = emqx_exhook_server:unload(Service),
|
||||||
{Options, Running2} = maps:take(Name, Running),
|
{Options, Running2} = maps:take(Name, Running),
|
||||||
{ok, State#{running := Running2,
|
{ok, State#{
|
||||||
stopped := maps:put(Name, Options, Stopped)
|
running := Running2,
|
||||||
}};
|
stopped := maps:put(Name, Options, Stopped)
|
||||||
not_found -> {ok, State}
|
}};
|
||||||
|
not_found ->
|
||||||
|
{ok, State}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec ensure_reload_timer(state()) -> state().
|
-spec ensure_reload_timer(state()) -> state().
|
||||||
ensure_reload_timer(State = #{waiting := Waiting,
|
ensure_reload_timer(
|
||||||
stopped := Stopped,
|
State = #{
|
||||||
trefs := TRefs}) ->
|
waiting := Waiting,
|
||||||
|
stopped := Stopped,
|
||||||
|
trefs := TRefs
|
||||||
|
}
|
||||||
|
) ->
|
||||||
Iter = maps:iterator(Waiting),
|
Iter = maps:iterator(Waiting),
|
||||||
|
|
||||||
{Waitting2, Stopped2, TRefs2} =
|
{Waitting2, Stopped2, TRefs2} =
|
||||||
ensure_reload_timer(maps:next(Iter), Waiting, Stopped, TRefs),
|
ensure_reload_timer(maps:next(Iter), Waiting, Stopped, TRefs),
|
||||||
|
|
||||||
State#{waiting := Waitting2,
|
State#{
|
||||||
stopped := Stopped2,
|
waiting := Waitting2,
|
||||||
trefs := TRefs2}.
|
stopped := Stopped2,
|
||||||
|
trefs := TRefs2
|
||||||
|
}.
|
||||||
|
|
||||||
ensure_reload_timer(none, Waiting, Stopped, TimerRef) ->
|
ensure_reload_timer(none, Waiting, Stopped, TimerRef) ->
|
||||||
{Waiting, Stopped, TimerRef};
|
{Waiting, Stopped, TimerRef};
|
||||||
|
ensure_reload_timer(
|
||||||
ensure_reload_timer({Name, #{auto_reconnect := Intv}, Iter},
|
{Name, #{auto_reconnect := Intv}, Iter},
|
||||||
Waiting,
|
Waiting,
|
||||||
Stopped,
|
Stopped,
|
||||||
TimerRef) when is_integer(Intv) ->
|
TimerRef
|
||||||
|
) when is_integer(Intv) ->
|
||||||
Next = maps:next(Iter),
|
Next = maps:next(Iter),
|
||||||
case maps:is_key(Name, TimerRef) of
|
case maps:is_key(Name, TimerRef) of
|
||||||
true ->
|
true ->
|
||||||
|
@ -445,54 +499,49 @@ ensure_reload_timer({Name, #{auto_reconnect := Intv}, Iter},
|
||||||
TimerRef2 = maps:put(Name, Ref, TimerRef),
|
TimerRef2 = maps:put(Name, Ref, TimerRef),
|
||||||
ensure_reload_timer(Next, Waiting, Stopped, TimerRef2)
|
ensure_reload_timer(Next, Waiting, Stopped, TimerRef2)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
ensure_reload_timer({Name, Opts, Iter}, Waiting, Stopped, TimerRef) ->
|
ensure_reload_timer({Name, Opts, Iter}, Waiting, Stopped, TimerRef) ->
|
||||||
ensure_reload_timer(maps:next(Iter),
|
ensure_reload_timer(
|
||||||
maps:remove(Name, Waiting),
|
maps:next(Iter),
|
||||||
maps:put(Name, Opts, Stopped),
|
maps:remove(Name, Waiting),
|
||||||
TimerRef).
|
maps:put(Name, Opts, Stopped),
|
||||||
|
TimerRef
|
||||||
|
).
|
||||||
|
|
||||||
-spec clean_reload_timer(server_name(), state()) -> state().
|
-spec clean_reload_timer(server_name(), state()) -> state().
|
||||||
clean_reload_timer(Name, State = #{trefs := TRefs}) ->
|
clean_reload_timer(Name, State = #{trefs := TRefs}) ->
|
||||||
case maps:take(Name, TRefs) of
|
case maps:take(Name, TRefs) of
|
||||||
error -> State;
|
error ->
|
||||||
|
State;
|
||||||
{TRef, NTRefs} ->
|
{TRef, NTRefs} ->
|
||||||
_ = erlang:cancel_timer(TRef),
|
_ = erlang:cancel_timer(TRef),
|
||||||
State#{trefs := NTRefs}
|
State#{trefs := NTRefs}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec do_move(binary(), position(), list(server_options())) ->
|
-spec do_move(binary(), position(), list(server_options())) ->
|
||||||
not_found | list(server_options()).
|
not_found | list(server_options()).
|
||||||
do_move(Name, Position, ConfL) ->
|
do_move(Name, Position, ConfL) ->
|
||||||
move(ConfL, Name, Position, []).
|
move(ConfL, Name, Position, []).
|
||||||
|
|
||||||
move([#{<<"name">> := Name} = Server | T], Name, Position, HeadL) ->
|
move([#{<<"name">> := Name} = Server | T], Name, Position, HeadL) ->
|
||||||
move_to(Position, Server, lists:reverse(HeadL) ++ T);
|
move_to(Position, Server, lists:reverse(HeadL) ++ T);
|
||||||
|
|
||||||
move([Server | T], Name, Position, HeadL) ->
|
move([Server | T], Name, Position, HeadL) ->
|
||||||
move(T, Name, Position, [Server | HeadL]);
|
move(T, Name, Position, [Server | HeadL]);
|
||||||
|
|
||||||
move([], _Name, _Position, _HeadL) ->
|
move([], _Name, _Position, _HeadL) ->
|
||||||
not_found.
|
not_found.
|
||||||
|
|
||||||
move_to(?CMD_MOVE_FRONT, Server, ServerL) ->
|
move_to(?CMD_MOVE_FRONT, Server, ServerL) ->
|
||||||
[Server | ServerL];
|
[Server | ServerL];
|
||||||
|
|
||||||
move_to(?CMD_MOVE_REAR, Server, ServerL) ->
|
move_to(?CMD_MOVE_REAR, Server, ServerL) ->
|
||||||
ServerL ++ [Server];
|
ServerL ++ [Server];
|
||||||
|
|
||||||
move_to(Position, Server, ServerL) ->
|
move_to(Position, Server, ServerL) ->
|
||||||
move_to(ServerL, Position, Server, []).
|
move_to(ServerL, Position, Server, []).
|
||||||
|
|
||||||
move_to([#{<<"name">> := Name} | _] = T, ?CMD_MOVE_BEFORE(Name), Server, HeadL) ->
|
move_to([#{<<"name">> := Name} | _] = T, ?CMD_MOVE_BEFORE(Name), Server, HeadL) ->
|
||||||
lists:reverse(HeadL) ++ [Server | T];
|
lists:reverse(HeadL) ++ [Server | T];
|
||||||
|
|
||||||
move_to([#{<<"name">> := Name} = H | T], ?CMD_MOVE_AFTER(Name), Server, HeadL) ->
|
move_to([#{<<"name">> := Name} = H | T], ?CMD_MOVE_AFTER(Name), Server, HeadL) ->
|
||||||
lists:reverse(HeadL) ++ [H, Server | T];
|
lists:reverse(HeadL) ++ [H, Server | T];
|
||||||
|
|
||||||
move_to([H | T], Position, Server, HeadL) ->
|
move_to([H | T], Position, Server, HeadL) ->
|
||||||
move_to(T, Position, Server, [H | HeadL]);
|
move_to(T, Position, Server, [H | HeadL]);
|
||||||
|
|
||||||
move_to([], _Position, _Server, _HeadL) ->
|
move_to([], _Position, _Server, _HeadL) ->
|
||||||
not_found.
|
not_found.
|
||||||
|
|
||||||
|
@ -504,48 +553,49 @@ reorder(ServerL) ->
|
||||||
|
|
||||||
reorder([#{name := Name} | T], Order, Orders) ->
|
reorder([#{name := Name} | T], Order, Orders) ->
|
||||||
reorder(T, Order + 1, Orders#{Name => Order});
|
reorder(T, Order + 1, Orders#{Name => Order});
|
||||||
|
|
||||||
reorder([], _Order, Orders) ->
|
reorder([], _Order, Orders) ->
|
||||||
Orders.
|
Orders.
|
||||||
|
|
||||||
get_servers_info(Status, Map) ->
|
get_servers_info(Status, Map) ->
|
||||||
Fold = fun(Name, Conf, Acc) ->
|
Fold = fun(Name, Conf, Acc) ->
|
||||||
[maps:merge(Conf, #{status => Status,
|
[
|
||||||
hooks => hooks(Name)}) | Acc]
|
maps:merge(Conf, #{
|
||||||
end,
|
status => Status,
|
||||||
|
hooks => hooks(Name)
|
||||||
|
})
|
||||||
|
| Acc
|
||||||
|
]
|
||||||
|
end,
|
||||||
maps:fold(Fold, [], Map).
|
maps:fold(Fold, [], Map).
|
||||||
|
|
||||||
where_is_server(Name, #{running := Running}) when is_map_key(Name, Running) ->
|
where_is_server(Name, #{running := Running}) when is_map_key(Name, Running) ->
|
||||||
{running, Running};
|
{running, Running};
|
||||||
|
|
||||||
where_is_server(Name, #{waiting := Waiting}) when is_map_key(Name, Waiting) ->
|
where_is_server(Name, #{waiting := Waiting}) when is_map_key(Name, Waiting) ->
|
||||||
{waiting, Waiting};
|
{waiting, Waiting};
|
||||||
|
|
||||||
where_is_server(Name, #{stopped := Stopped}) when is_map_key(Name, Stopped) ->
|
where_is_server(Name, #{stopped := Stopped}) when is_map_key(Name, Stopped) ->
|
||||||
{stopped, Stopped};
|
{stopped, Stopped};
|
||||||
|
|
||||||
where_is_server(_, _) ->
|
where_is_server(_, _) ->
|
||||||
not_found.
|
not_found.
|
||||||
|
|
||||||
-type replace_fun() :: fun((server_options()) -> server_options()).
|
-type replace_fun() :: fun((server_options()) -> server_options()).
|
||||||
|
|
||||||
-spec replace_conf(binary(), replace_fun(), list(server_options())) -> not_found
|
-spec replace_conf(binary(), replace_fun(), list(server_options())) ->
|
||||||
| list(server_options()).
|
not_found
|
||||||
|
| list(server_options()).
|
||||||
replace_conf(Name, ReplaceFun, ConfL) ->
|
replace_conf(Name, ReplaceFun, ConfL) ->
|
||||||
replace_conf(ConfL, Name, ReplaceFun, []).
|
replace_conf(ConfL, Name, ReplaceFun, []).
|
||||||
|
|
||||||
replace_conf([#{<<"name">> := Name} = H | T], Name, ReplaceFun, HeadL) ->
|
replace_conf([#{<<"name">> := Name} = H | T], Name, ReplaceFun, HeadL) ->
|
||||||
New = ReplaceFun(H),
|
New = ReplaceFun(H),
|
||||||
lists:reverse(HeadL) ++ [New | T];
|
lists:reverse(HeadL) ++ [New | T];
|
||||||
|
|
||||||
replace_conf([H | T], Name, ReplaceFun, HeadL) ->
|
replace_conf([H | T], Name, ReplaceFun, HeadL) ->
|
||||||
replace_conf(T, Name, ReplaceFun, [H | HeadL]);
|
replace_conf(T, Name, ReplaceFun, [H | HeadL]);
|
||||||
|
|
||||||
replace_conf([], _, _, _) ->
|
replace_conf([], _, _, _) ->
|
||||||
not_found.
|
not_found.
|
||||||
|
|
||||||
-spec restart_server(binary(), list(server_options()), state()) -> {ok, state()}
|
-spec restart_server(binary(), list(server_options()), state()) ->
|
||||||
| {{error, term()}, state()}.
|
{ok, state()}
|
||||||
|
| {{error, term()}, state()}.
|
||||||
restart_server(Name, ConfL, State) ->
|
restart_server(Name, ConfL, State) ->
|
||||||
case lists:search(fun(#{name := CName}) -> CName =:= Name end, ConfL) of
|
case lists:search(fun(#{name := CName}) -> CName =:= Name end, ConfL) of
|
||||||
false ->
|
false ->
|
||||||
|
@ -567,12 +617,15 @@ restart_server(Name, ConfL, State) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
sort_name_by_order(Names, Orders) ->
|
sort_name_by_order(Names, Orders) ->
|
||||||
lists:sort(fun(A, B) when is_binary(A) ->
|
lists:sort(
|
||||||
maps:get(A, Orders) < maps:get(B, Orders);
|
fun
|
||||||
(#{name := A}, #{name := B}) ->
|
(A, B) when is_binary(A) ->
|
||||||
maps:get(A, Orders) < maps:get(B, Orders)
|
maps:get(A, Orders) < maps:get(B, Orders);
|
||||||
end,
|
(#{name := A}, #{name := B}) ->
|
||||||
Names).
|
maps:get(A, Orders) < maps:get(B, Orders)
|
||||||
|
end,
|
||||||
|
Names
|
||||||
|
).
|
||||||
|
|
||||||
refresh_tick() ->
|
refresh_tick() ->
|
||||||
erlang:send_after(?REFRESH_INTERVAL, self(), ?FUNCTION_NAME).
|
erlang:send_after(?REFRESH_INTERVAL, self(), ?FUNCTION_NAME).
|
||||||
|
|
|
@ -39,46 +39,67 @@ namespace() -> exhook.
|
||||||
roots() -> [exhook].
|
roots() -> [exhook].
|
||||||
|
|
||||||
fields(exhook) ->
|
fields(exhook) ->
|
||||||
[{servers,
|
[
|
||||||
sc(hoconsc:array(ref(server)),
|
{servers,
|
||||||
#{ default => []
|
sc(
|
||||||
, desc => "List of exhook servers."
|
hoconsc:array(ref(server)),
|
||||||
})}
|
#{
|
||||||
|
default => [],
|
||||||
|
desc => "List of exhook servers."
|
||||||
|
}
|
||||||
|
)}
|
||||||
];
|
];
|
||||||
|
|
||||||
fields(server) ->
|
fields(server) ->
|
||||||
[ {name, sc(binary(),
|
[
|
||||||
#{ desc => "Name of the exhook server."
|
{name,
|
||||||
})}
|
sc(
|
||||||
, {enable, sc(boolean(),
|
binary(),
|
||||||
#{ default => true
|
#{desc => "Name of the exhook server."}
|
||||||
, desc => "Enable the exhook server."
|
)},
|
||||||
})}
|
{enable,
|
||||||
, {url, sc(binary(),
|
sc(
|
||||||
#{ desc => "URL of the gRPC server."
|
boolean(),
|
||||||
})}
|
#{
|
||||||
, {request_timeout, sc(duration(),
|
default => true,
|
||||||
#{ default => "5s"
|
desc => "Enable the exhook server."
|
||||||
, desc => "The timeout to request gRPC server."
|
}
|
||||||
})}
|
)},
|
||||||
, {failed_action, failed_action()}
|
{url,
|
||||||
, {ssl,
|
sc(
|
||||||
sc(ref(ssl_conf), #{})}
|
binary(),
|
||||||
, {auto_reconnect,
|
#{desc => "URL of the gRPC server."}
|
||||||
sc(hoconsc:union([false, duration()]),
|
)},
|
||||||
#{ default => "60s"
|
{request_timeout,
|
||||||
, desc => "Whether to automatically reconnect (initialize) the gRPC server.<br/>"
|
sc(
|
||||||
"When gRPC is not available, exhook tries to request the gRPC service at "
|
duration(),
|
||||||
"that interval and reinitialize the list of mounted hooks."
|
#{
|
||||||
})}
|
default => "5s",
|
||||||
, {pool_size,
|
desc => "The timeout to request gRPC server."
|
||||||
sc(integer(),
|
}
|
||||||
#{ default => 8
|
)},
|
||||||
, example => 8
|
{failed_action, failed_action()},
|
||||||
, desc => "The process pool size for gRPC client."
|
{ssl, sc(ref(ssl_conf), #{})},
|
||||||
})}
|
{auto_reconnect,
|
||||||
|
sc(
|
||||||
|
hoconsc:union([false, duration()]),
|
||||||
|
#{
|
||||||
|
default => "60s",
|
||||||
|
desc =>
|
||||||
|
"Whether to automatically reconnect (initialize) the gRPC server.<br/>"
|
||||||
|
"When gRPC is not available, exhook tries to request the gRPC service at "
|
||||||
|
"that interval and reinitialize the list of mounted hooks."
|
||||||
|
}
|
||||||
|
)},
|
||||||
|
{pool_size,
|
||||||
|
sc(
|
||||||
|
integer(),
|
||||||
|
#{
|
||||||
|
default => 8,
|
||||||
|
example => 8,
|
||||||
|
desc => "The process pool size for gRPC client."
|
||||||
|
}
|
||||||
|
)}
|
||||||
];
|
];
|
||||||
|
|
||||||
fields(ssl_conf) ->
|
fields(ssl_conf) ->
|
||||||
Schema = emqx_schema:client_ssl_opts_schema(#{}),
|
Schema = emqx_schema:client_ssl_opts_schema(#{}),
|
||||||
lists:keydelete(user_lookup_fun, 1, Schema).
|
lists:keydelete(user_lookup_fun, 1, Schema).
|
||||||
|
@ -99,11 +120,15 @@ ref(Field) ->
|
||||||
hoconsc:ref(?MODULE, Field).
|
hoconsc:ref(?MODULE, Field).
|
||||||
|
|
||||||
failed_action() ->
|
failed_action() ->
|
||||||
sc(hoconsc:enum([deny, ignore]),
|
sc(
|
||||||
#{ default => deny
|
hoconsc:enum([deny, ignore]),
|
||||||
, desc => "The value that is returned when the request "
|
#{
|
||||||
"to the gRPC server fails for any reason."
|
default => deny,
|
||||||
}).
|
desc =>
|
||||||
|
"The value that is returned when the request "
|
||||||
|
"to the gRPC server fails for any reason."
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
server_config() ->
|
server_config() ->
|
||||||
fields(server).
|
fields(server).
|
||||||
|
|
|
@ -22,56 +22,59 @@
|
||||||
-define(PB_CLIENT_MOD, emqx_exhook_v_1_hook_provider_client).
|
-define(PB_CLIENT_MOD, emqx_exhook_v_1_hook_provider_client).
|
||||||
|
|
||||||
%% Load/Unload
|
%% Load/Unload
|
||||||
-export([ load/2
|
-export([
|
||||||
, unload/1
|
load/2,
|
||||||
]).
|
unload/1
|
||||||
|
]).
|
||||||
|
|
||||||
%% APIs
|
%% APIs
|
||||||
-export([call/3]).
|
-export([call/3]).
|
||||||
|
|
||||||
%% Infos
|
%% Infos
|
||||||
-export([ name/1
|
-export([
|
||||||
, hooks/1
|
name/1,
|
||||||
, format/1
|
hooks/1,
|
||||||
, failed_action/1
|
format/1,
|
||||||
]).
|
failed_action/1
|
||||||
|
]).
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
-export([hk2func/1]).
|
-export([hk2func/1]).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-type server() :: #{%% Server name (equal to grpc client channel name)
|
%% Server name (equal to grpc client channel name)
|
||||||
name := binary(),
|
-type server() :: #{
|
||||||
%% The function options
|
name := binary(),
|
||||||
options := map(),
|
%% The function options
|
||||||
%% gRPC channel pid
|
options := map(),
|
||||||
channel := pid(),
|
%% gRPC channel pid
|
||||||
%% Registered hook names and options
|
channel := pid(),
|
||||||
hookspec := #{hookpoint() => map()},
|
%% Registered hook names and options
|
||||||
%% Metrcis name prefix
|
hookspec := #{hookpoint() => map()},
|
||||||
prefix := list()
|
%% Metrcis name prefix
|
||||||
}.
|
prefix := list()
|
||||||
|
}.
|
||||||
|
|
||||||
|
-type hookpoint() ::
|
||||||
-type hookpoint() :: 'client.connect'
|
'client.connect'
|
||||||
| 'client.connack'
|
| 'client.connack'
|
||||||
| 'client.connected'
|
| 'client.connected'
|
||||||
| 'client.disconnected'
|
| 'client.disconnected'
|
||||||
| 'client.authenticate'
|
| 'client.authenticate'
|
||||||
| 'client.authorize'
|
| 'client.authorize'
|
||||||
| 'client.subscribe'
|
| 'client.subscribe'
|
||||||
| 'client.unsubscribe'
|
| 'client.unsubscribe'
|
||||||
| 'session.created'
|
| 'session.created'
|
||||||
| 'session.subscribed'
|
| 'session.subscribed'
|
||||||
| 'session.unsubscribed'
|
| 'session.unsubscribed'
|
||||||
| 'session.resumed'
|
| 'session.resumed'
|
||||||
| 'session.discarded'
|
| 'session.discarded'
|
||||||
| 'session.takenover'
|
| 'session.takenover'
|
||||||
| 'session.terminated'
|
| 'session.terminated'
|
||||||
| 'message.publish'
|
| 'message.publish'
|
||||||
| 'message.delivered'
|
| 'message.delivered'
|
||||||
| 'message.acked'
|
| 'message.acked'
|
||||||
| 'message.dropped'.
|
| 'message.dropped'.
|
||||||
|
|
||||||
-export_type([server/0, hookpoint/0]).
|
-export_type([server/0, hookpoint/0]).
|
||||||
|
|
||||||
|
@ -86,14 +89,16 @@
|
||||||
-spec load(binary(), map()) -> {ok, server()} | {error, term()} | disable.
|
-spec load(binary(), map()) -> {ok, server()} | {error, term()} | disable.
|
||||||
load(_Name, #{enable := false}) ->
|
load(_Name, #{enable := false}) ->
|
||||||
disable;
|
disable;
|
||||||
|
|
||||||
load(Name, #{request_timeout := Timeout, failed_action := FailedAction} = Opts) ->
|
load(Name, #{request_timeout := Timeout, failed_action := FailedAction} = Opts) ->
|
||||||
ReqOpts = #{timeout => Timeout, failed_action => FailedAction},
|
ReqOpts = #{timeout => Timeout, failed_action => FailedAction},
|
||||||
{SvrAddr, ClientOpts} = channel_opts(Opts),
|
{SvrAddr, ClientOpts} = channel_opts(Opts),
|
||||||
case emqx_exhook_sup:start_grpc_client_channel(
|
case
|
||||||
Name,
|
emqx_exhook_sup:start_grpc_client_channel(
|
||||||
SvrAddr,
|
Name,
|
||||||
ClientOpts) of
|
SvrAddr,
|
||||||
|
ClientOpts
|
||||||
|
)
|
||||||
|
of
|
||||||
{ok, _ChannPoolPid} ->
|
{ok, _ChannPoolPid} ->
|
||||||
case do_init(Name, ReqOpts) of
|
case do_init(Name, ReqOpts) of
|
||||||
{ok, HookSpecs} ->
|
{ok, HookSpecs} ->
|
||||||
|
@ -102,42 +107,53 @@ load(Name, #{request_timeout := Timeout, failed_action := FailedAction} = Opts)
|
||||||
ensure_metrics(Prefix, HookSpecs),
|
ensure_metrics(Prefix, HookSpecs),
|
||||||
%% Ensure hooks
|
%% Ensure hooks
|
||||||
ensure_hooks(HookSpecs),
|
ensure_hooks(HookSpecs),
|
||||||
{ok, #{name => Name,
|
{ok, #{
|
||||||
options => ReqOpts,
|
name => Name,
|
||||||
channel => _ChannPoolPid,
|
options => ReqOpts,
|
||||||
hookspec => HookSpecs,
|
channel => _ChannPoolPid,
|
||||||
prefix => Prefix }};
|
hookspec => HookSpecs,
|
||||||
|
prefix => Prefix
|
||||||
|
}};
|
||||||
{error, _} = E ->
|
{error, _} = E ->
|
||||||
emqx_exhook_sup:stop_grpc_client_channel(Name),
|
emqx_exhook_sup:stop_grpc_client_channel(Name),
|
||||||
E
|
E
|
||||||
end;
|
end;
|
||||||
{error, _} = E -> E
|
{error, _} = E ->
|
||||||
|
E
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
channel_opts(Opts = #{url := URL}) ->
|
channel_opts(Opts = #{url := URL}) ->
|
||||||
ClientOpts = maps:merge(#{pool_size => erlang:system_info(schedulers)},
|
ClientOpts = maps:merge(
|
||||||
Opts),
|
#{pool_size => erlang:system_info(schedulers)},
|
||||||
|
Opts
|
||||||
|
),
|
||||||
case uri_string:parse(URL) of
|
case uri_string:parse(URL) of
|
||||||
#{scheme := <<"http">>, host := Host, port := Port} ->
|
#{scheme := <<"http">>, host := Host, port := Port} ->
|
||||||
{format_http_uri("http", Host, Port), ClientOpts};
|
{format_http_uri("http", Host, Port), ClientOpts};
|
||||||
#{scheme := <<"https">>, host := Host, port := Port} ->
|
#{scheme := <<"https">>, host := Host, port := Port} ->
|
||||||
SslOpts =
|
SslOpts =
|
||||||
case maps:get(ssl, Opts, undefined) of
|
case maps:get(ssl, Opts, undefined) of
|
||||||
undefined -> [];
|
undefined ->
|
||||||
#{enable := false} -> [];
|
[];
|
||||||
|
#{enable := false} ->
|
||||||
|
[];
|
||||||
MapOpts ->
|
MapOpts ->
|
||||||
filter(
|
filter(
|
||||||
[{cacertfile, maps:get(cacertfile, MapOpts, undefined)},
|
[
|
||||||
{certfile, maps:get(certfile, MapOpts, undefined)},
|
{cacertfile, maps:get(cacertfile, MapOpts, undefined)},
|
||||||
{keyfile, maps:get(keyfile, MapOpts, undefined)}
|
{certfile, maps:get(certfile, MapOpts, undefined)},
|
||||||
])
|
{keyfile, maps:get(keyfile, MapOpts, undefined)}
|
||||||
|
]
|
||||||
|
)
|
||||||
end,
|
end,
|
||||||
NClientOpts = ClientOpts#{
|
NClientOpts = ClientOpts#{
|
||||||
gun_opts =>
|
gun_opts =>
|
||||||
#{transport => ssl,
|
#{
|
||||||
transport_opts => SslOpts}
|
transport => ssl,
|
||||||
},
|
transport_opts => SslOpts
|
||||||
|
}
|
||||||
|
},
|
||||||
{format_http_uri("https", Host, Port), NClientOpts};
|
{format_http_uri("https", Host, Port), NClientOpts};
|
||||||
Error ->
|
Error ->
|
||||||
error({bad_server_url, URL, Error})
|
error({bad_server_url, URL, Error})
|
||||||
|
@ -147,7 +163,7 @@ format_http_uri(Scheme, Host, Port) ->
|
||||||
lists:flatten(io_lib:format("~ts://~ts:~w", [Scheme, Host, Port])).
|
lists:flatten(io_lib:format("~ts://~ts:~w", [Scheme, Host, Port])).
|
||||||
|
|
||||||
filter(Ls) ->
|
filter(Ls) ->
|
||||||
[ E || E <- Ls, E /= undefined].
|
[E || E <- Ls, E /= undefined].
|
||||||
|
|
||||||
-spec unload(server()) -> ok.
|
-spec unload(server()) -> ok.
|
||||||
unload(#{name := Name, options := ReqOpts, hookspec := HookSpecs}) ->
|
unload(#{name := Name, options := ReqOpts, hookspec := HookSpecs}) ->
|
||||||
|
@ -162,19 +178,24 @@ do_deinit(Name, ReqOpts) ->
|
||||||
|
|
||||||
do_init(ChannName, ReqOpts) ->
|
do_init(ChannName, ReqOpts) ->
|
||||||
%% BrokerInfo defined at: exhook.protos
|
%% BrokerInfo defined at: exhook.protos
|
||||||
BrokerInfo = maps:with([version, sysdescr, uptime, datetime],
|
BrokerInfo = maps:with(
|
||||||
maps:from_list(emqx_sys:info())),
|
[version, sysdescr, uptime, datetime],
|
||||||
|
maps:from_list(emqx_sys:info())
|
||||||
|
),
|
||||||
Req = #{broker => BrokerInfo},
|
Req = #{broker => BrokerInfo},
|
||||||
case do_call(ChannName, undefined, 'on_provider_loaded', Req, ReqOpts) of
|
case do_call(ChannName, undefined, 'on_provider_loaded', Req, ReqOpts) of
|
||||||
{ok, InitialResp} ->
|
{ok, InitialResp} ->
|
||||||
try
|
try
|
||||||
{ok, resolve_hookspec(maps:get(hooks, InitialResp, []))}
|
{ok, resolve_hookspec(maps:get(hooks, InitialResp, []))}
|
||||||
catch _:Reason:Stk ->
|
catch
|
||||||
?SLOG(error, #{msg => "failed_to_init_channel",
|
_:Reason:Stk ->
|
||||||
channel_name => ChannName,
|
?SLOG(error, #{
|
||||||
reason => Reason,
|
msg => "failed_to_init_channel",
|
||||||
stacktrace => Stk}),
|
channel_name => ChannName,
|
||||||
{error, Reason}
|
reason => Reason,
|
||||||
|
stacktrace => Stk
|
||||||
|
}),
|
||||||
|
{error, Reason}
|
||||||
end;
|
end;
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
|
@ -184,60 +205,80 @@ do_init(ChannName, ReqOpts) ->
|
||||||
resolve_hookspec(HookSpecs) when is_list(HookSpecs) ->
|
resolve_hookspec(HookSpecs) when is_list(HookSpecs) ->
|
||||||
MessageHooks = message_hooks(),
|
MessageHooks = message_hooks(),
|
||||||
AvailableHooks = available_hooks(),
|
AvailableHooks = available_hooks(),
|
||||||
lists:foldr(fun(HookSpec, Acc) ->
|
lists:foldr(
|
||||||
case maps:get(name, HookSpec, undefined) of
|
fun(HookSpec, Acc) ->
|
||||||
undefined -> Acc;
|
case maps:get(name, HookSpec, undefined) of
|
||||||
Name0 ->
|
undefined ->
|
||||||
Name = try
|
Acc;
|
||||||
binary_to_existing_atom(Name0, utf8)
|
Name0 ->
|
||||||
catch T:R:_ -> {T,R}
|
Name =
|
||||||
end,
|
try
|
||||||
case {lists:member(Name, AvailableHooks),
|
binary_to_existing_atom(Name0, utf8)
|
||||||
lists:member(Name, MessageHooks)} of
|
catch
|
||||||
{false, _} ->
|
T:R:_ -> {T, R}
|
||||||
error({unknown_hookpoint, Name});
|
end,
|
||||||
{true, false} ->
|
case {lists:member(Name, AvailableHooks), lists:member(Name, MessageHooks)} of
|
||||||
Acc#{Name => #{}};
|
{false, _} ->
|
||||||
{true, true} ->
|
error({unknown_hookpoint, Name});
|
||||||
Acc#{Name => #{
|
{true, false} ->
|
||||||
topics => maps:get(topics, HookSpec, [])}}
|
Acc#{Name => #{}};
|
||||||
end
|
{true, true} ->
|
||||||
end
|
Acc#{
|
||||||
end, #{}, HookSpecs).
|
Name => #{
|
||||||
|
topics => maps:get(topics, HookSpec, [])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
#{},
|
||||||
|
HookSpecs
|
||||||
|
).
|
||||||
|
|
||||||
ensure_metrics(Prefix, HookSpecs) ->
|
ensure_metrics(Prefix, HookSpecs) ->
|
||||||
Keys = [list_to_atom(Prefix ++ atom_to_list(Hookpoint))
|
Keys = [
|
||||||
|| Hookpoint <- maps:keys(HookSpecs)],
|
list_to_atom(Prefix ++ atom_to_list(Hookpoint))
|
||||||
|
|| Hookpoint <- maps:keys(HookSpecs)
|
||||||
|
],
|
||||||
lists:foreach(fun emqx_metrics:ensure/1, Keys).
|
lists:foreach(fun emqx_metrics:ensure/1, Keys).
|
||||||
|
|
||||||
ensure_hooks(HookSpecs) ->
|
ensure_hooks(HookSpecs) ->
|
||||||
lists:foreach(fun(Hookpoint) ->
|
lists:foreach(
|
||||||
case lists:keyfind(Hookpoint, 1, ?ENABLED_HOOKS) of
|
fun(Hookpoint) ->
|
||||||
false ->
|
case lists:keyfind(Hookpoint, 1, ?ENABLED_HOOKS) of
|
||||||
?SLOG(error, #{msg => "skipped_unknown_hookpoint", hookpoint => Hookpoint});
|
false ->
|
||||||
{Hookpoint, {M, F, A}} ->
|
?SLOG(error, #{msg => "skipped_unknown_hookpoint", hookpoint => Hookpoint});
|
||||||
emqx_hooks:put(Hookpoint, {M, F, A}),
|
{Hookpoint, {M, F, A}} ->
|
||||||
ets:update_counter(?HOOKS_REF_COUNTER, Hookpoint, {2, 1}, {Hookpoint, 0})
|
emqx_hooks:put(Hookpoint, {M, F, A}),
|
||||||
end
|
ets:update_counter(?HOOKS_REF_COUNTER, Hookpoint, {2, 1}, {Hookpoint, 0})
|
||||||
end, maps:keys(HookSpecs)).
|
end
|
||||||
|
end,
|
||||||
|
maps:keys(HookSpecs)
|
||||||
|
).
|
||||||
|
|
||||||
may_unload_hooks(HookSpecs) ->
|
may_unload_hooks(HookSpecs) ->
|
||||||
lists:foreach(fun(Hookpoint) ->
|
lists:foreach(
|
||||||
case ets:update_counter(?HOOKS_REF_COUNTER, Hookpoint, {2, -1}, {Hookpoint, 0}) of
|
fun(Hookpoint) ->
|
||||||
Cnt when Cnt =< 0 ->
|
case ets:update_counter(?HOOKS_REF_COUNTER, Hookpoint, {2, -1}, {Hookpoint, 0}) of
|
||||||
case lists:keyfind(Hookpoint, 1, ?ENABLED_HOOKS) of
|
Cnt when Cnt =< 0 ->
|
||||||
{Hookpoint, {M, F, _A}} ->
|
case lists:keyfind(Hookpoint, 1, ?ENABLED_HOOKS) of
|
||||||
emqx_hooks:del(Hookpoint, {M, F});
|
{Hookpoint, {M, F, _A}} ->
|
||||||
_ -> ok
|
emqx_hooks:del(Hookpoint, {M, F});
|
||||||
end,
|
_ ->
|
||||||
ets:delete(?HOOKS_REF_COUNTER, Hookpoint);
|
ok
|
||||||
_ -> ok
|
end,
|
||||||
end
|
ets:delete(?HOOKS_REF_COUNTER, Hookpoint);
|
||||||
end, maps:keys(HookSpecs)).
|
_ ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
maps:keys(HookSpecs)
|
||||||
|
).
|
||||||
|
|
||||||
format(#{name := Name, hookspec := Hooks}) ->
|
format(#{name := Name, hookspec := Hooks}) ->
|
||||||
lists:flatten(
|
lists:flatten(
|
||||||
io_lib:format("name=~ts, hooks=~0p, active=true", [Name, Hooks])).
|
io_lib:format("name=~ts, hooks=~0p, active=true", [Name, Hooks])
|
||||||
|
).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% APIs
|
%% APIs
|
||||||
|
@ -248,28 +289,41 @@ name(#{name := Name}) ->
|
||||||
|
|
||||||
hooks(#{hookspec := Hooks}) ->
|
hooks(#{hookspec := Hooks}) ->
|
||||||
FoldFun = fun(Hook, Params, Acc) ->
|
FoldFun = fun(Hook, Params, Acc) ->
|
||||||
[#{ name => Hook
|
[
|
||||||
, params => Params
|
#{
|
||||||
} | Acc]
|
name => Hook,
|
||||||
end,
|
params => Params
|
||||||
|
}
|
||||||
|
| Acc
|
||||||
|
]
|
||||||
|
end,
|
||||||
maps:fold(FoldFun, [], Hooks).
|
maps:fold(FoldFun, [], Hooks).
|
||||||
|
|
||||||
-spec call(hookpoint(), map(), server()) -> ignore
|
-spec call(hookpoint(), map(), server()) ->
|
||||||
| {ok, Resp :: term()}
|
ignore
|
||||||
| {error, term()}.
|
| {ok, Resp :: term()}
|
||||||
call(Hookpoint, Req, #{name := ChannName, options := ReqOpts,
|
| {error, term()}.
|
||||||
hookspec := Hooks, prefix := Prefix}) ->
|
call(Hookpoint, Req, #{
|
||||||
|
name := ChannName,
|
||||||
|
options := ReqOpts,
|
||||||
|
hookspec := Hooks,
|
||||||
|
prefix := Prefix
|
||||||
|
}) ->
|
||||||
case maps:get(Hookpoint, Hooks, undefined) of
|
case maps:get(Hookpoint, Hooks, undefined) of
|
||||||
undefined -> ignore;
|
undefined ->
|
||||||
|
ignore;
|
||||||
Opts ->
|
Opts ->
|
||||||
NeedCall = case lists:member(Hookpoint, message_hooks()) of
|
NeedCall =
|
||||||
false -> true;
|
case lists:member(Hookpoint, message_hooks()) of
|
||||||
_ ->
|
false ->
|
||||||
#{message := #{topic := Topic}} = Req,
|
true;
|
||||||
match_topic_filter(Topic, maps:get(topics, Opts, []))
|
_ ->
|
||||||
end,
|
#{message := #{topic := Topic}} = Req,
|
||||||
|
match_topic_filter(Topic, maps:get(topics, Opts, []))
|
||||||
|
end,
|
||||||
case NeedCall of
|
case NeedCall of
|
||||||
false -> ignore;
|
false ->
|
||||||
|
ignore;
|
||||||
_ ->
|
_ ->
|
||||||
inc_metrics(Prefix, Hookpoint),
|
inc_metrics(Prefix, Hookpoint),
|
||||||
GrpcFun = hk2func(Hookpoint),
|
GrpcFun = hk2func(Hookpoint),
|
||||||
|
@ -293,42 +347,67 @@ match_topic_filter(TopicName, TopicFilter) ->
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
-define(CALL_PB_CLIENT(ChanneName, Fun, Req, Options),
|
-define(CALL_PB_CLIENT(ChanneName, Fun, Req, Options),
|
||||||
apply(?PB_CLIENT_MOD, Fun, [Req, #{<<"channel">> => ChannName}, Options])).
|
apply(?PB_CLIENT_MOD, Fun, [Req, #{<<"channel">> => ChannName}, Options])
|
||||||
|
).
|
||||||
-else.
|
-else.
|
||||||
-define(CALL_PB_CLIENT(ChanneName, Fun, Req, Options),
|
-define(CALL_PB_CLIENT(ChanneName, Fun, Req, Options),
|
||||||
apply(?PB_CLIENT_MOD, Fun, [Req, Options])).
|
apply(?PB_CLIENT_MOD, Fun, [Req, Options])
|
||||||
|
).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-spec do_call(binary(), atom(), atom(), map(), map()) -> {ok, map()} | {error, term()}.
|
-spec do_call(binary(), atom(), atom(), map(), map()) -> {ok, map()} | {error, term()}.
|
||||||
do_call(ChannName, Hookpoint, Fun, Req, ReqOpts) ->
|
do_call(ChannName, Hookpoint, Fun, Req, ReqOpts) ->
|
||||||
Options = ReqOpts#{channel => ChannName},
|
Options = ReqOpts#{channel => ChannName},
|
||||||
?SLOG(debug, #{msg => "do_call", module => ?PB_CLIENT_MOD, function => Fun,
|
?SLOG(debug, #{
|
||||||
req => Req, options => Options}),
|
msg => "do_call",
|
||||||
|
module => ?PB_CLIENT_MOD,
|
||||||
|
function => Fun,
|
||||||
|
req => Req,
|
||||||
|
options => Options
|
||||||
|
}),
|
||||||
case catch ?CALL_PB_CLIENT(ChanneName, Fun, Req, Options) of
|
case catch ?CALL_PB_CLIENT(ChanneName, Fun, Req, Options) of
|
||||||
{ok, Resp, Metadata} ->
|
{ok, Resp, Metadata} ->
|
||||||
?SLOG(debug, #{msg => "do_call_ok", resp => Resp, metadata => Metadata}),
|
?SLOG(debug, #{msg => "do_call_ok", resp => Resp, metadata => Metadata}),
|
||||||
update_metrics(Hookpoint, ChannName, fun emqx_exhook_metrics:succeed/2),
|
update_metrics(Hookpoint, ChannName, fun emqx_exhook_metrics:succeed/2),
|
||||||
{ok, Resp};
|
{ok, Resp};
|
||||||
{error, {Code, Msg}, _Metadata} ->
|
{error, {Code, Msg}, _Metadata} ->
|
||||||
?SLOG(error, #{msg => "exhook_call_error", module => ?PB_CLIENT_MOD, function => Fun,
|
?SLOG(error, #{
|
||||||
req => Req, options => Options, code => Code, packet => Msg}),
|
msg => "exhook_call_error",
|
||||||
|
module => ?PB_CLIENT_MOD,
|
||||||
|
function => Fun,
|
||||||
|
req => Req,
|
||||||
|
options => Options,
|
||||||
|
code => Code,
|
||||||
|
packet => Msg
|
||||||
|
}),
|
||||||
update_metrics(Hookpoint, ChannName, fun emqx_exhook_metrics:failed/2),
|
update_metrics(Hookpoint, ChannName, fun emqx_exhook_metrics:failed/2),
|
||||||
{error, {Code, Msg}};
|
{error, {Code, Msg}};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(error, #{msg => "exhook_call_error", module => ?PB_CLIENT_MOD, function => Fun,
|
?SLOG(error, #{
|
||||||
req => Req, options => Options, reason => Reason}),
|
msg => "exhook_call_error",
|
||||||
|
module => ?PB_CLIENT_MOD,
|
||||||
|
function => Fun,
|
||||||
|
req => Req,
|
||||||
|
options => Options,
|
||||||
|
reason => Reason
|
||||||
|
}),
|
||||||
update_metrics(Hookpoint, ChannName, fun emqx_exhook_metrics:failed/2),
|
update_metrics(Hookpoint, ChannName, fun emqx_exhook_metrics:failed/2),
|
||||||
{error, Reason};
|
{error, Reason};
|
||||||
{'EXIT', {Reason, Stk}} ->
|
{'EXIT', {Reason, Stk}} ->
|
||||||
?SLOG(error, #{msg => "exhook_call_exception", module => ?PB_CLIENT_MOD, function => Fun,
|
?SLOG(error, #{
|
||||||
req => Req, options => Options, stacktrace => Stk}),
|
msg => "exhook_call_exception",
|
||||||
|
module => ?PB_CLIENT_MOD,
|
||||||
|
function => Fun,
|
||||||
|
req => Req,
|
||||||
|
options => Options,
|
||||||
|
stacktrace => Stk
|
||||||
|
}),
|
||||||
update_metrics(Hookpoint, ChannName, fun emqx_exhook_metrics:failed/2),
|
update_metrics(Hookpoint, ChannName, fun emqx_exhook_metrics:failed/2),
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
update_metrics(undefined, _ChannName, _Fun) ->
|
update_metrics(undefined, _ChannName, _Fun) ->
|
||||||
ok;
|
ok;
|
||||||
|
|
||||||
update_metrics(Hookpoint, ChannName, Fun) ->
|
update_metrics(Hookpoint, ChannName, Fun) ->
|
||||||
Fun(ChannName, Hookpoint).
|
Fun(ChannName, Hookpoint).
|
||||||
|
|
||||||
|
@ -356,20 +435,36 @@ hk2func('session.discarded') -> 'on_session_discarded';
|
||||||
hk2func('session.takenover') -> 'on_session_takenover';
|
hk2func('session.takenover') -> 'on_session_takenover';
|
||||||
hk2func('session.terminated') -> 'on_session_terminated';
|
hk2func('session.terminated') -> 'on_session_terminated';
|
||||||
hk2func('message.publish') -> 'on_message_publish';
|
hk2func('message.publish') -> 'on_message_publish';
|
||||||
hk2func('message.delivered') ->'on_message_delivered';
|
hk2func('message.delivered') -> 'on_message_delivered';
|
||||||
hk2func('message.acked') -> 'on_message_acked';
|
hk2func('message.acked') -> 'on_message_acked';
|
||||||
hk2func('message.dropped') ->'on_message_dropped'.
|
hk2func('message.dropped') -> 'on_message_dropped'.
|
||||||
|
|
||||||
-compile({inline, [message_hooks/0]}).
|
-compile({inline, [message_hooks/0]}).
|
||||||
message_hooks() ->
|
message_hooks() ->
|
||||||
['message.publish', 'message.delivered',
|
[
|
||||||
'message.acked', 'message.dropped'].
|
'message.publish',
|
||||||
|
'message.delivered',
|
||||||
|
'message.acked',
|
||||||
|
'message.dropped'
|
||||||
|
].
|
||||||
|
|
||||||
-compile({inline, [available_hooks/0]}).
|
-compile({inline, [available_hooks/0]}).
|
||||||
available_hooks() ->
|
available_hooks() ->
|
||||||
['client.connect', 'client.connack', 'client.connected',
|
[
|
||||||
'client.disconnected', 'client.authenticate', 'client.authorize',
|
'client.connect',
|
||||||
'client.subscribe', 'client.unsubscribe',
|
'client.connack',
|
||||||
'session.created', 'session.subscribed', 'session.unsubscribed',
|
'client.connected',
|
||||||
'session.resumed', 'session.discarded', 'session.takenover',
|
'client.disconnected',
|
||||||
'session.terminated' | message_hooks()].
|
'client.authenticate',
|
||||||
|
'client.authorize',
|
||||||
|
'client.subscribe',
|
||||||
|
'client.unsubscribe',
|
||||||
|
'session.created',
|
||||||
|
'session.subscribed',
|
||||||
|
'session.unsubscribed',
|
||||||
|
'session.resumed',
|
||||||
|
'session.discarded',
|
||||||
|
'session.takenover',
|
||||||
|
'session.terminated'
|
||||||
|
| message_hooks()
|
||||||
|
].
|
||||||
|
|
|
@ -18,21 +18,22 @@
|
||||||
|
|
||||||
-behaviour(supervisor).
|
-behaviour(supervisor).
|
||||||
|
|
||||||
-export([ start_link/0
|
-export([
|
||||||
, init/1
|
start_link/0,
|
||||||
]).
|
init/1
|
||||||
|
]).
|
||||||
|
|
||||||
-export([ start_grpc_client_channel/3
|
-export([
|
||||||
, stop_grpc_client_channel/1
|
start_grpc_client_channel/3,
|
||||||
]).
|
stop_grpc_client_channel/1
|
||||||
|
]).
|
||||||
|
|
||||||
-define(CHILD(Mod, Type, Args),
|
-define(CHILD(Mod, Type, Args), #{
|
||||||
#{ id => Mod
|
id => Mod,
|
||||||
, start => {Mod, start_link, Args}
|
start => {Mod, start_link, Args},
|
||||||
, type => Type
|
type => Type,
|
||||||
, shutdown => 15000
|
shutdown => 15000
|
||||||
}
|
}).
|
||||||
).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Supervisor APIs & Callbacks
|
%% Supervisor APIs & Callbacks
|
||||||
|
@ -52,9 +53,10 @@ init([]) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec start_grpc_client_channel(
|
-spec start_grpc_client_channel(
|
||||||
binary(),
|
binary(),
|
||||||
uri_string:uri_string(),
|
uri_string:uri_string(),
|
||||||
grpc_client_sup:options()) -> {ok, pid()} | {error, term()}.
|
grpc_client_sup:options()
|
||||||
|
) -> {ok, pid()} | {error, term()}.
|
||||||
start_grpc_client_channel(Name, SvrAddr, Options) ->
|
start_grpc_client_channel(Name, SvrAddr, Options) ->
|
||||||
grpc_client_sup:create_channel_pool(Name, SvrAddr, Options).
|
grpc_client_sup:create_channel_pool(Name, SvrAddr, Options).
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,13 @@
|
||||||
|
|
||||||
-behaviour(emqx_bpapi).
|
-behaviour(emqx_bpapi).
|
||||||
|
|
||||||
-export([ introduced_in/0
|
-export([
|
||||||
|
introduced_in/0,
|
||||||
|
|
||||||
, all_servers_info/1
|
all_servers_info/1,
|
||||||
, server_info/2
|
server_info/2,
|
||||||
, server_hooks_metrics/2
|
server_hooks_metrics/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include_lib("emqx/include/bpapi.hrl").
|
-include_lib("emqx/include/bpapi.hrl").
|
||||||
|
|
||||||
|
@ -31,16 +32,16 @@ introduced_in() ->
|
||||||
"5.0.0".
|
"5.0.0".
|
||||||
|
|
||||||
-spec all_servers_info([node()]) ->
|
-spec all_servers_info([node()]) ->
|
||||||
emqx_rpc:erpc_multicall(map()).
|
emqx_rpc:erpc_multicall(map()).
|
||||||
all_servers_info(Nodes) ->
|
all_servers_info(Nodes) ->
|
||||||
erpc:multicall(Nodes, emqx_exhook_mgr, all_servers_info, []).
|
erpc:multicall(Nodes, emqx_exhook_mgr, all_servers_info, []).
|
||||||
|
|
||||||
-spec server_info([node()], emqx_exhook_mgr:server_name()) ->
|
-spec server_info([node()], emqx_exhook_mgr:server_name()) ->
|
||||||
emqx_rpc:erpc_multicall(map()).
|
emqx_rpc:erpc_multicall(map()).
|
||||||
server_info(Nodes, Name) ->
|
server_info(Nodes, Name) ->
|
||||||
erpc:multicall(Nodes, emqx_exhook_mgr, server_info, [Name]).
|
erpc:multicall(Nodes, emqx_exhook_mgr, server_info, [Name]).
|
||||||
|
|
||||||
-spec server_hooks_metrics([node()], emqx_exhook_mgr:server_name()) ->
|
-spec server_hooks_metrics([node()], emqx_exhook_mgr:server_name()) ->
|
||||||
emqx_rpc:erpc_multicall(emqx_exhook_metrics:hooks_metrics()).
|
emqx_rpc:erpc_multicall(emqx_exhook_metrics:hooks_metrics()).
|
||||||
server_hooks_metrics(Nodes, Name) ->
|
server_hooks_metrics(Nodes, Name) ->
|
||||||
erpc:multicall(Nodes, emqx_exhook_mgr, server_hooks_metrics, [Name]).
|
erpc:multicall(Nodes, emqx_exhook_mgr, server_hooks_metrics, [Name]).
|
||||||
|
|
|
@ -23,26 +23,27 @@
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
-define(CLUSTER_RPC_SHARD, emqx_cluster_rpc_shard).
|
-define(CLUSTER_RPC_SHARD, emqx_cluster_rpc_shard).
|
||||||
|
|
||||||
-define(CONF_DEFAULT, <<"
|
-define(CONF_DEFAULT, <<
|
||||||
exhook {
|
"\n"
|
||||||
servers = [
|
"exhook {\n"
|
||||||
{ name = default,
|
" servers = [\n"
|
||||||
url = \"http://127.0.0.1:9000\"
|
" { name = default,\n"
|
||||||
},
|
" url = \"http://127.0.0.1:9000\"\n"
|
||||||
{ name = enable,
|
" },\n"
|
||||||
enable = false,
|
" { name = enable,\n"
|
||||||
url = \"http://127.0.0.1:9000\"
|
" enable = false,\n"
|
||||||
},
|
" url = \"http://127.0.0.1:9000\"\n"
|
||||||
{ name = error,
|
" },\n"
|
||||||
url = \"http://127.0.0.1:9001\"
|
" { name = error,\n"
|
||||||
},
|
" url = \"http://127.0.0.1:9001\"\n"
|
||||||
{ name = not_reconnect,
|
" },\n"
|
||||||
auto_reconnect = false,
|
" { name = not_reconnect,\n"
|
||||||
url = \"http://127.0.0.1:9001\"
|
" auto_reconnect = false,\n"
|
||||||
}
|
" url = \"http://127.0.0.1:9001\"\n"
|
||||||
]
|
" }\n"
|
||||||
}
|
" ]\n"
|
||||||
">>).
|
"}\n"
|
||||||
|
>>).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Setups
|
%% Setups
|
||||||
|
@ -79,7 +80,8 @@ init_per_testcase(_, Config) ->
|
||||||
|
|
||||||
end_per_testcase(_, Config) ->
|
end_per_testcase(_, Config) ->
|
||||||
case erlang:whereis(node()) of
|
case erlang:whereis(node()) of
|
||||||
undefined -> ok;
|
undefined ->
|
||||||
|
ok;
|
||||||
P ->
|
P ->
|
||||||
erlang:unlink(P),
|
erlang:unlink(P),
|
||||||
erlang:exit(P, kill)
|
erlang:exit(P, kill)
|
||||||
|
@ -95,22 +97,29 @@ load_cfg(Cfg) ->
|
||||||
|
|
||||||
t_access_failed_if_no_server_running(_) ->
|
t_access_failed_if_no_server_running(_) ->
|
||||||
emqx_exhook_mgr:disable(<<"default">>),
|
emqx_exhook_mgr:disable(<<"default">>),
|
||||||
ClientInfo = #{clientid => <<"user-id-1">>,
|
ClientInfo = #{
|
||||||
username => <<"usera">>,
|
clientid => <<"user-id-1">>,
|
||||||
peerhost => {127,0,0,1},
|
username => <<"usera">>,
|
||||||
sockport => 1883,
|
peerhost => {127, 0, 0, 1},
|
||||||
protocol => mqtt,
|
sockport => 1883,
|
||||||
mountpoint => undefined
|
protocol => mqtt,
|
||||||
},
|
mountpoint => undefined
|
||||||
?assertMatch({stop, {error, not_authorized}},
|
},
|
||||||
emqx_exhook_handler:on_client_authenticate(ClientInfo, #{auth_result => success})),
|
?assertMatch(
|
||||||
|
{stop, {error, not_authorized}},
|
||||||
|
emqx_exhook_handler:on_client_authenticate(ClientInfo, #{auth_result => success})
|
||||||
|
),
|
||||||
|
|
||||||
?assertMatch({stop, deny},
|
?assertMatch(
|
||||||
emqx_exhook_handler:on_client_authorize(ClientInfo, publish, <<"t/1">>, allow)),
|
{stop, deny},
|
||||||
|
emqx_exhook_handler:on_client_authorize(ClientInfo, publish, <<"t/1">>, allow)
|
||||||
|
),
|
||||||
|
|
||||||
Message = emqx_message:make(<<"t/1">>, <<"abc">>),
|
Message = emqx_message:make(<<"t/1">>, <<"abc">>),
|
||||||
?assertMatch({stop, Message},
|
?assertMatch(
|
||||||
emqx_exhook_handler:on_message_publish(Message)),
|
{stop, Message},
|
||||||
|
emqx_exhook_handler:on_message_publish(Message)
|
||||||
|
),
|
||||||
emqx_exhook_mgr:enable(<<"default">>).
|
emqx_exhook_mgr:enable(<<"default">>).
|
||||||
|
|
||||||
t_lookup(_) ->
|
t_lookup(_) ->
|
||||||
|
@ -120,9 +129,14 @@ t_lookup(_) ->
|
||||||
|
|
||||||
t_list(_) ->
|
t_list(_) ->
|
||||||
[H | _] = emqx_exhook_mgr:list(),
|
[H | _] = emqx_exhook_mgr:list(),
|
||||||
?assertMatch(#{name := _,
|
?assertMatch(
|
||||||
status := _,
|
#{
|
||||||
hooks := _}, H).
|
name := _,
|
||||||
|
status := _,
|
||||||
|
hooks := _
|
||||||
|
},
|
||||||
|
H
|
||||||
|
).
|
||||||
|
|
||||||
t_unexpected(_) ->
|
t_unexpected(_) ->
|
||||||
ok = gen_server:cast(emqx_exhook_mgr, unexpected),
|
ok = gen_server:cast(emqx_exhook_mgr, unexpected),
|
||||||
|
@ -149,9 +163,11 @@ t_error_update_conf(_) ->
|
||||||
ErrorAnd = #{<<"name">> => Name, <<"url">> => <<"http://127.0.0.1:9001">>},
|
ErrorAnd = #{<<"name">> => Name, <<"url">> => <<"http://127.0.0.1:9001">>},
|
||||||
{ok, _} = emqx_exhook_mgr:update_config(Path, {add, ErrorAnd}),
|
{ok, _} = emqx_exhook_mgr:update_config(Path, {add, ErrorAnd}),
|
||||||
|
|
||||||
DisableAnd = #{<<"name">> => Name,
|
DisableAnd = #{
|
||||||
<<"url">> => <<"http://127.0.0.1:9001">>,
|
<<"name">> => Name,
|
||||||
<<"enable">> => false},
|
<<"url">> => <<"http://127.0.0.1:9001">>,
|
||||||
|
<<"enable">> => false
|
||||||
|
},
|
||||||
{ok, _} = emqx_exhook_mgr:update_config(Path, {add, DisableAnd}),
|
{ok, _} = emqx_exhook_mgr:update_config(Path, {add, DisableAnd}),
|
||||||
|
|
||||||
{ok, _} = emqx_exhook_mgr:update_config(Path, {delete, <<"error">>}),
|
{ok, _} = emqx_exhook_mgr:update_config(Path, {delete, <<"error">>}),
|
||||||
|
@ -179,10 +195,12 @@ t_metrics(_) ->
|
||||||
|
|
||||||
t_handler(_) ->
|
t_handler(_) ->
|
||||||
%% connect
|
%% connect
|
||||||
{ok, C} = emqtt:start_link([{host, "localhost"},
|
{ok, C} = emqtt:start_link([
|
||||||
{port, 1883},
|
{host, "localhost"},
|
||||||
{username, <<"gooduser">>},
|
{port, 1883},
|
||||||
{clientid, <<"exhook_gooduser">>}]),
|
{username, <<"gooduser">>},
|
||||||
|
{clientid, <<"exhook_gooduser">>}
|
||||||
|
]),
|
||||||
{ok, _} = emqtt:connect(C),
|
{ok, _} = emqtt:connect(C),
|
||||||
|
|
||||||
%% pub/sub
|
%% pub/sub
|
||||||
|
@ -212,13 +230,14 @@ t_handler(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_simulated_handler(_) ->
|
t_simulated_handler(_) ->
|
||||||
ClientInfo = #{clientid => <<"user-id-1">>,
|
ClientInfo = #{
|
||||||
username => <<"usera">>,
|
clientid => <<"user-id-1">>,
|
||||||
peerhost => {127,0,0,1},
|
username => <<"usera">>,
|
||||||
sockport => 1883,
|
peerhost => {127, 0, 0, 1},
|
||||||
protocol => mqtt,
|
sockport => 1883,
|
||||||
mountpoint => undefined
|
protocol => mqtt,
|
||||||
},
|
mountpoint => undefined
|
||||||
|
},
|
||||||
%% resume/takeover
|
%% resume/takeover
|
||||||
ok = emqx_exhook_handler:on_session_resumed(ClientInfo, undefined),
|
ok = emqx_exhook_handler:on_session_resumed(ClientInfo, undefined),
|
||||||
ok = emqx_exhook_handler:on_session_discarded(ClientInfo, undefined),
|
ok = emqx_exhook_handler:on_session_discarded(ClientInfo, undefined),
|
||||||
|
@ -232,36 +251,38 @@ t_misc_test(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_get_basic_usage_info(_Config) ->
|
t_get_basic_usage_info(_Config) ->
|
||||||
#{ num_servers := NumServers
|
#{
|
||||||
, servers := Servers
|
num_servers := NumServers,
|
||||||
} = emqx_exhook:get_basic_usage_info(),
|
servers := Servers
|
||||||
|
} = emqx_exhook:get_basic_usage_info(),
|
||||||
?assertEqual(1, NumServers),
|
?assertEqual(1, NumServers),
|
||||||
?assertMatch([_], Servers),
|
?assertMatch([_], Servers),
|
||||||
[#{driver := Driver, hooks := Hooks}] = Servers,
|
[#{driver := Driver, hooks := Hooks}] = Servers,
|
||||||
?assertEqual(grpc, Driver),
|
?assertEqual(grpc, Driver),
|
||||||
?assertEqual(
|
?assertEqual(
|
||||||
[
|
[
|
||||||
'client.authenticate',
|
'client.authenticate',
|
||||||
'client.authorize',
|
'client.authorize',
|
||||||
'client.connack',
|
'client.connack',
|
||||||
'client.connect',
|
'client.connect',
|
||||||
'client.connected',
|
'client.connected',
|
||||||
'client.disconnected',
|
'client.disconnected',
|
||||||
'client.subscribe',
|
'client.subscribe',
|
||||||
'client.unsubscribe',
|
'client.unsubscribe',
|
||||||
'message.acked',
|
'message.acked',
|
||||||
'message.delivered',
|
'message.delivered',
|
||||||
'message.dropped',
|
'message.dropped',
|
||||||
'message.publish',
|
'message.publish',
|
||||||
'session.created',
|
'session.created',
|
||||||
'session.discarded',
|
'session.discarded',
|
||||||
'session.resumed',
|
'session.resumed',
|
||||||
'session.subscribed',
|
'session.subscribed',
|
||||||
'session.takenover',
|
'session.takenover',
|
||||||
'session.terminated',
|
'session.terminated',
|
||||||
'session.unsubscribed'
|
'session.unsubscribed'
|
||||||
],
|
],
|
||||||
lists:sort(Hooks)).
|
lists:sort(Hooks)
|
||||||
|
).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Utils
|
%% Utils
|
||||||
|
@ -276,14 +297,17 @@ unmeck_print() ->
|
||||||
meck:unload(emqx_ctl).
|
meck:unload(emqx_ctl).
|
||||||
|
|
||||||
loaded_exhook_hookpoints() ->
|
loaded_exhook_hookpoints() ->
|
||||||
lists:filtermap(fun(E) ->
|
lists:filtermap(
|
||||||
Name = element(2, E),
|
fun(E) ->
|
||||||
Callbacks = element(3, E),
|
Name = element(2, E),
|
||||||
case lists:any(fun is_exhook_callback/1, Callbacks) of
|
Callbacks = element(3, E),
|
||||||
true -> {true, Name};
|
case lists:any(fun is_exhook_callback/1, Callbacks) of
|
||||||
_ -> false
|
true -> {true, Name};
|
||||||
end
|
_ -> false
|
||||||
end, ets:tab2list(emqx_hooks)).
|
end
|
||||||
|
end,
|
||||||
|
ets:tab2list(emqx_hooks)
|
||||||
|
).
|
||||||
|
|
||||||
is_exhook_callback(Cb) ->
|
is_exhook_callback(Cb) ->
|
||||||
Action = element(2, Cb),
|
Action = element(2, Cb),
|
||||||
|
|
|
@ -26,19 +26,29 @@
|
||||||
-define(BASE_PATH, "api").
|
-define(BASE_PATH, "api").
|
||||||
-define(CLUSTER_RPC_SHARD, emqx_cluster_rpc_shard).
|
-define(CLUSTER_RPC_SHARD, emqx_cluster_rpc_shard).
|
||||||
|
|
||||||
-define(CONF_DEFAULT, <<"
|
-define(CONF_DEFAULT, <<
|
||||||
exhook {
|
"\n"
|
||||||
servers =
|
"exhook {\n"
|
||||||
[ { name = default,
|
" servers =\n"
|
||||||
url = \"http://127.0.0.1:9000\"
|
" [ { name = default,\n"
|
||||||
}
|
" url = \"http://127.0.0.1:9000\"\n"
|
||||||
]
|
" }\n"
|
||||||
}
|
" ]\n"
|
||||||
">>).
|
"}\n"
|
||||||
|
>>).
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[ t_list, t_get, t_add, t_move_front, t_move_rear
|
[
|
||||||
, t_move_before, t_move_after, t_delete, t_hooks, t_update
|
t_list,
|
||||||
|
t_get,
|
||||||
|
t_add,
|
||||||
|
t_move_front,
|
||||||
|
t_move_rear,
|
||||||
|
t_move_before,
|
||||||
|
t_move_after,
|
||||||
|
t_delete,
|
||||||
|
t_hooks,
|
||||||
|
t_update
|
||||||
].
|
].
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
|
@ -71,7 +81,6 @@ init_per_testcase(t_add, Config) ->
|
||||||
_ = emqx_exhook_demo_svr:start(<<"test1">>, 9001),
|
_ = emqx_exhook_demo_svr:start(<<"test1">>, 9001),
|
||||||
timer:sleep(200),
|
timer:sleep(200),
|
||||||
Config;
|
Config;
|
||||||
|
|
||||||
init_per_testcase(_, Config) ->
|
init_per_testcase(_, Config) ->
|
||||||
{ok, _} = emqx_cluster_rpc:start_link(),
|
{ok, _} = emqx_cluster_rpc:start_link(),
|
||||||
timer:sleep(200),
|
timer:sleep(200),
|
||||||
|
@ -79,7 +88,8 @@ init_per_testcase(_, Config) ->
|
||||||
|
|
||||||
end_per_testcase(_, Config) ->
|
end_per_testcase(_, Config) ->
|
||||||
case erlang:whereis(node()) of
|
case erlang:whereis(node()) of
|
||||||
undefined -> ok;
|
undefined ->
|
||||||
|
ok;
|
||||||
P ->
|
P ->
|
||||||
erlang:unlink(P),
|
erlang:unlink(P),
|
||||||
erlang:exit(P, kill)
|
erlang:exit(P, kill)
|
||||||
|
@ -87,108 +97,168 @@ end_per_testcase(_, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
t_list(_) ->
|
t_list(_) ->
|
||||||
{ok, Data} = request_api(get, api_path(["exhooks"]), "",
|
{ok, Data} = request_api(
|
||||||
auth_header_()),
|
get,
|
||||||
|
api_path(["exhooks"]),
|
||||||
|
"",
|
||||||
|
auth_header_()
|
||||||
|
),
|
||||||
|
|
||||||
List = decode_json(Data),
|
List = decode_json(Data),
|
||||||
?assertEqual(1, length(List)),
|
?assertEqual(1, length(List)),
|
||||||
|
|
||||||
[Svr] = List,
|
[Svr] = List,
|
||||||
|
|
||||||
?assertMatch(#{name := <<"default">>,
|
?assertMatch(
|
||||||
metrics := _,
|
#{
|
||||||
node_metrics := _,
|
name := <<"default">>,
|
||||||
node_status := _,
|
metrics := _,
|
||||||
hooks := _
|
node_metrics := _,
|
||||||
}, Svr).
|
node_status := _,
|
||||||
|
hooks := _
|
||||||
|
},
|
||||||
|
Svr
|
||||||
|
).
|
||||||
|
|
||||||
t_get(_) ->
|
t_get(_) ->
|
||||||
{ok, Data} = request_api(get, api_path(["exhooks", "default"]), "",
|
{ok, Data} = request_api(
|
||||||
auth_header_()),
|
get,
|
||||||
|
api_path(["exhooks", "default"]),
|
||||||
|
"",
|
||||||
|
auth_header_()
|
||||||
|
),
|
||||||
|
|
||||||
Svr = decode_json(Data),
|
Svr = decode_json(Data),
|
||||||
|
|
||||||
?assertMatch(#{name := <<"default">>,
|
?assertMatch(
|
||||||
metrics := _,
|
#{
|
||||||
node_metrics := _,
|
name := <<"default">>,
|
||||||
node_status := _,
|
metrics := _,
|
||||||
hooks := _
|
node_metrics := _,
|
||||||
}, Svr).
|
node_status := _,
|
||||||
|
hooks := _
|
||||||
|
},
|
||||||
|
Svr
|
||||||
|
).
|
||||||
|
|
||||||
t_add(Cfg) ->
|
t_add(Cfg) ->
|
||||||
Template = proplists:get_value(template, Cfg),
|
Template = proplists:get_value(template, Cfg),
|
||||||
Instance = Template#{name => <<"test1">>,
|
Instance = Template#{
|
||||||
url => "http://127.0.0.1:9001"
|
name => <<"test1">>,
|
||||||
},
|
url => "http://127.0.0.1:9001"
|
||||||
{ok, Data} = request_api(post, api_path(["exhooks"]), "",
|
},
|
||||||
auth_header_(), Instance),
|
{ok, Data} = request_api(
|
||||||
|
post,
|
||||||
|
api_path(["exhooks"]),
|
||||||
|
"",
|
||||||
|
auth_header_(),
|
||||||
|
Instance
|
||||||
|
),
|
||||||
|
|
||||||
Svr = decode_json(Data),
|
Svr = decode_json(Data),
|
||||||
|
|
||||||
?assertMatch(#{name := <<"test1">>,
|
?assertMatch(
|
||||||
metrics := _,
|
#{
|
||||||
node_metrics := _,
|
name := <<"test1">>,
|
||||||
node_status := _,
|
metrics := _,
|
||||||
hooks := _}, Svr),
|
node_metrics := _,
|
||||||
|
node_status := _,
|
||||||
|
hooks := _
|
||||||
|
},
|
||||||
|
Svr
|
||||||
|
),
|
||||||
|
|
||||||
?assertMatch([<<"default">>, <<"test1">>], emqx_exhook_mgr:running()).
|
?assertMatch([<<"default">>, <<"test1">>], emqx_exhook_mgr:running()).
|
||||||
|
|
||||||
t_move_front(_) ->
|
t_move_front(_) ->
|
||||||
Result = request_api(post, api_path(["exhooks", "default", "move"]), "",
|
Result = request_api(
|
||||||
auth_header_(),
|
post,
|
||||||
#{position => <<"front">>}),
|
api_path(["exhooks", "default", "move"]),
|
||||||
|
"",
|
||||||
|
auth_header_(),
|
||||||
|
#{position => <<"front">>}
|
||||||
|
),
|
||||||
|
|
||||||
?assertMatch({ok, <<>>}, Result),
|
?assertMatch({ok, <<>>}, Result),
|
||||||
?assertMatch([<<"default">>, <<"test1">>], emqx_exhook_mgr:running()).
|
?assertMatch([<<"default">>, <<"test1">>], emqx_exhook_mgr:running()).
|
||||||
|
|
||||||
t_move_rear(_) ->
|
t_move_rear(_) ->
|
||||||
Result = request_api(post, api_path(["exhooks", "default", "move"]), "",
|
Result = request_api(
|
||||||
auth_header_(),
|
post,
|
||||||
#{position => <<"rear">>}),
|
api_path(["exhooks", "default", "move"]),
|
||||||
|
"",
|
||||||
|
auth_header_(),
|
||||||
|
#{position => <<"rear">>}
|
||||||
|
),
|
||||||
|
|
||||||
?assertMatch({ok, <<>>}, Result),
|
?assertMatch({ok, <<>>}, Result),
|
||||||
?assertMatch([<<"test1">>, <<"default">>], emqx_exhook_mgr:running()).
|
?assertMatch([<<"test1">>, <<"default">>], emqx_exhook_mgr:running()).
|
||||||
|
|
||||||
t_move_before(_) ->
|
t_move_before(_) ->
|
||||||
Result = request_api(post, api_path(["exhooks", "default", "move"]), "",
|
Result = request_api(
|
||||||
auth_header_(),
|
post,
|
||||||
#{position => <<"before:test1">>}),
|
api_path(["exhooks", "default", "move"]),
|
||||||
|
"",
|
||||||
|
auth_header_(),
|
||||||
|
#{position => <<"before:test1">>}
|
||||||
|
),
|
||||||
|
|
||||||
?assertMatch({ok, <<>>}, Result),
|
?assertMatch({ok, <<>>}, Result),
|
||||||
?assertMatch([<<"default">>, <<"test1">>], emqx_exhook_mgr:running()).
|
?assertMatch([<<"default">>, <<"test1">>], emqx_exhook_mgr:running()).
|
||||||
|
|
||||||
t_move_after(_) ->
|
t_move_after(_) ->
|
||||||
Result = request_api(post, api_path(["exhooks", "default", "move"]), "",
|
Result = request_api(
|
||||||
auth_header_(),
|
post,
|
||||||
#{position => <<"after:test1">>}),
|
api_path(["exhooks", "default", "move"]),
|
||||||
|
"",
|
||||||
|
auth_header_(),
|
||||||
|
#{position => <<"after:test1">>}
|
||||||
|
),
|
||||||
|
|
||||||
?assertMatch({ok, <<>>}, Result),
|
?assertMatch({ok, <<>>}, Result),
|
||||||
?assertMatch([<<"test1">>, <<"default">>], emqx_exhook_mgr:running()).
|
?assertMatch([<<"test1">>, <<"default">>], emqx_exhook_mgr:running()).
|
||||||
|
|
||||||
t_delete(_) ->
|
t_delete(_) ->
|
||||||
Result = request_api(delete, api_path(["exhooks", "test1"]), "",
|
Result = request_api(
|
||||||
auth_header_()),
|
delete,
|
||||||
|
api_path(["exhooks", "test1"]),
|
||||||
|
"",
|
||||||
|
auth_header_()
|
||||||
|
),
|
||||||
|
|
||||||
?assertMatch({ok, <<>>}, Result),
|
?assertMatch({ok, <<>>}, Result),
|
||||||
?assertMatch([<<"default">>], emqx_exhook_mgr:running()).
|
?assertMatch([<<"default">>], emqx_exhook_mgr:running()).
|
||||||
|
|
||||||
t_hooks(_Cfg) ->
|
t_hooks(_Cfg) ->
|
||||||
{ok, Data} = request_api(get, api_path(["exhooks", "default", "hooks"]), "",
|
{ok, Data} = request_api(
|
||||||
auth_header_()),
|
get,
|
||||||
|
api_path(["exhooks", "default", "hooks"]),
|
||||||
|
"",
|
||||||
|
auth_header_()
|
||||||
|
),
|
||||||
|
|
||||||
[Hook1 | _] = decode_json(Data),
|
[Hook1 | _] = decode_json(Data),
|
||||||
|
|
||||||
?assertMatch(#{name := _,
|
?assertMatch(
|
||||||
params := _,
|
#{
|
||||||
metrics := _,
|
name := _,
|
||||||
node_metrics := _
|
params := _,
|
||||||
}, Hook1).
|
metrics := _,
|
||||||
|
node_metrics := _
|
||||||
|
},
|
||||||
|
Hook1
|
||||||
|
).
|
||||||
|
|
||||||
t_update(Cfg) ->
|
t_update(Cfg) ->
|
||||||
Template = proplists:get_value(template, Cfg),
|
Template = proplists:get_value(template, Cfg),
|
||||||
Instance = Template#{enable => false},
|
Instance = Template#{enable => false},
|
||||||
{ok, <<>>} = request_api(put, api_path(["exhooks", "default"]), "",
|
{ok, <<>>} = request_api(
|
||||||
auth_header_(), Instance),
|
put,
|
||||||
|
api_path(["exhooks", "default"]),
|
||||||
|
"",
|
||||||
|
auth_header_(),
|
||||||
|
Instance
|
||||||
|
),
|
||||||
|
|
||||||
?assertMatch([], emqx_exhook_mgr:running()).
|
?assertMatch([], emqx_exhook_mgr:running()).
|
||||||
|
|
||||||
|
@ -203,24 +273,27 @@ request_api(Method, Url, QueryParams, Auth) ->
|
||||||
request_api(Method, Url, QueryParams, Auth, []).
|
request_api(Method, Url, QueryParams, Auth, []).
|
||||||
|
|
||||||
request_api(Method, Url, QueryParams, Auth, []) ->
|
request_api(Method, Url, QueryParams, Auth, []) ->
|
||||||
NewUrl = case QueryParams of
|
NewUrl =
|
||||||
"" -> Url;
|
case QueryParams of
|
||||||
_ -> Url ++ "?" ++ QueryParams
|
"" -> Url;
|
||||||
end,
|
_ -> Url ++ "?" ++ QueryParams
|
||||||
|
end,
|
||||||
do_request_api(Method, {NewUrl, [Auth]});
|
do_request_api(Method, {NewUrl, [Auth]});
|
||||||
request_api(Method, Url, QueryParams, Auth, Body) ->
|
request_api(Method, Url, QueryParams, Auth, Body) ->
|
||||||
NewUrl = case QueryParams of
|
NewUrl =
|
||||||
"" -> Url;
|
case QueryParams of
|
||||||
_ -> Url ++ "?" ++ QueryParams
|
"" -> Url;
|
||||||
end,
|
_ -> Url ++ "?" ++ QueryParams
|
||||||
|
end,
|
||||||
do_request_api(Method, {NewUrl, [Auth], "application/json", emqx_json:encode(Body)}).
|
do_request_api(Method, {NewUrl, [Auth], "application/json", emqx_json:encode(Body)}).
|
||||||
|
|
||||||
do_request_api(Method, Request)->
|
do_request_api(Method, Request) ->
|
||||||
case httpc:request(Method, Request, [], [{body_format, binary}]) of
|
case httpc:request(Method, Request, [], [{body_format, binary}]) of
|
||||||
{error, socket_closed_remotely} ->
|
{error, socket_closed_remotely} ->
|
||||||
{error, socket_closed_remotely};
|
{error, socket_closed_remotely};
|
||||||
{ok, {{"HTTP/1.1", Code, _}, _, Return} }
|
{ok, {{"HTTP/1.1", Code, _}, _, Return}} when
|
||||||
when Code =:= 200 orelse Code =:= 204 orelse Code =:= 201 ->
|
Code =:= 200 orelse Code =:= 204 orelse Code =:= 201
|
||||||
|
->
|
||||||
{ok, Return};
|
{ok, Return};
|
||||||
{ok, {Reason, _, _}} ->
|
{ok, {Reason, _, _}} ->
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
|
@ -232,8 +305,8 @@ auth_header_() ->
|
||||||
auth_header_(binary_to_list(AppId), binary_to_list(AppSecret)).
|
auth_header_(binary_to_list(AppId), binary_to_list(AppSecret)).
|
||||||
|
|
||||||
auth_header_(User, Pass) ->
|
auth_header_(User, Pass) ->
|
||||||
Encoded = base64:encode_to_string(lists:append([User,":",Pass])),
|
Encoded = base64:encode_to_string(lists:append([User, ":", Pass])),
|
||||||
{"Authorization","Basic " ++ Encoded}.
|
{"Authorization", "Basic " ++ Encoded}.
|
||||||
|
|
||||||
api_path(Parts)->
|
api_path(Parts) ->
|
||||||
?HOST ++ filename:join([?BASE_PATH, ?API_VERSION] ++ Parts).
|
?HOST ++ filename:join([?BASE_PATH, ?API_VERSION] ++ Parts).
|
||||||
|
|
|
@ -19,37 +19,39 @@
|
||||||
-behaviour(emqx_exhook_v_1_hook_provider_bhvr).
|
-behaviour(emqx_exhook_v_1_hook_provider_bhvr).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
-export([ start/0
|
-export([
|
||||||
, start/2
|
start/0,
|
||||||
, stop/0
|
start/2,
|
||||||
, stop/1
|
stop/0,
|
||||||
, take/0
|
stop/1,
|
||||||
, in/1
|
take/0,
|
||||||
]).
|
in/1
|
||||||
|
]).
|
||||||
|
|
||||||
%% gRPC server HookProvider callbacks
|
%% gRPC server HookProvider callbacks
|
||||||
-export([ on_provider_loaded/2
|
-export([
|
||||||
, on_provider_unloaded/2
|
on_provider_loaded/2,
|
||||||
, on_client_connect/2
|
on_provider_unloaded/2,
|
||||||
, on_client_connack/2
|
on_client_connect/2,
|
||||||
, on_client_connected/2
|
on_client_connack/2,
|
||||||
, on_client_disconnected/2
|
on_client_connected/2,
|
||||||
, on_client_authenticate/2
|
on_client_disconnected/2,
|
||||||
, on_client_authorize/2
|
on_client_authenticate/2,
|
||||||
, on_client_subscribe/2
|
on_client_authorize/2,
|
||||||
, on_client_unsubscribe/2
|
on_client_subscribe/2,
|
||||||
, on_session_created/2
|
on_client_unsubscribe/2,
|
||||||
, on_session_subscribed/2
|
on_session_created/2,
|
||||||
, on_session_unsubscribed/2
|
on_session_subscribed/2,
|
||||||
, on_session_resumed/2
|
on_session_unsubscribed/2,
|
||||||
, on_session_discarded/2
|
on_session_resumed/2,
|
||||||
, on_session_takenover/2
|
on_session_discarded/2,
|
||||||
, on_session_terminated/2
|
on_session_takenover/2,
|
||||||
, on_message_publish/2
|
on_session_terminated/2,
|
||||||
, on_message_delivered/2
|
on_message_publish/2,
|
||||||
, on_message_dropped/2
|
on_message_delivered/2,
|
||||||
, on_message_acked/2
|
on_message_dropped/2,
|
||||||
]).
|
on_message_acked/2
|
||||||
|
]).
|
||||||
|
|
||||||
-define(PORT, 9000).
|
-define(PORT, 9000).
|
||||||
-define(NAME, ?MODULE).
|
-define(NAME, ?MODULE).
|
||||||
|
@ -62,7 +64,7 @@ start() ->
|
||||||
start(?NAME, ?PORT).
|
start(?NAME, ?PORT).
|
||||||
|
|
||||||
start(Name, Port) ->
|
start(Name, Port) ->
|
||||||
Pid = spawn(fun() -> mgr_main(Name, Port) end),
|
Pid = spawn(fun() -> mgr_main(Name, Port) end),
|
||||||
register(to_atom_name(Name), Pid),
|
register(to_atom_name(Name), Pid),
|
||||||
{ok, Pid}.
|
{ok, Pid}.
|
||||||
|
|
||||||
|
@ -75,17 +77,20 @@ stop(Name) ->
|
||||||
|
|
||||||
take() ->
|
take() ->
|
||||||
to_atom_name(?NAME) ! {take, self()},
|
to_atom_name(?NAME) ! {take, self()},
|
||||||
receive {value, V} -> V
|
receive
|
||||||
after 5000 -> error(timeout) end.
|
{value, V} -> V
|
||||||
|
after 5000 -> error(timeout)
|
||||||
|
end.
|
||||||
|
|
||||||
in({FunName, Req}) ->
|
in({FunName, Req}) ->
|
||||||
to_atom_name(?NAME) ! {in, FunName, Req}.
|
to_atom_name(?NAME) ! {in, FunName, Req}.
|
||||||
|
|
||||||
mgr_main(Name, Port) ->
|
mgr_main(Name, Port) ->
|
||||||
application:ensure_all_started(grpc),
|
application:ensure_all_started(grpc),
|
||||||
Services = #{protos => [emqx_exhook_pb],
|
Services = #{
|
||||||
services => #{'emqx.exhook.v1.HookProvider' => emqx_exhook_demo_svr}
|
protos => [emqx_exhook_pb],
|
||||||
},
|
services => #{'emqx.exhook.v1.HookProvider' => emqx_exhook_demo_svr}
|
||||||
|
},
|
||||||
Options = [],
|
Options = [],
|
||||||
Svr = grpc:start_server(Name, Port, Services, Options),
|
Svr = grpc:start_server(Name, Port, Services, Options),
|
||||||
mgr_loop([Svr, queue:new(), queue:new()]).
|
mgr_loop([Svr, queue:new(), queue:new()]).
|
||||||
|
@ -103,9 +108,12 @@ mgr_loop([Svr, Q, Takes]) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
reply(Q1, Q2) ->
|
reply(Q1, Q2) ->
|
||||||
case queue:len(Q1) =:= 0 orelse
|
case
|
||||||
queue:len(Q2) =:= 0 of
|
queue:len(Q1) =:= 0 orelse
|
||||||
true -> {Q1, Q2};
|
queue:len(Q2) =:= 0
|
||||||
|
of
|
||||||
|
true ->
|
||||||
|
{Q1, Q2};
|
||||||
_ ->
|
_ ->
|
||||||
{{value, {Name, V}}, NQ1} = queue:out(Q1),
|
{{value, {Name, V}}, NQ1} = queue:out(Q1),
|
||||||
{{value, From}, NQ2} = queue:out(Q2),
|
{{value, From}, NQ2} = queue:out(Q2),
|
||||||
|
@ -115,7 +123,6 @@ reply(Q1, Q2) ->
|
||||||
|
|
||||||
to_atom_name(Name) when is_atom(Name) ->
|
to_atom_name(Name) when is_atom(Name) ->
|
||||||
Name;
|
Name;
|
||||||
|
|
||||||
to_atom_name(Name) ->
|
to_atom_name(Name) ->
|
||||||
erlang:binary_to_atom(Name).
|
erlang:binary_to_atom(Name).
|
||||||
|
|
||||||
|
@ -123,240 +130,286 @@ to_atom_name(Name) ->
|
||||||
%% callbacks
|
%% callbacks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec on_provider_loaded(emqx_exhook_pb:provider_loaded_request(), grpc:metadata())
|
-spec on_provider_loaded(emqx_exhook_pb:provider_loaded_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:loaded_response(), grpc:metadata()}
|
{ok, emqx_exhook_pb:loaded_response(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
|
||||||
on_provider_loaded(Req, Md) ->
|
on_provider_loaded(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{hooks => [
|
{ok,
|
||||||
#{name => <<"client.connect">>},
|
#{
|
||||||
#{name => <<"client.connack">>},
|
hooks => [
|
||||||
#{name => <<"client.connected">>},
|
#{name => <<"client.connect">>},
|
||||||
#{name => <<"client.disconnected">>},
|
#{name => <<"client.connack">>},
|
||||||
#{name => <<"client.authenticate">>},
|
#{name => <<"client.connected">>},
|
||||||
#{name => <<"client.authorize">>},
|
#{name => <<"client.disconnected">>},
|
||||||
#{name => <<"client.subscribe">>},
|
#{name => <<"client.authenticate">>},
|
||||||
#{name => <<"client.unsubscribe">>},
|
#{name => <<"client.authorize">>},
|
||||||
#{name => <<"session.created">>},
|
#{name => <<"client.subscribe">>},
|
||||||
#{name => <<"session.subscribed">>},
|
#{name => <<"client.unsubscribe">>},
|
||||||
#{name => <<"session.unsubscribed">>},
|
#{name => <<"session.created">>},
|
||||||
#{name => <<"session.resumed">>},
|
#{name => <<"session.subscribed">>},
|
||||||
#{name => <<"session.discarded">>},
|
#{name => <<"session.unsubscribed">>},
|
||||||
#{name => <<"session.takenover">>},
|
#{name => <<"session.resumed">>},
|
||||||
#{name => <<"session.terminated">>},
|
#{name => <<"session.discarded">>},
|
||||||
#{name => <<"message.publish">>},
|
#{name => <<"session.takenover">>},
|
||||||
#{name => <<"message.delivered">>},
|
#{name => <<"session.terminated">>},
|
||||||
#{name => <<"message.acked">>},
|
#{name => <<"message.publish">>},
|
||||||
#{name => <<"message.dropped">>}]}, Md}.
|
#{name => <<"message.delivered">>},
|
||||||
-spec on_provider_unloaded(emqx_exhook_pb:provider_unloaded_request(), grpc:metadata())
|
#{name => <<"message.acked">>},
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
#{name => <<"message.dropped">>}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
]
|
||||||
|
},
|
||||||
|
Md}.
|
||||||
|
-spec on_provider_unloaded(emqx_exhook_pb:provider_unloaded_request(), grpc:metadata()) ->
|
||||||
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_provider_unloaded(Req, Md) ->
|
on_provider_unloaded(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_client_connect(emqx_exhook_pb:client_connect_request(), grpc:metadata())
|
-spec on_client_connect(emqx_exhook_pb:client_connect_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_client_connect(Req, Md) ->
|
on_client_connect(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_client_connack(emqx_exhook_pb:client_connack_request(), grpc:metadata())
|
-spec on_client_connack(emqx_exhook_pb:client_connack_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_client_connack(Req, Md) ->
|
on_client_connack(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_client_connected(emqx_exhook_pb:client_connected_request(), grpc:metadata())
|
-spec on_client_connected(emqx_exhook_pb:client_connected_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_client_connected(Req, Md) ->
|
on_client_connected(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_client_disconnected(emqx_exhook_pb:client_disconnected_request(), grpc:metadata())
|
-spec on_client_disconnected(emqx_exhook_pb:client_disconnected_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_client_disconnected(Req, Md) ->
|
on_client_disconnected(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_client_authenticate(emqx_exhook_pb:client_authenticate_request(), grpc:metadata())
|
-spec on_client_authenticate(emqx_exhook_pb:client_authenticate_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
{ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_client_authenticate(#{clientinfo := #{username := Username}} = Req, Md) ->
|
on_client_authenticate(#{clientinfo := #{username := Username}} = Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
%% some cases for testing
|
%% some cases for testing
|
||||||
case Username of
|
case Username of
|
||||||
<<"baduser">> ->
|
<<"baduser">> ->
|
||||||
{ok, #{type => 'STOP_AND_RETURN',
|
{ok,
|
||||||
value => {bool_result, false}}, Md};
|
#{
|
||||||
|
type => 'STOP_AND_RETURN',
|
||||||
|
value => {bool_result, false}
|
||||||
|
},
|
||||||
|
Md};
|
||||||
<<"gooduser">> ->
|
<<"gooduser">> ->
|
||||||
{ok, #{type => 'STOP_AND_RETURN',
|
{ok,
|
||||||
value => {bool_result, true}}, Md};
|
#{
|
||||||
|
type => 'STOP_AND_RETURN',
|
||||||
|
value => {bool_result, true}
|
||||||
|
},
|
||||||
|
Md};
|
||||||
<<"normaluser">> ->
|
<<"normaluser">> ->
|
||||||
{ok, #{type => 'CONTINUE',
|
{ok,
|
||||||
value => {bool_result, true}}, Md};
|
#{
|
||||||
|
type => 'CONTINUE',
|
||||||
|
value => {bool_result, true}
|
||||||
|
},
|
||||||
|
Md};
|
||||||
_ ->
|
_ ->
|
||||||
{ok, #{type => 'IGNORE'}, Md}
|
{ok, #{type => 'IGNORE'}, Md}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec on_client_authorize(emqx_exhook_pb:client_authorize_request(), grpc:metadata())
|
-spec on_client_authorize(emqx_exhook_pb:client_authorize_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
{ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_client_authorize(#{clientinfo := #{username := Username}} = Req, Md) ->
|
on_client_authorize(#{clientinfo := #{username := Username}} = Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
%% some cases for testing
|
%% some cases for testing
|
||||||
case Username of
|
case Username of
|
||||||
<<"baduser">> ->
|
<<"baduser">> ->
|
||||||
{ok, #{type => 'STOP_AND_RETURN',
|
{ok,
|
||||||
value => {bool_result, false}}, Md};
|
#{
|
||||||
|
type => 'STOP_AND_RETURN',
|
||||||
|
value => {bool_result, false}
|
||||||
|
},
|
||||||
|
Md};
|
||||||
<<"gooduser">> ->
|
<<"gooduser">> ->
|
||||||
{ok, #{type => 'STOP_AND_RETURN',
|
{ok,
|
||||||
value => {bool_result, true}}, Md};
|
#{
|
||||||
|
type => 'STOP_AND_RETURN',
|
||||||
|
value => {bool_result, true}
|
||||||
|
},
|
||||||
|
Md};
|
||||||
<<"normaluser">> ->
|
<<"normaluser">> ->
|
||||||
{ok, #{type => 'CONTINUE',
|
{ok,
|
||||||
value => {bool_result, true}}, Md};
|
#{
|
||||||
|
type => 'CONTINUE',
|
||||||
|
value => {bool_result, true}
|
||||||
|
},
|
||||||
|
Md};
|
||||||
_ ->
|
_ ->
|
||||||
{ok, #{type => 'IGNORE'}, Md}
|
{ok, #{type => 'IGNORE'}, Md}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec on_client_subscribe(emqx_exhook_pb:client_subscribe_request(), grpc:metadata())
|
-spec on_client_subscribe(emqx_exhook_pb:client_subscribe_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_client_subscribe(Req, Md) ->
|
on_client_subscribe(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_client_unsubscribe(emqx_exhook_pb:client_unsubscribe_request(), grpc:metadata())
|
-spec on_client_unsubscribe(emqx_exhook_pb:client_unsubscribe_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_client_unsubscribe(Req, Md) ->
|
on_client_unsubscribe(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_session_created(emqx_exhook_pb:session_created_request(), grpc:metadata())
|
-spec on_session_created(emqx_exhook_pb:session_created_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_session_created(Req, Md) ->
|
on_session_created(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_session_subscribed(emqx_exhook_pb:session_subscribed_request(), grpc:metadata())
|
-spec on_session_subscribed(emqx_exhook_pb:session_subscribed_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_session_subscribed(Req, Md) ->
|
on_session_subscribed(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_session_unsubscribed(emqx_exhook_pb:session_unsubscribed_request(), grpc:metadata())
|
-spec on_session_unsubscribed(emqx_exhook_pb:session_unsubscribed_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_session_unsubscribed(Req, Md) ->
|
on_session_unsubscribed(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_session_resumed(emqx_exhook_pb:session_resumed_request(), grpc:metadata())
|
-spec on_session_resumed(emqx_exhook_pb:session_resumed_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_session_resumed(Req, Md) ->
|
on_session_resumed(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_session_discarded(emqx_exhook_pb:session_discarded_request(), grpc:metadata())
|
-spec on_session_discarded(emqx_exhook_pb:session_discarded_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_session_discarded(Req, Md) ->
|
on_session_discarded(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_session_takenover(emqx_exhook_pb:session_takenover_request(), grpc:metadata())
|
-spec on_session_takenover(emqx_exhook_pb:session_takenover_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_session_takenover(Req, Md) ->
|
on_session_takenover(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_session_terminated(emqx_exhook_pb:session_terminated_request(), grpc:metadata())
|
-spec on_session_terminated(emqx_exhook_pb:session_terminated_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_session_terminated(Req, Md) ->
|
on_session_terminated(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_message_publish(emqx_exhook_pb:message_publish_request(), grpc:metadata())
|
-spec on_message_publish(emqx_exhook_pb:message_publish_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
{ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_message_publish(#{message := #{from := From} = Msg} = Req, Md) ->
|
on_message_publish(#{message := #{from := From} = Msg} = Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
%% some cases for testing
|
%% some cases for testing
|
||||||
case From of
|
case From of
|
||||||
<<"baduser">> ->
|
<<"baduser">> ->
|
||||||
NMsg = deny(Msg#{qos => 0,
|
NMsg = deny(Msg#{
|
||||||
topic => <<"">>,
|
qos => 0,
|
||||||
payload => <<"">>
|
topic => <<"">>,
|
||||||
}),
|
payload => <<"">>
|
||||||
{ok, #{type => 'STOP_AND_RETURN',
|
}),
|
||||||
value => {message, NMsg}}, Md};
|
{ok,
|
||||||
|
#{
|
||||||
|
type => 'STOP_AND_RETURN',
|
||||||
|
value => {message, NMsg}
|
||||||
|
},
|
||||||
|
Md};
|
||||||
<<"gooduser">> ->
|
<<"gooduser">> ->
|
||||||
NMsg = allow(Msg#{topic => From,
|
NMsg = allow(Msg#{
|
||||||
payload => From}),
|
topic => From,
|
||||||
{ok, #{type => 'STOP_AND_RETURN',
|
payload => From
|
||||||
value => {message, NMsg}}, Md};
|
}),
|
||||||
|
{ok,
|
||||||
|
#{
|
||||||
|
type => 'STOP_AND_RETURN',
|
||||||
|
value => {message, NMsg}
|
||||||
|
},
|
||||||
|
Md};
|
||||||
_ ->
|
_ ->
|
||||||
{ok, #{type => 'IGNORE'}, Md}
|
{ok, #{type => 'IGNORE'}, Md}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
deny(Msg) ->
|
deny(Msg) ->
|
||||||
NHeader = maps:put(<<"allow_publish">>, <<"false">>,
|
NHeader = maps:put(
|
||||||
maps:get(headers, Msg, #{})),
|
<<"allow_publish">>,
|
||||||
|
<<"false">>,
|
||||||
|
maps:get(headers, Msg, #{})
|
||||||
|
),
|
||||||
maps:put(headers, NHeader, Msg).
|
maps:put(headers, NHeader, Msg).
|
||||||
|
|
||||||
allow(Msg) ->
|
allow(Msg) ->
|
||||||
NHeader = maps:put(<<"allow_publish">>, <<"true">>,
|
NHeader = maps:put(
|
||||||
maps:get(headers, Msg, #{})),
|
<<"allow_publish">>,
|
||||||
|
<<"true">>,
|
||||||
|
maps:get(headers, Msg, #{})
|
||||||
|
),
|
||||||
maps:put(headers, NHeader, Msg).
|
maps:put(headers, NHeader, Msg).
|
||||||
|
|
||||||
-spec on_message_delivered(emqx_exhook_pb:message_delivered_request(), grpc:metadata())
|
-spec on_message_delivered(emqx_exhook_pb:message_delivered_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_message_delivered(Req, Md) ->
|
on_message_delivered(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_message_dropped(emqx_exhook_pb:message_dropped_request(), grpc:metadata())
|
-spec on_message_dropped(emqx_exhook_pb:message_dropped_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_message_dropped(Req, Md) ->
|
on_message_dropped(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
{ok, #{}, Md}.
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
-spec on_message_acked(emqx_exhook_pb:message_acked_request(), grpc:metadata())
|
-spec on_message_acked(emqx_exhook_pb:message_acked_request(), grpc:metadata()) ->
|
||||||
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
{ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
| {error, grpc_cowboy_h:error_response()}.
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
on_message_acked(Req, Md) ->
|
on_message_acked(Req, Md) ->
|
||||||
?MODULE:in({?FUNCTION_NAME, Req}),
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
|
|
@ -26,19 +26,20 @@
|
||||||
|
|
||||||
-define(TARGET_HOOK, 'message.publish').
|
-define(TARGET_HOOK, 'message.publish').
|
||||||
|
|
||||||
-define(CONF, <<"
|
-define(CONF, <<
|
||||||
exhook {
|
"\n"
|
||||||
servers = [
|
"exhook {\n"
|
||||||
{ name = succed,
|
" servers = [\n"
|
||||||
url = \"http://127.0.0.1:9000\"
|
" { name = succed,\n"
|
||||||
},
|
" url = \"http://127.0.0.1:9000\"\n"
|
||||||
{ name = failed,
|
" },\n"
|
||||||
failed_action = ignore,
|
" { name = failed,\n"
|
||||||
url = \"http://127.0.0.1:9001\"
|
" failed_action = ignore,\n"
|
||||||
},
|
" url = \"http://127.0.0.1:9001\"\n"
|
||||||
]
|
" },\n"
|
||||||
}
|
" ]\n"
|
||||||
">>).
|
"}\n"
|
||||||
|
>>).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Setups
|
%% Setups
|
||||||
|
@ -78,11 +79,11 @@ end_per_testcase(_, Config) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
t_servers_metrics(_Cfg) ->
|
t_servers_metrics(_Cfg) ->
|
||||||
Test = fun(C) ->
|
Test = fun(C) ->
|
||||||
Repeat = fun() ->
|
Repeat = fun() ->
|
||||||
emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0)
|
emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0)
|
||||||
end,
|
end,
|
||||||
repeat(Repeat, 10)
|
repeat(Repeat, 10)
|
||||||
end,
|
end,
|
||||||
with_connection(Test),
|
with_connection(Test),
|
||||||
|
|
||||||
timer:sleep(200),
|
timer:sleep(200),
|
||||||
|
@ -99,54 +100,58 @@ t_servers_metrics(_Cfg) ->
|
||||||
|
|
||||||
t_rate(_) ->
|
t_rate(_) ->
|
||||||
Test = fun(C) ->
|
Test = fun(C) ->
|
||||||
Repeat = fun() ->
|
Repeat = fun() ->
|
||||||
emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0)
|
emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
repeat(Repeat, 5),
|
repeat(Repeat, 5),
|
||||||
timer:sleep(200),
|
timer:sleep(200),
|
||||||
emqx_exhook_metrics:update(timer:seconds(1)),
|
emqx_exhook_metrics:update(timer:seconds(1)),
|
||||||
SM = emqx_exhook_metrics:server_metrics(<<"succed">>),
|
SM = emqx_exhook_metrics:server_metrics(<<"succed">>),
|
||||||
?assertMatch(#{rate := 5, max_rate := 5}, SM),
|
?assertMatch(#{rate := 5, max_rate := 5}, SM),
|
||||||
|
|
||||||
repeat(Repeat, 6),
|
repeat(Repeat, 6),
|
||||||
timer:sleep(200),
|
timer:sleep(200),
|
||||||
emqx_exhook_metrics:update(timer:seconds(1)),
|
emqx_exhook_metrics:update(timer:seconds(1)),
|
||||||
SM2 = emqx_exhook_metrics:server_metrics(<<"succed">>),
|
SM2 = emqx_exhook_metrics:server_metrics(<<"succed">>),
|
||||||
?assertMatch(#{rate := 6, max_rate := 6}, SM2),
|
?assertMatch(#{rate := 6, max_rate := 6}, SM2),
|
||||||
|
|
||||||
repeat(Repeat, 3),
|
repeat(Repeat, 3),
|
||||||
timer:sleep(200),
|
timer:sleep(200),
|
||||||
emqx_exhook_metrics:update(timer:seconds(1)),
|
emqx_exhook_metrics:update(timer:seconds(1)),
|
||||||
SM3 = emqx_exhook_metrics:server_metrics(<<"succed">>),
|
SM3 = emqx_exhook_metrics:server_metrics(<<"succed">>),
|
||||||
?assertMatch(#{rate := 3, max_rate := 6}, SM3)
|
?assertMatch(#{rate := 3, max_rate := 6}, SM3)
|
||||||
end,
|
end,
|
||||||
with_connection(Test),
|
with_connection(Test),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_hooks_metrics(_) ->
|
t_hooks_metrics(_) ->
|
||||||
Test = fun(C) ->
|
Test = fun(C) ->
|
||||||
Repeat = fun() ->
|
Repeat = fun() ->
|
||||||
emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0)
|
emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
repeat(Repeat, 5),
|
repeat(Repeat, 5),
|
||||||
timer:sleep(200),
|
timer:sleep(200),
|
||||||
HM = emqx_exhook_metrics:hooks_metrics(<<"succed">>),
|
HM = emqx_exhook_metrics:hooks_metrics(<<"succed">>),
|
||||||
?assertMatch(#{'message.publish' :=
|
?assertMatch(
|
||||||
#{failed := 0, succeed := 5}}, HM)
|
#{
|
||||||
end,
|
'message.publish' :=
|
||||||
|
#{failed := 0, succeed := 5}
|
||||||
|
},
|
||||||
|
HM
|
||||||
|
)
|
||||||
|
end,
|
||||||
with_connection(Test),
|
with_connection(Test),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_on_server_deleted(_) ->
|
t_on_server_deleted(_) ->
|
||||||
|
|
||||||
Test = fun(C) ->
|
Test = fun(C) ->
|
||||||
Repeat = fun() ->
|
Repeat = fun() ->
|
||||||
emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0)
|
emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0)
|
||||||
end,
|
end,
|
||||||
repeat(Repeat, 10)
|
repeat(Repeat, 10)
|
||||||
end,
|
end,
|
||||||
with_connection(Test),
|
with_connection(Test),
|
||||||
|
|
||||||
timer:sleep(200),
|
timer:sleep(200),
|
||||||
|
@ -165,50 +170,56 @@ clear_metrics() ->
|
||||||
ets:delete_all_objects(?HOOKS_METRICS).
|
ets:delete_all_objects(?HOOKS_METRICS).
|
||||||
|
|
||||||
init_injections(Injects) ->
|
init_injections(Injects) ->
|
||||||
lists:map(fun({Name, _}) ->
|
lists:map(
|
||||||
Str = erlang:atom_to_list(Name),
|
fun({Name, _}) ->
|
||||||
case lists:prefix("on_", Str) of
|
Str = erlang:atom_to_list(Name),
|
||||||
true ->
|
case lists:prefix("on_", Str) of
|
||||||
Action = fun(Req, #{<<"channel">> := SvrName} = Md) ->
|
true ->
|
||||||
case maps:get(?SvrFun(SvrName, Name), Injects, undefined) of
|
Action = fun(Req, #{<<"channel">> := SvrName} = Md) ->
|
||||||
undefined ->
|
case maps:get(?SvrFun(SvrName, Name), Injects, undefined) of
|
||||||
meck:passthrough([Req, Md]);
|
undefined ->
|
||||||
Injection ->
|
meck:passthrough([Req, Md]);
|
||||||
Injection(Req, Md)
|
Injection ->
|
||||||
end
|
Injection(Req, Md)
|
||||||
end,
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
meck:expect(emqx_exhook_demo_svr, Name, Action);
|
meck:expect(emqx_exhook_demo_svr, Name, Action);
|
||||||
_ ->
|
_ ->
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
emqx_exhook_demo_svr:module_info(exports)).
|
emqx_exhook_demo_svr:module_info(exports)
|
||||||
|
).
|
||||||
|
|
||||||
hook_injects() ->
|
hook_injects() ->
|
||||||
#{?SvrFun(<<"failed">>, emqx_exhook_server:hk2func(?TARGET_HOOK)) =>
|
#{
|
||||||
fun(_Req, _Md) ->
|
?SvrFun(<<"failed">>, emqx_exhook_server:hk2func(?TARGET_HOOK)) =>
|
||||||
{error, "Error due to test"}
|
fun(_Req, _Md) ->
|
||||||
end,
|
{error, "Error due to test"}
|
||||||
?SvrFun(<<"failed">>, on_provider_loaded) =>
|
end,
|
||||||
fun(_Req, Md) ->
|
?SvrFun(<<"failed">>, on_provider_loaded) =>
|
||||||
{ok, #{hooks => [#{name => <<"message.publish">>}]}, Md}
|
fun(_Req, Md) ->
|
||||||
end,
|
{ok, #{hooks => [#{name => <<"message.publish">>}]}, Md}
|
||||||
?SvrFun(<<"succed">>, on_provider_loaded) =>
|
end,
|
||||||
fun(_Req, Md) ->
|
?SvrFun(<<"succed">>, on_provider_loaded) =>
|
||||||
{ok, #{hooks => [#{name => <<"message.publish">>}]}, Md}
|
fun(_Req, Md) ->
|
||||||
end
|
{ok, #{hooks => [#{name => <<"message.publish">>}]}, Md}
|
||||||
}.
|
end
|
||||||
|
}.
|
||||||
|
|
||||||
with_connection(Fun) ->
|
with_connection(Fun) ->
|
||||||
{ok, C} = emqtt:start_link([{host, "localhost"},
|
{ok, C} = emqtt:start_link([
|
||||||
{port, 1883},
|
{host, "localhost"},
|
||||||
{username, <<"admin">>},
|
{port, 1883},
|
||||||
{clientid, <<"exhook_tester">>}]),
|
{username, <<"admin">>},
|
||||||
|
{clientid, <<"exhook_tester">>}
|
||||||
|
]),
|
||||||
{ok, _} = emqtt:connect(C),
|
{ok, _} = emqtt:connect(C),
|
||||||
try
|
try
|
||||||
Fun(C)
|
Fun(C)
|
||||||
catch Type:Error:Trace ->
|
catch
|
||||||
|
Type:Error:Trace ->
|
||||||
emqtt:stop(C),
|
emqtt:stop(C),
|
||||||
erlang:raise(Type, Error, Trace)
|
erlang:raise(Type, Error, Trace)
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -19,363 +19,440 @@
|
||||||
-include_lib("proper/include/proper.hrl").
|
-include_lib("proper/include/proper.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
-import(emqx_proper_types,
|
-import(
|
||||||
[ conninfo/0
|
emqx_proper_types,
|
||||||
, clientinfo/0
|
[
|
||||||
, sessioninfo/0
|
conninfo/0,
|
||||||
, message/0
|
clientinfo/0,
|
||||||
, connack_return_code/0
|
sessioninfo/0,
|
||||||
, topictab/0
|
message/0,
|
||||||
, topic/0
|
connack_return_code/0,
|
||||||
, subopts/0
|
topictab/0,
|
||||||
]).
|
topic/0,
|
||||||
|
subopts/0
|
||||||
-define(CONF_DEFAULT, <<"
|
|
||||||
exhook {
|
|
||||||
servers =
|
|
||||||
[ { name = default,
|
|
||||||
url = \"http://127.0.0.1:9000\"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
).
|
||||||
">>).
|
|
||||||
|
-define(CONF_DEFAULT, <<
|
||||||
|
"\n"
|
||||||
|
"exhook {\n"
|
||||||
|
" servers =\n"
|
||||||
|
" [ { name = default,\n"
|
||||||
|
" url = \"http://127.0.0.1:9000\"\n"
|
||||||
|
" }\n"
|
||||||
|
" ]\n"
|
||||||
|
"}\n"
|
||||||
|
>>).
|
||||||
|
|
||||||
-define(ALL(Vars, Types, Exprs),
|
-define(ALL(Vars, Types, Exprs),
|
||||||
?SETUP(fun() ->
|
?SETUP(
|
||||||
|
fun() ->
|
||||||
State = do_setup(),
|
State = do_setup(),
|
||||||
fun() -> do_teardown(State) end
|
fun() -> do_teardown(State) end
|
||||||
end, ?FORALL(Vars, Types, Exprs))).
|
end,
|
||||||
|
?FORALL(Vars, Types, Exprs)
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Properties
|
%% Properties
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
prop_client_connect() ->
|
prop_client_connect() ->
|
||||||
?ALL({ConnInfo, ConnProps},
|
?ALL(
|
||||||
{conninfo(), conn_properties()},
|
{ConnInfo, ConnProps},
|
||||||
begin
|
{conninfo(), conn_properties()},
|
||||||
ok = emqx_hooks:run('client.connect', [ConnInfo, ConnProps]),
|
begin
|
||||||
{'on_client_connect', Resp} = emqx_exhook_demo_svr:take(),
|
ok = emqx_hooks:run('client.connect', [ConnInfo, ConnProps]),
|
||||||
Expected =
|
{'on_client_connect', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
#{props => properties(ConnProps),
|
Expected =
|
||||||
conninfo => from_conninfo(ConnInfo)
|
#{
|
||||||
|
props => properties(ConnProps),
|
||||||
|
conninfo => from_conninfo(ConnInfo)
|
||||||
},
|
},
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_client_connack() ->
|
prop_client_connack() ->
|
||||||
?ALL({ConnInfo, Rc, AckProps},
|
?ALL(
|
||||||
{conninfo(), connack_return_code(), ack_properties()},
|
{ConnInfo, Rc, AckProps},
|
||||||
|
{conninfo(), connack_return_code(), ack_properties()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('client.connack', [ConnInfo, Rc, AckProps]),
|
ok = emqx_hooks:run('client.connack', [ConnInfo, Rc, AckProps]),
|
||||||
{'on_client_connack', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_client_connack', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{props => properties(AckProps),
|
#{
|
||||||
result_code => atom_to_binary(Rc, utf8),
|
props => properties(AckProps),
|
||||||
conninfo => from_conninfo(ConnInfo)
|
result_code => atom_to_binary(Rc, utf8),
|
||||||
},
|
conninfo => from_conninfo(ConnInfo)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_client_authenticate() ->
|
prop_client_authenticate() ->
|
||||||
?ALL({ClientInfo0, AuthResult},
|
?ALL(
|
||||||
{clientinfo(), authresult()},
|
{ClientInfo0, AuthResult},
|
||||||
|
{clientinfo(), authresult()},
|
||||||
begin
|
begin
|
||||||
ClientInfo = inject_magic_into(username, ClientInfo0),
|
ClientInfo = inject_magic_into(username, ClientInfo0),
|
||||||
OutAuthResult = emqx_hooks:run_fold('client.authenticate', [ClientInfo], AuthResult),
|
OutAuthResult = emqx_hooks:run_fold('client.authenticate', [ClientInfo], AuthResult),
|
||||||
ExpectedAuthResult = case maps:get(username, ClientInfo) of
|
ExpectedAuthResult =
|
||||||
<<"baduser">> -> {error, not_authorized};
|
case maps:get(username, ClientInfo) of
|
||||||
<<"gooduser">> -> ok;
|
<<"baduser">> ->
|
||||||
<<"normaluser">> -> ok;
|
{error, not_authorized};
|
||||||
_ -> case AuthResult of
|
<<"gooduser">> ->
|
||||||
ok -> ok;
|
ok;
|
||||||
_ -> {error, not_authorized}
|
<<"normaluser">> ->
|
||||||
end
|
ok;
|
||||||
end,
|
_ ->
|
||||||
|
case AuthResult of
|
||||||
|
ok -> ok;
|
||||||
|
_ -> {error, not_authorized}
|
||||||
|
end
|
||||||
|
end,
|
||||||
?assertEqual(ExpectedAuthResult, OutAuthResult),
|
?assertEqual(ExpectedAuthResult, OutAuthResult),
|
||||||
|
|
||||||
{'on_client_authenticate', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_client_authenticate', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{result => authresult_to_bool(AuthResult),
|
#{
|
||||||
clientinfo => from_clientinfo(ClientInfo)
|
result => authresult_to_bool(AuthResult),
|
||||||
},
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_client_authorize() ->
|
prop_client_authorize() ->
|
||||||
?ALL({ClientInfo0, PubSub, Topic, Result},
|
?ALL(
|
||||||
{clientinfo(), oneof([publish, subscribe]),
|
{ClientInfo0, PubSub, Topic, Result},
|
||||||
topic(), oneof([allow, deny])},
|
{clientinfo(), oneof([publish, subscribe]), topic(), oneof([allow, deny])},
|
||||||
begin
|
begin
|
||||||
ClientInfo = inject_magic_into(username, ClientInfo0),
|
ClientInfo = inject_magic_into(username, ClientInfo0),
|
||||||
OutResult = emqx_hooks:run_fold(
|
OutResult = emqx_hooks:run_fold(
|
||||||
'client.authorize',
|
'client.authorize',
|
||||||
[ClientInfo, PubSub, Topic],
|
[ClientInfo, PubSub, Topic],
|
||||||
Result),
|
Result
|
||||||
ExpectedOutResult = case maps:get(username, ClientInfo) of
|
),
|
||||||
<<"baduser">> -> deny;
|
ExpectedOutResult =
|
||||||
<<"gooduser">> -> allow;
|
case maps:get(username, ClientInfo) of
|
||||||
<<"normaluser">> -> allow;
|
<<"baduser">> -> deny;
|
||||||
_ -> Result
|
<<"gooduser">> -> allow;
|
||||||
end,
|
<<"normaluser">> -> allow;
|
||||||
|
_ -> Result
|
||||||
|
end,
|
||||||
?assertEqual(ExpectedOutResult, OutResult),
|
?assertEqual(ExpectedOutResult, OutResult),
|
||||||
|
|
||||||
{'on_client_authorize', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_client_authorize', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{result => aclresult_to_bool(Result),
|
#{
|
||||||
type => pubsub_to_enum(PubSub),
|
result => aclresult_to_bool(Result),
|
||||||
topic => Topic,
|
type => pubsub_to_enum(PubSub),
|
||||||
clientinfo => from_clientinfo(ClientInfo)
|
topic => Topic,
|
||||||
},
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_client_connected() ->
|
prop_client_connected() ->
|
||||||
?ALL({ClientInfo, ConnInfo},
|
?ALL(
|
||||||
{clientinfo(), conninfo()},
|
{ClientInfo, ConnInfo},
|
||||||
|
{clientinfo(), conninfo()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('client.connected', [ClientInfo, ConnInfo]),
|
ok = emqx_hooks:run('client.connected', [ClientInfo, ConnInfo]),
|
||||||
{'on_client_connected', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_client_connected', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{clientinfo => from_clientinfo(ClientInfo)
|
#{clientinfo => from_clientinfo(ClientInfo)},
|
||||||
},
|
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_client_disconnected() ->
|
prop_client_disconnected() ->
|
||||||
?ALL({ClientInfo, Reason, ConnInfo},
|
?ALL(
|
||||||
{clientinfo(), shutdown_reason(), conninfo()},
|
{ClientInfo, Reason, ConnInfo},
|
||||||
|
{clientinfo(), shutdown_reason(), conninfo()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]),
|
ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]),
|
||||||
{'on_client_disconnected', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_client_disconnected', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{reason => stringfy(Reason),
|
#{
|
||||||
clientinfo => from_clientinfo(ClientInfo)
|
reason => stringfy(Reason),
|
||||||
},
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_client_subscribe() ->
|
prop_client_subscribe() ->
|
||||||
?ALL({ClientInfo, SubProps, TopicTab},
|
?ALL(
|
||||||
{clientinfo(), sub_properties(), topictab()},
|
{ClientInfo, SubProps, TopicTab},
|
||||||
|
{clientinfo(), sub_properties(), topictab()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('client.subscribe', [ClientInfo, SubProps, TopicTab]),
|
ok = emqx_hooks:run('client.subscribe', [ClientInfo, SubProps, TopicTab]),
|
||||||
{'on_client_subscribe', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_client_subscribe', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{props => properties(SubProps),
|
#{
|
||||||
topic_filters => topicfilters(TopicTab),
|
props => properties(SubProps),
|
||||||
clientinfo => from_clientinfo(ClientInfo)
|
topic_filters => topicfilters(TopicTab),
|
||||||
},
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_client_unsubscribe() ->
|
prop_client_unsubscribe() ->
|
||||||
?ALL({ClientInfo, UnSubProps, TopicTab},
|
?ALL(
|
||||||
{clientinfo(), unsub_properties(), topictab()},
|
{ClientInfo, UnSubProps, TopicTab},
|
||||||
|
{clientinfo(), unsub_properties(), topictab()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('client.unsubscribe', [ClientInfo, UnSubProps, TopicTab]),
|
ok = emqx_hooks:run('client.unsubscribe', [ClientInfo, UnSubProps, TopicTab]),
|
||||||
{'on_client_unsubscribe', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_client_unsubscribe', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{props => properties(UnSubProps),
|
#{
|
||||||
topic_filters => topicfilters(TopicTab),
|
props => properties(UnSubProps),
|
||||||
clientinfo => from_clientinfo(ClientInfo)
|
topic_filters => topicfilters(TopicTab),
|
||||||
},
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_session_created() ->
|
prop_session_created() ->
|
||||||
?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
?ALL(
|
||||||
|
{ClientInfo, SessInfo},
|
||||||
|
{clientinfo(), sessioninfo()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('session.created', [ClientInfo, SessInfo]),
|
ok = emqx_hooks:run('session.created', [ClientInfo, SessInfo]),
|
||||||
{'on_session_created', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_session_created', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{clientinfo => from_clientinfo(ClientInfo)
|
#{clientinfo => from_clientinfo(ClientInfo)},
|
||||||
},
|
?assertEqual(Expected, Resp),
|
||||||
?assertEqual(Expected, Resp),
|
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_session_subscribed() ->
|
prop_session_subscribed() ->
|
||||||
?ALL({ClientInfo, Topic, SubOpts},
|
?ALL(
|
||||||
{clientinfo(), topic(), subopts()},
|
{ClientInfo, Topic, SubOpts},
|
||||||
|
{clientinfo(), topic(), subopts()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('session.subscribed', [ClientInfo, Topic, SubOpts]),
|
ok = emqx_hooks:run('session.subscribed', [ClientInfo, Topic, SubOpts]),
|
||||||
{'on_session_subscribed', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_session_subscribed', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{topic => Topic,
|
#{
|
||||||
subopts => subopts(SubOpts),
|
topic => Topic,
|
||||||
clientinfo => from_clientinfo(ClientInfo)
|
subopts => subopts(SubOpts),
|
||||||
},
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_session_unsubscribed() ->
|
prop_session_unsubscribed() ->
|
||||||
?ALL({ClientInfo, Topic, SubOpts},
|
?ALL(
|
||||||
{clientinfo(), topic(), subopts()},
|
{ClientInfo, Topic, SubOpts},
|
||||||
|
{clientinfo(), topic(), subopts()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('session.unsubscribed', [ClientInfo, Topic, SubOpts]),
|
ok = emqx_hooks:run('session.unsubscribed', [ClientInfo, Topic, SubOpts]),
|
||||||
{'on_session_unsubscribed', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_session_unsubscribed', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{topic => Topic,
|
#{
|
||||||
clientinfo => from_clientinfo(ClientInfo)
|
topic => Topic,
|
||||||
},
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_session_resumed() ->
|
prop_session_resumed() ->
|
||||||
?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
?ALL(
|
||||||
|
{ClientInfo, SessInfo},
|
||||||
|
{clientinfo(), sessioninfo()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('session.resumed', [ClientInfo, SessInfo]),
|
ok = emqx_hooks:run('session.resumed', [ClientInfo, SessInfo]),
|
||||||
{'on_session_resumed', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_session_resumed', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{clientinfo => from_clientinfo(ClientInfo)
|
#{clientinfo => from_clientinfo(ClientInfo)},
|
||||||
},
|
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_session_discared() ->
|
prop_session_discared() ->
|
||||||
?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
?ALL(
|
||||||
|
{ClientInfo, SessInfo},
|
||||||
|
{clientinfo(), sessioninfo()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('session.discarded', [ClientInfo, SessInfo]),
|
ok = emqx_hooks:run('session.discarded', [ClientInfo, SessInfo]),
|
||||||
{'on_session_discarded', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_session_discarded', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{clientinfo => from_clientinfo(ClientInfo)
|
#{clientinfo => from_clientinfo(ClientInfo)},
|
||||||
},
|
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_session_takenover() ->
|
prop_session_takenover() ->
|
||||||
?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
?ALL(
|
||||||
|
{ClientInfo, SessInfo},
|
||||||
|
{clientinfo(), sessioninfo()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('session.takenover', [ClientInfo, SessInfo]),
|
ok = emqx_hooks:run('session.takenover', [ClientInfo, SessInfo]),
|
||||||
{'on_session_takenover', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_session_takenover', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{clientinfo => from_clientinfo(ClientInfo)
|
#{clientinfo => from_clientinfo(ClientInfo)},
|
||||||
},
|
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_session_terminated() ->
|
prop_session_terminated() ->
|
||||||
?ALL({ClientInfo, Reason, SessInfo},
|
?ALL(
|
||||||
{clientinfo(), shutdown_reason(), sessioninfo()},
|
{ClientInfo, Reason, SessInfo},
|
||||||
|
{clientinfo(), shutdown_reason(), sessioninfo()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('session.terminated', [ClientInfo, Reason, SessInfo]),
|
ok = emqx_hooks:run('session.terminated', [ClientInfo, Reason, SessInfo]),
|
||||||
{'on_session_terminated', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_session_terminated', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{reason => stringfy(Reason),
|
#{
|
||||||
clientinfo => from_clientinfo(ClientInfo)
|
reason => stringfy(Reason),
|
||||||
},
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp),
|
?assertEqual(Expected, Resp),
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_message_publish() ->
|
prop_message_publish() ->
|
||||||
?ALL(Msg0, message(),
|
?ALL(
|
||||||
|
Msg0,
|
||||||
|
message(),
|
||||||
begin
|
begin
|
||||||
Msg = emqx_message:from_map(
|
Msg = emqx_message:from_map(
|
||||||
inject_magic_into(from, emqx_message:to_map(Msg0))),
|
inject_magic_into(from, emqx_message:to_map(Msg0))
|
||||||
OutMsg= emqx_hooks:run_fold('message.publish', [], Msg),
|
),
|
||||||
|
OutMsg = emqx_hooks:run_fold('message.publish', [], Msg),
|
||||||
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
||||||
true ->
|
true ->
|
||||||
?assertEqual(Msg, OutMsg),
|
?assertEqual(Msg, OutMsg),
|
||||||
skip;
|
skip;
|
||||||
_ ->
|
_ ->
|
||||||
ExpectedOutMsg = case emqx_message:from(Msg) of
|
ExpectedOutMsg =
|
||||||
<<"baduser">> ->
|
case emqx_message:from(Msg) of
|
||||||
MsgMap = #{headers := Headers}
|
<<"baduser">> ->
|
||||||
= emqx_message:to_map(Msg),
|
MsgMap =
|
||||||
emqx_message:from_map(
|
#{headers := Headers} =
|
||||||
MsgMap#{qos => 0,
|
emqx_message:to_map(Msg),
|
||||||
topic => <<"">>,
|
emqx_message:from_map(
|
||||||
payload => <<"">>,
|
MsgMap#{
|
||||||
headers => maps:put(allow_publish, false, Headers)
|
qos => 0,
|
||||||
});
|
topic => <<"">>,
|
||||||
<<"gooduser">> = From ->
|
payload => <<"">>,
|
||||||
MsgMap = #{headers := Headers}
|
headers => maps:put(allow_publish, false, Headers)
|
||||||
= emqx_message:to_map(Msg),
|
}
|
||||||
emqx_message:from_map(
|
);
|
||||||
MsgMap#{topic => From,
|
<<"gooduser">> = From ->
|
||||||
payload => From,
|
MsgMap =
|
||||||
headers => maps:put(allow_publish, true, Headers)
|
#{headers := Headers} =
|
||||||
});
|
emqx_message:to_map(Msg),
|
||||||
_ ->
|
emqx_message:from_map(
|
||||||
Msg
|
MsgMap#{
|
||||||
end,
|
topic => From,
|
||||||
|
payload => From,
|
||||||
|
headers => maps:put(allow_publish, true, Headers)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
_ ->
|
||||||
|
Msg
|
||||||
|
end,
|
||||||
?assertEqual(ExpectedOutMsg, OutMsg),
|
?assertEqual(ExpectedOutMsg, OutMsg),
|
||||||
|
|
||||||
{'on_message_publish', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_message_publish', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{message => from_message(Msg)
|
#{message => from_message(Msg)},
|
||||||
},
|
|
||||||
?assertEqual(Expected, Resp)
|
?assertEqual(Expected, Resp)
|
||||||
end,
|
end,
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_message_dropped() ->
|
prop_message_dropped() ->
|
||||||
?ALL({Msg, By, Reason}, {message(), hardcoded, shutdown_reason()},
|
?ALL(
|
||||||
|
{Msg, By, Reason},
|
||||||
|
{message(), hardcoded, shutdown_reason()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('message.dropped', [Msg, By, Reason]),
|
ok = emqx_hooks:run('message.dropped', [Msg, By, Reason]),
|
||||||
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
||||||
true -> skip;
|
true ->
|
||||||
|
skip;
|
||||||
_ ->
|
_ ->
|
||||||
{'on_message_dropped', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_message_dropped', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{reason => stringfy(Reason),
|
#{
|
||||||
message => from_message(Msg)
|
reason => stringfy(Reason),
|
||||||
},
|
message => from_message(Msg)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp)
|
?assertEqual(Expected, Resp)
|
||||||
end,
|
end,
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_message_delivered() ->
|
prop_message_delivered() ->
|
||||||
?ALL({ClientInfo, Msg}, {clientinfo(), message()},
|
?ALL(
|
||||||
|
{ClientInfo, Msg},
|
||||||
|
{clientinfo(), message()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('message.delivered', [ClientInfo, Msg]),
|
ok = emqx_hooks:run('message.delivered', [ClientInfo, Msg]),
|
||||||
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
||||||
true -> skip;
|
true ->
|
||||||
|
skip;
|
||||||
_ ->
|
_ ->
|
||||||
{'on_message_delivered', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_message_delivered', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{clientinfo => from_clientinfo(ClientInfo),
|
#{
|
||||||
message => from_message(Msg)
|
clientinfo => from_clientinfo(ClientInfo),
|
||||||
},
|
message => from_message(Msg)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp)
|
?assertEqual(Expected, Resp)
|
||||||
end,
|
end,
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
prop_message_acked() ->
|
prop_message_acked() ->
|
||||||
?ALL({ClientInfo, Msg}, {clientinfo(), message()},
|
?ALL(
|
||||||
|
{ClientInfo, Msg},
|
||||||
|
{clientinfo(), message()},
|
||||||
begin
|
begin
|
||||||
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
|
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
|
||||||
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
||||||
true -> skip;
|
true ->
|
||||||
|
skip;
|
||||||
_ ->
|
_ ->
|
||||||
{'on_message_acked', Resp} = emqx_exhook_demo_svr:take(),
|
{'on_message_acked', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
Expected =
|
Expected =
|
||||||
#{clientinfo => from_clientinfo(ClientInfo),
|
#{
|
||||||
message => from_message(Msg)
|
clientinfo => from_clientinfo(ClientInfo),
|
||||||
},
|
message => from_message(Msg)
|
||||||
|
},
|
||||||
?assertEqual(Expected, Resp)
|
?assertEqual(Expected, Resp)
|
||||||
end,
|
end,
|
||||||
true
|
true
|
||||||
end).
|
end
|
||||||
|
).
|
||||||
|
|
||||||
nodestr() ->
|
nodestr() ->
|
||||||
stringfy(node()).
|
stringfy(node()).
|
||||||
|
@ -388,7 +465,7 @@ sockport(#{sockname := {_, Port}}) ->
|
||||||
|
|
||||||
%% copied from emqx_exhook
|
%% copied from emqx_exhook
|
||||||
|
|
||||||
ntoa({0,0,0,0,0,16#ffff,AB,CD}) ->
|
ntoa({0, 0, 0, 0, 0, 16#ffff, AB, CD}) ->
|
||||||
list_to_binary(inet_parse:ntoa({AB bsr 8, AB rem 256, CD bsr 8, CD rem 256}));
|
list_to_binary(inet_parse:ntoa({AB bsr 8, AB rem 256, CD bsr 8, CD rem 256}));
|
||||||
ntoa(IP) ->
|
ntoa(IP) ->
|
||||||
list_to_binary(inet_parse:ntoa(IP)).
|
list_to_binary(inet_parse:ntoa(IP)).
|
||||||
|
@ -396,12 +473,22 @@ ntoa(IP) ->
|
||||||
maybe(undefined) -> <<>>;
|
maybe(undefined) -> <<>>;
|
||||||
maybe(B) -> B.
|
maybe(B) -> B.
|
||||||
|
|
||||||
properties(undefined) -> [];
|
properties(undefined) ->
|
||||||
|
[];
|
||||||
properties(M) when is_map(M) ->
|
properties(M) when is_map(M) ->
|
||||||
maps:fold(fun(K, V, Acc) ->
|
maps:fold(
|
||||||
[#{name => stringfy(K),
|
fun(K, V, Acc) ->
|
||||||
value => stringfy(V)} | Acc]
|
[
|
||||||
end, [], M).
|
#{
|
||||||
|
name => stringfy(K),
|
||||||
|
value => stringfy(V)
|
||||||
|
}
|
||||||
|
| Acc
|
||||||
|
]
|
||||||
|
end,
|
||||||
|
[],
|
||||||
|
M
|
||||||
|
).
|
||||||
|
|
||||||
topicfilters(Tfs) when is_list(Tfs) ->
|
topicfilters(Tfs) when is_list(Tfs) ->
|
||||||
[#{name => Topic, qos => Qos} || {Topic, #{qos := Qos}} <- Tfs].
|
[#{name => Topic, qos => Qos} || {Topic, #{qos := Qos}} <- Tfs].
|
||||||
|
@ -417,12 +504,13 @@ stringfy(Term) ->
|
||||||
unicode:characters_to_binary((io_lib:format("~0p", [Term]))).
|
unicode:characters_to_binary((io_lib:format("~0p", [Term]))).
|
||||||
|
|
||||||
subopts(SubOpts) ->
|
subopts(SubOpts) ->
|
||||||
#{qos => maps:get(qos, SubOpts, 0),
|
#{
|
||||||
rh => maps:get(rh, SubOpts, 0),
|
qos => maps:get(qos, SubOpts, 0),
|
||||||
rap => maps:get(rap, SubOpts, 0),
|
rh => maps:get(rh, SubOpts, 0),
|
||||||
nl => maps:get(nl, SubOpts, 0),
|
rap => maps:get(rap, SubOpts, 0),
|
||||||
share => maps:get(share, SubOpts, <<>>)
|
nl => maps:get(nl, SubOpts, 0),
|
||||||
}.
|
share => maps:get(share, SubOpts, <<>>)
|
||||||
|
}.
|
||||||
|
|
||||||
authresult_to_bool(AuthResult) ->
|
authresult_to_bool(AuthResult) ->
|
||||||
AuthResult == ok.
|
AuthResult == ok.
|
||||||
|
@ -434,42 +522,46 @@ pubsub_to_enum(publish) -> 'PUBLISH';
|
||||||
pubsub_to_enum(subscribe) -> 'SUBSCRIBE'.
|
pubsub_to_enum(subscribe) -> 'SUBSCRIBE'.
|
||||||
|
|
||||||
from_conninfo(ConnInfo) ->
|
from_conninfo(ConnInfo) ->
|
||||||
#{node => nodestr(),
|
#{
|
||||||
clientid => maps:get(clientid, ConnInfo),
|
node => nodestr(),
|
||||||
username => maybe(maps:get(username, ConnInfo, <<>>)),
|
clientid => maps:get(clientid, ConnInfo),
|
||||||
peerhost => peerhost(ConnInfo),
|
username => maybe(maps:get(username, ConnInfo, <<>>)),
|
||||||
sockport => sockport(ConnInfo),
|
peerhost => peerhost(ConnInfo),
|
||||||
proto_name => maps:get(proto_name, ConnInfo),
|
sockport => sockport(ConnInfo),
|
||||||
proto_ver => stringfy(maps:get(proto_ver, ConnInfo)),
|
proto_name => maps:get(proto_name, ConnInfo),
|
||||||
keepalive => maps:get(keepalive, ConnInfo)
|
proto_ver => stringfy(maps:get(proto_ver, ConnInfo)),
|
||||||
}.
|
keepalive => maps:get(keepalive, ConnInfo)
|
||||||
|
}.
|
||||||
|
|
||||||
from_clientinfo(ClientInfo) ->
|
from_clientinfo(ClientInfo) ->
|
||||||
#{node => nodestr(),
|
#{
|
||||||
clientid => maps:get(clientid, ClientInfo),
|
node => nodestr(),
|
||||||
username => maybe(maps:get(username, ClientInfo, <<>>)),
|
clientid => maps:get(clientid, ClientInfo),
|
||||||
password => maybe(maps:get(password, ClientInfo, <<>>)),
|
username => maybe(maps:get(username, ClientInfo, <<>>)),
|
||||||
peerhost => ntoa(maps:get(peerhost, ClientInfo)),
|
password => maybe(maps:get(password, ClientInfo, <<>>)),
|
||||||
sockport => maps:get(sockport, ClientInfo),
|
peerhost => ntoa(maps:get(peerhost, ClientInfo)),
|
||||||
protocol => stringfy(maps:get(protocol, ClientInfo)),
|
sockport => maps:get(sockport, ClientInfo),
|
||||||
mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
|
protocol => stringfy(maps:get(protocol, ClientInfo)),
|
||||||
is_superuser => maps:get(is_superuser, ClientInfo, false),
|
mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
|
||||||
anonymous => maps:get(anonymous, ClientInfo, true),
|
is_superuser => maps:get(is_superuser, ClientInfo, false),
|
||||||
cn => maybe(maps:get(cn, ClientInfo, <<>>)),
|
anonymous => maps:get(anonymous, ClientInfo, true),
|
||||||
dn => maybe(maps:get(dn, ClientInfo, <<>>))
|
cn => maybe(maps:get(cn, ClientInfo, <<>>)),
|
||||||
|
dn => maybe(maps:get(dn, ClientInfo, <<>>))
|
||||||
}.
|
}.
|
||||||
|
|
||||||
from_message(Msg) ->
|
from_message(Msg) ->
|
||||||
#{node => nodestr(),
|
#{
|
||||||
id => emqx_guid:to_hexstr(emqx_message:id(Msg)),
|
node => nodestr(),
|
||||||
qos => emqx_message:qos(Msg),
|
id => emqx_guid:to_hexstr(emqx_message:id(Msg)),
|
||||||
from => stringfy(emqx_message:from(Msg)),
|
qos => emqx_message:qos(Msg),
|
||||||
topic => emqx_message:topic(Msg),
|
from => stringfy(emqx_message:from(Msg)),
|
||||||
payload => emqx_message:payload(Msg),
|
topic => emqx_message:topic(Msg),
|
||||||
timestamp => emqx_message:timestamp(Msg),
|
payload => emqx_message:payload(Msg),
|
||||||
headers => emqx_exhook_handler:headers(
|
timestamp => emqx_message:timestamp(Msg),
|
||||||
emqx_message:get_headers(Msg))
|
headers => emqx_exhook_handler:headers(
|
||||||
}.
|
emqx_message:get_headers(Msg)
|
||||||
|
)
|
||||||
|
}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Helper
|
%% Helper
|
||||||
|
@ -513,17 +605,19 @@ shutdown_reason() ->
|
||||||
oneof([utf8(), {shutdown, emqx_proper_types:limited_atom()}]).
|
oneof([utf8(), {shutdown, emqx_proper_types:limited_atom()}]).
|
||||||
|
|
||||||
authresult() ->
|
authresult() ->
|
||||||
?LET(RC, connack_return_code(),
|
?LET(
|
||||||
case RC of
|
RC,
|
||||||
success -> ok;
|
connack_return_code(),
|
||||||
_ -> {error, RC}
|
case RC of
|
||||||
end).
|
success -> ok;
|
||||||
|
_ -> {error, RC}
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
inject_magic_into(Key, Object) ->
|
inject_magic_into(Key, Object) ->
|
||||||
case castspell() of
|
case castspell() of
|
||||||
muggles -> Object;
|
muggles -> Object;
|
||||||
Spell ->
|
Spell -> Object#{Key => Spell}
|
||||||
Object#{Key => Spell}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
castspell() ->
|
castspell() ->
|
||||||
|
|
Loading…
Reference in New Issue