refactor(exhook): adapt to the hocon schmea
This commit is contained in:
parent
58b39361b3
commit
879c191e41
|
@ -0,0 +1,29 @@
|
||||||
|
.rebar3
|
||||||
|
_*
|
||||||
|
.eunit
|
||||||
|
*.o
|
||||||
|
*.beam
|
||||||
|
*.plt
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
.erlang.cookie
|
||||||
|
ebin
|
||||||
|
log
|
||||||
|
erl_crash.dump
|
||||||
|
.rebar
|
||||||
|
logs
|
||||||
|
_build
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
rebar3.crashdump
|
||||||
|
*~
|
||||||
|
rebar.lock
|
||||||
|
data/
|
||||||
|
*.conf.rendered
|
||||||
|
*.pyc
|
||||||
|
.DS_Store
|
||||||
|
*.class
|
||||||
|
Mnesia.nonode@nohost/
|
||||||
|
src/emqx_exhook_pb.erl
|
||||||
|
src/emqx_exhook_v_1_hook_provider_client.erl
|
||||||
|
src/emqx_exhook_v_1_hook_provider_bhvr.erl
|
|
@ -0,0 +1,116 @@
|
||||||
|
# 设计
|
||||||
|
|
||||||
|
## 动机
|
||||||
|
|
||||||
|
在 EMQ X Broker v4.1-v4.2 中,我们发布了 2 个插件来扩展 emqx 的编程能力:
|
||||||
|
|
||||||
|
1. `emqx-extension-hook` 提供了使用 Java, Python 向 Broker 挂载钩子的功能
|
||||||
|
2. `emqx-exproto` 提供了使用 Java,Python 编写用户自定义协议接入插件的功能
|
||||||
|
|
||||||
|
但在后续的支持中发现许多难以处理的问题:
|
||||||
|
|
||||||
|
1. 有大量的编程语言需要支持,需要编写和维护如 Go, JavaScript, Lua.. 等语言的驱动。
|
||||||
|
2. `erlport` 使用的操作系统的管道进行通信,这让用户代码只能部署在和 emqx 同一个操作系统上。部署方式受到了极大的限制。
|
||||||
|
3. 用户程序的启动参数直接打包到 Broker 中,导致用户开发无法实时的进行调试,单步跟踪等。
|
||||||
|
4. `erlport` 会占用 `stdin` `stdout`。
|
||||||
|
|
||||||
|
因此,我们计划重构这部分的实现,其中主要的内容是:
|
||||||
|
1. 使用 `gRPC` 替换 `erlport`。
|
||||||
|
2. 将 `emqx-extension-hook` 重命名为 `emqx-exhook`
|
||||||
|
|
||||||
|
|
||||||
|
旧版本的设计:[emqx-extension-hook design in v4.2.0](https://github.com/emqx/emqx-exhook/blob/v4.2.0/docs/design.md)
|
||||||
|
|
||||||
|
## 设计
|
||||||
|
|
||||||
|
架构如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
EMQ X
|
||||||
|
+========================+ +========+==========+
|
||||||
|
| ExHook | | | |
|
||||||
|
| +----------------+ | gRPC | gRPC | User's |
|
||||||
|
| | gRPC Client | ------------------> | Server | Codes |
|
||||||
|
| +----------------+ | (HTTP/2) | | |
|
||||||
|
| | | | |
|
||||||
|
+========================+ +========+==========+
|
||||||
|
```
|
||||||
|
|
||||||
|
`emqx-exhook` 通过 gRPC 的方式向用户部署的 gRPC 服务发送钩子的请求,并处理其返回的值。
|
||||||
|
|
||||||
|
|
||||||
|
和 emqx 原生的钩子一致,emqx-exhook 也按照链式的方式执行:
|
||||||
|
|
||||||
|
<img src="https://docs.emqx.net/broker/latest/cn/advanced/assets/chain_of_responsiblity.png" style="zoom:50%;" />
|
||||||
|
|
||||||
|
### gRPC 服务示例
|
||||||
|
|
||||||
|
用户需要实现的方法,和数据类型的定义在 `priv/protos/exhook.proto` 文件中:
|
||||||
|
|
||||||
|
```protobuff
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package emqx.exhook.v1;
|
||||||
|
|
||||||
|
service HookProvider {
|
||||||
|
|
||||||
|
rpc OnProviderLoaded(ProviderLoadedRequest) returns (LoadedResponse) {};
|
||||||
|
|
||||||
|
rpc OnProviderUnloaded(ProviderUnloadedRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnClientConnect(ClientConnectRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnClientConnack(ClientConnackRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnClientConnected(ClientConnectedRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnClientDisconnected(ClientDisconnectedRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnClientAuthenticate(ClientAuthenticateRequest) returns (ValuedResponse) {};
|
||||||
|
|
||||||
|
rpc OnClientCheckAcl(ClientCheckAclRequest) returns (ValuedResponse) {};
|
||||||
|
|
||||||
|
rpc OnClientSubscribe(ClientSubscribeRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnClientUnsubscribe(ClientUnsubscribeRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnSessionCreated(SessionCreatedRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnSessionSubscribed(SessionSubscribedRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnSessionUnsubscribed(SessionUnsubscribedRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnSessionResumed(SessionResumedRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnSessionDiscarded(SessionDiscardedRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnSessionTakeovered(SessionTakeoveredRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnSessionTerminated(SessionTerminatedRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnMessagePublish(MessagePublishRequest) returns (ValuedResponse) {};
|
||||||
|
|
||||||
|
rpc OnMessageDelivered(MessageDeliveredRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnMessageDropped(MessageDroppedRequest) returns (EmptySuccess) {};
|
||||||
|
|
||||||
|
rpc OnMessageAcked(MessageAckedRequest) returns (EmptySuccess) {};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置文件示例
|
||||||
|
|
||||||
|
```
|
||||||
|
## 配置 gRPC 服务地址 (HTTP)
|
||||||
|
##
|
||||||
|
## s1 为服务器的名称
|
||||||
|
exhook.server.s1.url = http://127.0.0.1:9001
|
||||||
|
|
||||||
|
## 配置 gRPC 服务地址 (HTTPS)
|
||||||
|
##
|
||||||
|
## s2 为服务器名称
|
||||||
|
exhook.server.s2.url = https://127.0.0.1:9002
|
||||||
|
exhook.server.s2.cacertfile = ca.pem
|
||||||
|
exhook.server.s2.certfile = cert.pem
|
||||||
|
exhook.server.s2.keyfile = key.pem
|
||||||
|
```
|
|
@ -0,0 +1,14 @@
|
||||||
|
##====================================================================
|
||||||
|
## EMQ X Hooks
|
||||||
|
##====================================================================
|
||||||
|
|
||||||
|
exhook: {
|
||||||
|
server.default: {
|
||||||
|
url: "http://127.0.0.1:9000"
|
||||||
|
#ssl: {
|
||||||
|
# cacertfile: "{{ platform_etc_dir }}/certs/cacert.pem"
|
||||||
|
# certfile: "{{ platform_etc_dir }}/certs/cert.pem"
|
||||||
|
# keyfile: "{{ platform_etc_dir }}/certs/key.pem"
|
||||||
|
#}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@
|
||||||
, {'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.check_acl', {emqx_exhook_handler, on_client_check_acl, []}}
|
||||||
, {'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, []}}
|
|
@ -0,0 +1,38 @@
|
||||||
|
%%-*- mode: erlang -*-
|
||||||
|
|
||||||
|
{mapping, "exhook.server.$name.url", "emqx_exhook.servers", [
|
||||||
|
{datatype, string}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{mapping, "exhook.server.$name.ssl.cacertfile", "emqx_exhook.servers", [
|
||||||
|
{datatype, string}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{mapping, "exhook.server.$name.ssl.certfile", "emqx_exhook.servers", [
|
||||||
|
{datatype, string}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{mapping, "exhook.server.$name.ssl.keyfile", "emqx_exhook.servers", [
|
||||||
|
{datatype, string}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{translation, "emqx_exhook.servers", fun(Conf) ->
|
||||||
|
Filter = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end,
|
||||||
|
ServerOptions = fun(Prefix) ->
|
||||||
|
case http_uri:parse(cuttlefish:conf_get(Prefix ++ ".url", Conf)) of
|
||||||
|
{ok, {http, _, Host, Port, _, _}} ->
|
||||||
|
[{scheme, http}, {host, Host}, {port, Port}];
|
||||||
|
{ok, {https, _, Host, Port, _, _}} ->
|
||||||
|
[{scheme, https}, {host, Host}, {port, Port},
|
||||||
|
{ssl_options,
|
||||||
|
Filter([{ssl, true},
|
||||||
|
{certfile, cuttlefish:conf_get(Prefix ++ ".ssl.certfile", Conf, undefined)},
|
||||||
|
{keyfile, cuttlefish:conf_get(Prefix ++ ".ssl.keyfile", Conf, undefined)},
|
||||||
|
{cacertfile, cuttlefish:conf_get(Prefix ++ ".ssl.cacertfile", Conf, undefined)}
|
||||||
|
])}];
|
||||||
|
_ -> error(invalid_server_options)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
[{list_to_atom(Name), ServerOptions("exhook.server." ++ Name)}
|
||||||
|
|| {["exhook", "server", Name, "url"], _} <- cuttlefish_variable:filter_by_prefix("exhook.server", Conf)]
|
||||||
|
end}.
|
|
@ -127,14 +127,14 @@ message ClientAuthorizeRequest {
|
||||||
|
|
||||||
ClientInfo clientinfo = 1;
|
ClientInfo clientinfo = 1;
|
||||||
|
|
||||||
enum AuthzReqType {
|
enum AuthorizeReqType {
|
||||||
|
|
||||||
PUBLISH = 0;
|
PUBLISH = 0;
|
||||||
|
|
||||||
SUBSCRIBE = 1;
|
SUBSCRIBE = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthzReqType type = 2;
|
AuthorizeReqType type = 2;
|
||||||
|
|
||||||
string topic = 3;
|
string topic = 3;
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
%%-*- mode: erlang -*-
|
||||||
|
{plugins,
|
||||||
|
[rebar3_proper,
|
||||||
|
{grpc_plugin, {git, "https://github.com/HJianBo/grpc_plugin", {tag, "v0.10.2"}}}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{deps,
|
||||||
|
[{grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.2"}}}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{grpc,
|
||||||
|
[{protos, ["priv/protos"]},
|
||||||
|
{gpb_opts, [{module_name_prefix, "emqx_"},
|
||||||
|
{module_name_suffix, "_pb"}]}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{provider_hooks,
|
||||||
|
[{pre, [{compile, {grpc, gen}},
|
||||||
|
{clean, {grpc, clean}}]}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{edoc_opts, [{preprocess, true}]}.
|
||||||
|
|
||||||
|
{erl_opts, [warn_unused_vars,
|
||||||
|
warn_shadow_vars,
|
||||||
|
warn_unused_import,
|
||||||
|
warn_obsolete_guard,
|
||||||
|
debug_info,
|
||||||
|
{parse_transform}]}.
|
||||||
|
|
||||||
|
{xref_checks, [undefined_function_calls, undefined_functions,
|
||||||
|
locals_not_used, deprecated_function_calls,
|
||||||
|
warnings_as_errors, deprecated_functions]}.
|
||||||
|
{xref_ignores, [emqx_exhook_pb]}.
|
||||||
|
|
||||||
|
{cover_enabled, true}.
|
||||||
|
{cover_opts, [verbose]}.
|
||||||
|
{cover_export_enabled, true}.
|
||||||
|
{cover_excl_mods, [emqx_exhook_pb,
|
||||||
|
emqx_exhook_v_1_hook_provider_bhvr,
|
||||||
|
emqx_exhook_v_1_hook_provider_client]}.
|
||||||
|
|
||||||
|
{profiles,
|
||||||
|
[{test,
|
||||||
|
[{deps,
|
||||||
|
[{emqx_ct_helper, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "v1.3.1"}}}
|
||||||
|
]}
|
||||||
|
]}
|
||||||
|
]}.
|
|
@ -1,6 +1,6 @@
|
||||||
{application, emqx_exhook,
|
{application, emqx_exhook,
|
||||||
[{description, "EMQ X Extension for Hook"},
|
[{description, "EMQ X Extension for Hook"},
|
||||||
{vsn, "4.3.2"},
|
{vsn, "4.3.3"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{mod, {emqx_exhook_app, []}},
|
{mod, {emqx_exhook_app, []}},
|
|
@ -0,0 +1,33 @@
|
||||||
|
%% -*-: erlang -*-
|
||||||
|
{VSN,
|
||||||
|
[
|
||||||
|
{"4.3.2", [
|
||||||
|
{load_module, emqx_exhook_app, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{"4.3.1", [
|
||||||
|
{load_module, emqx_exhook_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_exhook_server, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{"4.3.0", [
|
||||||
|
{load_module, emqx_exhook_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_exhook_pb, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_exhook_server, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<".*">>, []}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{"4.3.2", [
|
||||||
|
{load_module, emqx_exhook_app, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{"4.3.1", [
|
||||||
|
{load_module, emqx_exhook_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_exhook_server, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{"4.3.0", [
|
||||||
|
{load_module, emqx_exhook_app, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_exhook_pb, brutal_purge, soft_purge, []},
|
||||||
|
{load_module, emqx_exhook_server, brutal_purge, soft_purge, []}
|
||||||
|
]},
|
||||||
|
{<<".*">>, []}
|
||||||
|
]
|
||||||
|
}.
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_exhook).
|
-module(emqx_exhook).
|
||||||
|
|
||||||
-include("src/exhook/include/emqx_exhook.hrl").
|
-include("emqx_exhook.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,13 +40,13 @@
|
||||||
list() ->
|
list() ->
|
||||||
[server(Name) || Name <- running()].
|
[server(Name) || Name <- running()].
|
||||||
|
|
||||||
-spec enable(atom()|string(), list()) -> ok | {error, term()}.
|
-spec enable(atom()|string(), map()) -> ok | {error, term()}.
|
||||||
enable(Name, Opts) ->
|
enable(Name, Options) ->
|
||||||
case lists:member(Name, running()) of
|
case lists:member(Name, running()) of
|
||||||
true ->
|
true ->
|
||||||
{error, already_started};
|
{error, already_started};
|
||||||
_ ->
|
_ ->
|
||||||
case emqx_exhook_server:load(Name, Opts) of
|
case emqx_exhook_server:load(Name, Options) of
|
||||||
{ok, ServiceState} ->
|
{ok, ServiceState} ->
|
||||||
save(Name, ServiceState);
|
save(Name, ServiceState);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
-behaviour(application).
|
-behaviour(application).
|
||||||
|
|
||||||
-include("src/exhook/include/emqx_exhook.hrl").
|
-include("emqx_exhook.hrl").
|
||||||
|
|
||||||
-emqx_plugin(extension).
|
-emqx_plugin(extension).
|
||||||
|
|
||||||
|
@ -67,9 +67,10 @@ stop(_State) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
load_all_servers() ->
|
load_all_servers() ->
|
||||||
lists:foreach(fun({Name, Options}) ->
|
_ = maps:map(fun(Name, Options) ->
|
||||||
load_server(Name, Options)
|
load_server(Name, Options)
|
||||||
end, application:get_env(?APP, servers, [])).
|
end, emqx_config:get([exhook, server])),
|
||||||
|
ok.
|
||||||
|
|
||||||
unload_all_servers() ->
|
unload_all_servers() ->
|
||||||
emqx_exhook:disable_all().
|
emqx_exhook:disable_all().
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_exhook_cli).
|
-module(emqx_exhook_cli).
|
||||||
|
|
||||||
-include("src/exhook/include/emqx_exhook.hrl").
|
-include("emqx_exhook.hrl").
|
||||||
|
|
||||||
-export([cli/1]).
|
-export([cli/1]).
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_exhook_handler).
|
-module(emqx_exhook_handler).
|
||||||
|
|
||||||
-include("src/exhook/include/emqx_exhook.hrl").
|
-include("emqx_exhook.hrl").
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
|
@ -87,6 +87,7 @@ on_client_disconnected(ClientInfo, Reason, _ConnInfo) ->
|
||||||
},
|
},
|
||||||
cast('client.disconnected', Req).
|
cast('client.disconnected', Req).
|
||||||
|
|
||||||
|
%% FIXME: `AuthResult`
|
||||||
on_client_authenticate(ClientInfo, AuthResult) ->
|
on_client_authenticate(ClientInfo, AuthResult) ->
|
||||||
%% XXX: Bool is missing more information about the atom of the result
|
%% XXX: Bool is missing more information about the atom of the result
|
||||||
%% So, the `Req` has missed detailed info too.
|
%% So, the `Req` has missed detailed info too.
|
|
@ -0,0 +1,62 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2017-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_exhook_schema).
|
||||||
|
|
||||||
|
-dialyzer(no_return).
|
||||||
|
-dialyzer(no_match).
|
||||||
|
-dialyzer(no_contracts).
|
||||||
|
-dialyzer(no_unused).
|
||||||
|
-dialyzer(no_fail_call).
|
||||||
|
|
||||||
|
-include_lib("typerefl/include/types.hrl").
|
||||||
|
|
||||||
|
-behaviour(hocon_schema).
|
||||||
|
|
||||||
|
-export([structs/0, fields/1]).
|
||||||
|
-export([t/1, t/3, t/4, ref/1]).
|
||||||
|
|
||||||
|
structs() -> [server].
|
||||||
|
|
||||||
|
fields(server) ->
|
||||||
|
[{"$name", t(ref(server_structs))}];
|
||||||
|
|
||||||
|
fields(server_structs) ->
|
||||||
|
[ {url, t(string(), "emqx_exhook.url", "")}
|
||||||
|
, {ssl, t(ref(ssl_conf_group))}
|
||||||
|
];
|
||||||
|
|
||||||
|
fields(ssl_conf_group) ->
|
||||||
|
[ {cacertfile, string()}
|
||||||
|
, {certfile, string()}
|
||||||
|
, {keyfile, string()}
|
||||||
|
].
|
||||||
|
|
||||||
|
%% types
|
||||||
|
|
||||||
|
t(Type) -> #{type => Type}.
|
||||||
|
|
||||||
|
t(Type, Mapping, Default) ->
|
||||||
|
hoconsc:t(Type, #{mapping => Mapping, default => Default}).
|
||||||
|
|
||||||
|
t(Type, Mapping, Default, OverrideEnv) ->
|
||||||
|
hoconsc:t(Type, #{ mapping => Mapping
|
||||||
|
, default => Default
|
||||||
|
, override_env => OverrideEnv
|
||||||
|
}).
|
||||||
|
|
||||||
|
ref(Field) ->
|
||||||
|
hoconsc:ref(?MODULE, Field).
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_exhook_server).
|
-module(emqx_exhook_server).
|
||||||
|
|
||||||
-include("src/exhook/include/emqx_exhook.hrl").
|
-include("emqx_exhook.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,13 +74,17 @@
|
||||||
|
|
||||||
-export_type([server/0]).
|
-export_type([server/0]).
|
||||||
|
|
||||||
|
-type options() :: #{ url := uri_string:uri_string()
|
||||||
|
, ssl => map()
|
||||||
|
}.
|
||||||
|
|
||||||
-dialyzer({nowarn_function, [inc_metrics/2]}).
|
-dialyzer({nowarn_function, [inc_metrics/2]}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Load/Unload APIs
|
%% Load/Unload APIs
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec load(atom(), list()) -> {ok, server()} | {error, term()} .
|
-spec load(atom(), options()) -> {ok, server()} | {error, term()} .
|
||||||
load(Name0, Opts0) ->
|
load(Name0, Opts0) ->
|
||||||
Name = to_list(Name0),
|
Name = to_list(Name0),
|
||||||
{SvrAddr, ClientOpts} = channel_opts(Opts0),
|
{SvrAddr, ClientOpts} = channel_opts(Opts0),
|
||||||
|
@ -117,20 +121,27 @@ to_list(Name) when is_list(Name) ->
|
||||||
Name.
|
Name.
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
channel_opts(Opts) ->
|
channel_opts(Opts = #{url := URL}) ->
|
||||||
Scheme = proplists:get_value(scheme, Opts),
|
io:format("~p~n", [Opts]),
|
||||||
Host = proplists:get_value(host, Opts),
|
case uri_string:parse(URL) of
|
||||||
Port = proplists:get_value(port, Opts),
|
#{scheme := <<"http">>, host := Host, port := Port} ->
|
||||||
SvrAddr = format_http_uri(Scheme, Host, Port),
|
{format_http_uri("http", Host, Port), #{}};
|
||||||
ClientOpts = case Scheme of
|
#{scheme := <<"https">>, host := Host, port := Port} ->
|
||||||
https ->
|
SslOpts =
|
||||||
SslOpts = lists:keydelete(ssl, 1, proplists:get_value(ssl_options, Opts, [])),
|
case maps:get(ssl, Opts, undefined) of
|
||||||
#{gun_opts =>
|
undefined -> [];
|
||||||
#{transport => ssl,
|
MapOpts ->
|
||||||
transport_opts => SslOpts}};
|
filter(
|
||||||
_ -> #{}
|
[{cacertfile, maps:get(cacertfile, MapOpts, undefined)},
|
||||||
end,
|
{certfile, maps:get(certfile, MapOpts, undefined)},
|
||||||
{SvrAddr, ClientOpts}.
|
{keyfile, maps:get(keyfile, MapOpts, undefined)}
|
||||||
|
])
|
||||||
|
end,
|
||||||
|
{format_http_uri("https", Host, Port),
|
||||||
|
#{gun_opts => #{transport => ssl, transport_opts => SslOpts}}};
|
||||||
|
_ ->
|
||||||
|
error(bad_server_url)
|
||||||
|
end.
|
||||||
|
|
||||||
format_http_uri(Scheme, Host0, Port) ->
|
format_http_uri(Scheme, Host0, Port) ->
|
||||||
Host = case is_tuple(Host0) of
|
Host = case is_tuple(Host0) of
|
||||||
|
@ -139,6 +150,9 @@ format_http_uri(Scheme, Host0, Port) ->
|
||||||
end,
|
end,
|
||||||
lists:flatten(io_lib:format("~s://~s:~w", [Scheme, Host, Port])).
|
lists:flatten(io_lib:format("~s://~s:~w", [Scheme, Host, Port])).
|
||||||
|
|
||||||
|
filter(Ls) ->
|
||||||
|
[ E || E <- Ls, E /= undefined].
|
||||||
|
|
||||||
-spec unload(server()) -> ok.
|
-spec unload(server()) -> ok.
|
||||||
unload(#server{name = Name, hookspec = HookSpecs}) ->
|
unload(#server{name = Name, hookspec = HookSpecs}) ->
|
||||||
_ = do_deinit(Name),
|
_ = do_deinit(Name),
|
|
@ -0,0 +1,96 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_exhook_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Setups
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_suite(Cfg) ->
|
||||||
|
_ = emqx_exhook_demo_svr:start(),
|
||||||
|
emqx_ct_helpers:start_apps([emqx_exhook], fun set_special_cfgs/1),
|
||||||
|
Cfg.
|
||||||
|
|
||||||
|
end_per_suite(_Cfg) ->
|
||||||
|
emqx_ct_helpers:stop_apps([emqx_exhook]),
|
||||||
|
emqx_exhook_demo_svr:stop().
|
||||||
|
|
||||||
|
set_special_cfgs(emqx) ->
|
||||||
|
application:set_env(emqx, allow_anonymous, false),
|
||||||
|
application:set_env(emqx, enable_acl_cache, false),
|
||||||
|
application:set_env(emqx, plugins_loaded_file, undefined),
|
||||||
|
application:set_env(emqx, modules_loaded_file, undefined);
|
||||||
|
set_special_cfgs(emqx_exhook) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
t_noserver_nohook(_) ->
|
||||||
|
emqx_exhook:disable(default),
|
||||||
|
?assertEqual([], ets:tab2list(emqx_hooks)),
|
||||||
|
|
||||||
|
Opts = proplists:get_value(
|
||||||
|
default,
|
||||||
|
application:get_env(emqx_exhook, servers, [])
|
||||||
|
),
|
||||||
|
ok = emqx_exhook:enable(default, Opts),
|
||||||
|
?assertNotEqual([], ets:tab2list(emqx_hooks)).
|
||||||
|
|
||||||
|
t_cli_list(_) ->
|
||||||
|
meck_print(),
|
||||||
|
?assertEqual( [[emqx_exhook_server:format(Svr) || Svr <- emqx_exhook:list()]]
|
||||||
|
, emqx_exhook_cli:cli(["server", "list"])
|
||||||
|
),
|
||||||
|
unmeck_print().
|
||||||
|
|
||||||
|
t_cli_enable_disable(_) ->
|
||||||
|
meck_print(),
|
||||||
|
?assertEqual([already_started], emqx_exhook_cli:cli(["server", "enable", "default"])),
|
||||||
|
?assertEqual(ok, emqx_exhook_cli:cli(["server", "disable", "default"])),
|
||||||
|
?assertEqual([], emqx_exhook_cli:cli(["server", "list"])),
|
||||||
|
|
||||||
|
?assertEqual([not_running], emqx_exhook_cli:cli(["server", "disable", "default"])),
|
||||||
|
?assertEqual(ok, emqx_exhook_cli:cli(["server", "enable", "default"])),
|
||||||
|
unmeck_print().
|
||||||
|
|
||||||
|
t_cli_stats(_) ->
|
||||||
|
meck_print(),
|
||||||
|
_ = emqx_exhook_cli:cli(["server", "stats"]),
|
||||||
|
_ = emqx_exhook_cli:cli(x),
|
||||||
|
unmeck_print().
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Utils
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
meck_print() ->
|
||||||
|
meck:new(emqx_ctl, [passthrough, no_history, no_link]),
|
||||||
|
meck:expect(emqx_ctl, print, fun(_) -> ok end),
|
||||||
|
meck:expect(emqx_ctl, print, fun(_, Args) -> Args end).
|
||||||
|
|
||||||
|
unmeck_print() ->
|
||||||
|
meck:unload(emqx_ctl).
|
|
@ -0,0 +1,339 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_exhook_demo_svr).
|
||||||
|
|
||||||
|
-behavior(emqx_exhook_v_1_hook_provider_bhvr).
|
||||||
|
|
||||||
|
%%
|
||||||
|
-export([ start/0
|
||||||
|
, stop/0
|
||||||
|
, take/0
|
||||||
|
, in/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% gRPC server HookProvider callbacks
|
||||||
|
-export([ on_provider_loaded/2
|
||||||
|
, on_provider_unloaded/2
|
||||||
|
, on_client_connect/2
|
||||||
|
, on_client_connack/2
|
||||||
|
, on_client_connected/2
|
||||||
|
, on_client_disconnected/2
|
||||||
|
, on_client_authenticate/2
|
||||||
|
, on_client_check_acl/2
|
||||||
|
, on_client_subscribe/2
|
||||||
|
, on_client_unsubscribe/2
|
||||||
|
, on_session_created/2
|
||||||
|
, on_session_subscribed/2
|
||||||
|
, on_session_unsubscribed/2
|
||||||
|
, on_session_resumed/2
|
||||||
|
, on_session_discarded/2
|
||||||
|
, on_session_takeovered/2
|
||||||
|
, on_session_terminated/2
|
||||||
|
, on_message_publish/2
|
||||||
|
, on_message_delivered/2
|
||||||
|
, on_message_dropped/2
|
||||||
|
, on_message_acked/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
-define(PORT, 9000).
|
||||||
|
-define(NAME, ?MODULE).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Server APIs
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
start() ->
|
||||||
|
Pid = spawn(fun mngr_main/0),
|
||||||
|
register(?MODULE, Pid),
|
||||||
|
{ok, Pid}.
|
||||||
|
|
||||||
|
stop() ->
|
||||||
|
grpc:stop_server(?NAME),
|
||||||
|
?MODULE ! stop.
|
||||||
|
|
||||||
|
take() ->
|
||||||
|
?MODULE ! {take, self()},
|
||||||
|
receive {value, V} -> V
|
||||||
|
after 5000 -> error(timeout) end.
|
||||||
|
|
||||||
|
in({FunName, Req}) ->
|
||||||
|
?MODULE ! {in, FunName, Req}.
|
||||||
|
|
||||||
|
mngr_main() ->
|
||||||
|
application:ensure_all_started(grpc),
|
||||||
|
Services = #{protos => [emqx_exhook_pb],
|
||||||
|
services => #{'emqx.exhook.v1.HookProvider' => emqx_exhook_demo_svr}
|
||||||
|
},
|
||||||
|
Options = [],
|
||||||
|
Svr = grpc:start_server(?NAME, ?PORT, Services, Options),
|
||||||
|
mngr_loop([Svr, queue:new(), queue:new()]).
|
||||||
|
|
||||||
|
mngr_loop([Svr, Q, Takes]) ->
|
||||||
|
receive
|
||||||
|
{in, FunName, Req} ->
|
||||||
|
{NQ1, NQ2} = reply(queue:in({FunName, Req}, Q), Takes),
|
||||||
|
mngr_loop([Svr, NQ1, NQ2]);
|
||||||
|
{take, From} ->
|
||||||
|
{NQ1, NQ2} = reply(Q, queue:in(From, Takes)),
|
||||||
|
mngr_loop([Svr, NQ1, NQ2]);
|
||||||
|
stop ->
|
||||||
|
exit(normal)
|
||||||
|
end.
|
||||||
|
|
||||||
|
reply(Q1, Q2) ->
|
||||||
|
case queue:len(Q1) =:= 0 orelse
|
||||||
|
queue:len(Q2) =:= 0 of
|
||||||
|
true -> {Q1, Q2};
|
||||||
|
_ ->
|
||||||
|
{{value, {Name, V}}, NQ1} = queue:out(Q1),
|
||||||
|
{{value, From}, NQ2} = queue:out(Q2),
|
||||||
|
From ! {value, {Name, V}},
|
||||||
|
{NQ1, NQ2}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% callbacks
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-spec on_provider_loaded(emqx_exhook_pb:provider_loaded_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:loaded_response(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
|
||||||
|
on_provider_loaded(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{hooks => [
|
||||||
|
#{name => <<"client.connect">>},
|
||||||
|
#{name => <<"client.connack">>},
|
||||||
|
#{name => <<"client.connected">>},
|
||||||
|
#{name => <<"client.disconnected">>},
|
||||||
|
#{name => <<"client.authenticate">>},
|
||||||
|
#{name => <<"client.check_acl">>},
|
||||||
|
#{name => <<"client.subscribe">>},
|
||||||
|
#{name => <<"client.unsubscribe">>},
|
||||||
|
#{name => <<"session.created">>},
|
||||||
|
#{name => <<"session.subscribed">>},
|
||||||
|
#{name => <<"session.unsubscribed">>},
|
||||||
|
#{name => <<"session.resumed">>},
|
||||||
|
#{name => <<"session.discarded">>},
|
||||||
|
#{name => <<"session.takeovered">>},
|
||||||
|
#{name => <<"session.terminated">>},
|
||||||
|
#{name => <<"message.publish">>},
|
||||||
|
#{name => <<"message.delivered">>},
|
||||||
|
#{name => <<"message.acked">>},
|
||||||
|
#{name => <<"message.dropped">>}]}, 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) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_client_connect(emqx_exhook_pb:client_connect_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_client_connect(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_client_connack(emqx_exhook_pb:client_connack_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_client_connack(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_client_connected(emqx_exhook_pb:client_connected_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_client_connected(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_client_disconnected(emqx_exhook_pb:client_disconnected_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_client_disconnected(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_client_authenticate(emqx_exhook_pb:client_authenticate_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_client_authenticate(#{clientinfo := #{username := Username}} = Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
%% some cases for testing
|
||||||
|
case Username of
|
||||||
|
<<"baduser">> ->
|
||||||
|
{ok, #{type => 'STOP_AND_RETURN',
|
||||||
|
value => {bool_result, false}}, Md};
|
||||||
|
<<"gooduser">> ->
|
||||||
|
{ok, #{type => 'STOP_AND_RETURN',
|
||||||
|
value => {bool_result, true}}, Md};
|
||||||
|
<<"normaluser">> ->
|
||||||
|
{ok, #{type => 'CONTINUE',
|
||||||
|
value => {bool_result, true}}, Md};
|
||||||
|
_ ->
|
||||||
|
{ok, #{type => 'IGNORE'}, Md}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec on_client_check_acl(emqx_exhook_pb:client_check_acl_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_client_check_acl(#{clientinfo := #{username := Username}} = Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
%% some cases for testing
|
||||||
|
case Username of
|
||||||
|
<<"baduser">> ->
|
||||||
|
{ok, #{type => 'STOP_AND_RETURN',
|
||||||
|
value => {bool_result, false}}, Md};
|
||||||
|
<<"gooduser">> ->
|
||||||
|
{ok, #{type => 'STOP_AND_RETURN',
|
||||||
|
value => {bool_result, true}}, Md};
|
||||||
|
<<"normaluser">> ->
|
||||||
|
{ok, #{type => 'CONTINUE',
|
||||||
|
value => {bool_result, true}}, Md};
|
||||||
|
_ ->
|
||||||
|
{ok, #{type => 'IGNORE'}, Md}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec on_client_subscribe(emqx_exhook_pb:client_subscribe_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_client_subscribe(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_client_unsubscribe(emqx_exhook_pb:client_unsubscribe_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_client_unsubscribe(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_session_created(emqx_exhook_pb:session_created_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_session_created(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_session_subscribed(emqx_exhook_pb:session_subscribed_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_session_subscribed(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_session_unsubscribed(emqx_exhook_pb:session_unsubscribed_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_session_unsubscribed(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_session_resumed(emqx_exhook_pb:session_resumed_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_session_resumed(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_session_discarded(emqx_exhook_pb:session_discarded_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_session_discarded(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_session_takeovered(emqx_exhook_pb:session_takeovered_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_session_takeovered(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_session_terminated(emqx_exhook_pb:session_terminated_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_session_terminated(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_message_publish(emqx_exhook_pb:message_publish_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_message_publish(#{message := #{from := From} = Msg} = Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
%% some cases for testing
|
||||||
|
case From of
|
||||||
|
<<"baduser">> ->
|
||||||
|
NMsg = Msg#{qos => 0,
|
||||||
|
topic => <<"">>,
|
||||||
|
payload => <<"">>
|
||||||
|
},
|
||||||
|
{ok, #{type => 'STOP_AND_RETURN',
|
||||||
|
value => {message, NMsg}}, Md};
|
||||||
|
<<"gooduser">> ->
|
||||||
|
NMsg = Msg#{topic => From,
|
||||||
|
payload => From},
|
||||||
|
{ok, #{type => 'STOP_AND_RETURN',
|
||||||
|
value => {message, NMsg}}, Md};
|
||||||
|
_ ->
|
||||||
|
{ok, #{type => 'IGNORE'}, Md}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec on_message_delivered(emqx_exhook_pb:message_delivered_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_message_delivered(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_message_dropped(emqx_exhook_pb:message_dropped_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_message_dropped(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
||||||
|
|
||||||
|
-spec on_message_acked(emqx_exhook_pb:message_acked_request(), grpc:metadata())
|
||||||
|
-> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
||||||
|
| {error, grpc_cowboy_h:error_response()}.
|
||||||
|
on_message_acked(Req, Md) ->
|
||||||
|
?MODULE:in({?FUNCTION_NAME, Req}),
|
||||||
|
%io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
||||||
|
{ok, #{}, Md}.
|
|
@ -0,0 +1,531 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(prop_exhook_hooks).
|
||||||
|
|
||||||
|
-include_lib("proper/include/proper.hrl").
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
-import(emqx_ct_proper_types,
|
||||||
|
[ conninfo/0
|
||||||
|
, clientinfo/0
|
||||||
|
, sessioninfo/0
|
||||||
|
, message/0
|
||||||
|
, connack_return_code/0
|
||||||
|
, topictab/0
|
||||||
|
, topic/0
|
||||||
|
, subopts/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
-define(ALL(Vars, Types, Exprs),
|
||||||
|
?SETUP(fun() ->
|
||||||
|
State = do_setup(),
|
||||||
|
fun() -> do_teardown(State) end
|
||||||
|
end, ?FORALL(Vars, Types, Exprs))).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Properties
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
prop_client_connect() ->
|
||||||
|
?ALL({ConnInfo, ConnProps},
|
||||||
|
{conninfo(), conn_properties()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('client.connect', [ConnInfo, ConnProps]),
|
||||||
|
{'on_client_connect', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{props => properties(ConnProps),
|
||||||
|
conninfo => from_conninfo(ConnInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_client_connack() ->
|
||||||
|
?ALL({ConnInfo, Rc, AckProps},
|
||||||
|
{conninfo(), connack_return_code(), ack_properties()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('client.connack', [ConnInfo, Rc, AckProps]),
|
||||||
|
{'on_client_connack', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{props => properties(AckProps),
|
||||||
|
result_code => atom_to_binary(Rc, utf8),
|
||||||
|
conninfo => from_conninfo(ConnInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_client_authenticate() ->
|
||||||
|
?ALL({ClientInfo0, AuthResult},
|
||||||
|
{clientinfo(), authresult()},
|
||||||
|
begin
|
||||||
|
ClientInfo = inject_magic_into(username, ClientInfo0),
|
||||||
|
OutAuthResult = emqx_hooks:run_fold('client.authenticate', [ClientInfo], AuthResult),
|
||||||
|
ExpectedAuthResult = case maps:get(username, ClientInfo) of
|
||||||
|
<<"baduser">> ->
|
||||||
|
AuthResult#{
|
||||||
|
auth_result => not_authorized,
|
||||||
|
anonymous => false};
|
||||||
|
<<"gooduser">> ->
|
||||||
|
AuthResult#{
|
||||||
|
auth_result => success,
|
||||||
|
anonymous => false};
|
||||||
|
<<"normaluser">> ->
|
||||||
|
AuthResult#{
|
||||||
|
auth_result => success,
|
||||||
|
anonymous => false};
|
||||||
|
_ ->
|
||||||
|
case maps:get(auth_result, AuthResult) of
|
||||||
|
success ->
|
||||||
|
#{auth_result => success,
|
||||||
|
anonymous => false};
|
||||||
|
_ ->
|
||||||
|
#{auth_result => not_authorized,
|
||||||
|
anonymous => false}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
?assertEqual(ExpectedAuthResult, OutAuthResult),
|
||||||
|
|
||||||
|
{'on_client_authenticate', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{result => authresult_to_bool(AuthResult),
|
||||||
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_client_check_acl() ->
|
||||||
|
?ALL({ClientInfo0, PubSub, Topic, Result},
|
||||||
|
{clientinfo(), oneof([publish, subscribe]),
|
||||||
|
topic(), oneof([allow, deny])},
|
||||||
|
begin
|
||||||
|
ClientInfo = inject_magic_into(username, ClientInfo0),
|
||||||
|
OutResult = emqx_hooks:run_fold(
|
||||||
|
'client.check_acl',
|
||||||
|
[ClientInfo, PubSub, Topic],
|
||||||
|
Result),
|
||||||
|
ExpectedOutResult = case maps:get(username, ClientInfo) of
|
||||||
|
<<"baduser">> -> deny;
|
||||||
|
<<"gooduser">> -> allow;
|
||||||
|
<<"normaluser">> -> allow;
|
||||||
|
_ -> Result
|
||||||
|
end,
|
||||||
|
?assertEqual(ExpectedOutResult, OutResult),
|
||||||
|
|
||||||
|
{'on_client_check_acl', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{result => aclresult_to_bool(Result),
|
||||||
|
type => pubsub_to_enum(PubSub),
|
||||||
|
topic => Topic,
|
||||||
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_client_connected() ->
|
||||||
|
?ALL({ClientInfo, ConnInfo},
|
||||||
|
{clientinfo(), conninfo()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('client.connected', [ClientInfo, ConnInfo]),
|
||||||
|
{'on_client_connected', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_client_disconnected() ->
|
||||||
|
?ALL({ClientInfo, Reason, ConnInfo},
|
||||||
|
{clientinfo(), shutdown_reason(), conninfo()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]),
|
||||||
|
{'on_client_disconnected', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{reason => stringfy(Reason),
|
||||||
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_client_subscribe() ->
|
||||||
|
?ALL({ClientInfo, SubProps, TopicTab},
|
||||||
|
{clientinfo(), sub_properties(), topictab()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('client.subscribe', [ClientInfo, SubProps, TopicTab]),
|
||||||
|
{'on_client_subscribe', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{props => properties(SubProps),
|
||||||
|
topic_filters => topicfilters(TopicTab),
|
||||||
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_client_unsubscribe() ->
|
||||||
|
?ALL({ClientInfo, UnSubProps, TopicTab},
|
||||||
|
{clientinfo(), unsub_properties(), topictab()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('client.unsubscribe', [ClientInfo, UnSubProps, TopicTab]),
|
||||||
|
{'on_client_unsubscribe', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{props => properties(UnSubProps),
|
||||||
|
topic_filters => topicfilters(TopicTab),
|
||||||
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_session_created() ->
|
||||||
|
?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('session.created', [ClientInfo, SessInfo]),
|
||||||
|
{'on_session_created', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_session_subscribed() ->
|
||||||
|
?ALL({ClientInfo, Topic, SubOpts},
|
||||||
|
{clientinfo(), topic(), subopts()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('session.subscribed', [ClientInfo, Topic, SubOpts]),
|
||||||
|
{'on_session_subscribed', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{topic => Topic,
|
||||||
|
subopts => subopts(SubOpts),
|
||||||
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_session_unsubscribed() ->
|
||||||
|
?ALL({ClientInfo, Topic, SubOpts},
|
||||||
|
{clientinfo(), topic(), subopts()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('session.unsubscribed', [ClientInfo, Topic, SubOpts]),
|
||||||
|
{'on_session_unsubscribed', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{topic => Topic,
|
||||||
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_session_resumed() ->
|
||||||
|
?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('session.resumed', [ClientInfo, SessInfo]),
|
||||||
|
{'on_session_resumed', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_session_discared() ->
|
||||||
|
?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('session.discarded', [ClientInfo, SessInfo]),
|
||||||
|
{'on_session_discarded', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_session_takeovered() ->
|
||||||
|
?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('session.takeovered', [ClientInfo, SessInfo]),
|
||||||
|
{'on_session_takeovered', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_session_terminated() ->
|
||||||
|
?ALL({ClientInfo, Reason, SessInfo},
|
||||||
|
{clientinfo(), shutdown_reason(), sessioninfo()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('session.terminated', [ClientInfo, Reason, SessInfo]),
|
||||||
|
{'on_session_terminated', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{reason => stringfy(Reason),
|
||||||
|
clientinfo => from_clientinfo(ClientInfo)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp),
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_message_publish() ->
|
||||||
|
?ALL(Msg0, message(),
|
||||||
|
begin
|
||||||
|
Msg = emqx_message:from_map(
|
||||||
|
inject_magic_into(from, emqx_message:to_map(Msg0))),
|
||||||
|
OutMsg= emqx_hooks:run_fold('message.publish', [], Msg),
|
||||||
|
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
||||||
|
true ->
|
||||||
|
?assertEqual(Msg, OutMsg),
|
||||||
|
skip;
|
||||||
|
_ ->
|
||||||
|
ExpectedOutMsg = case emqx_message:from(Msg) of
|
||||||
|
<<"baduser">> ->
|
||||||
|
MsgMap = emqx_message:to_map(Msg),
|
||||||
|
emqx_message:from_map(
|
||||||
|
MsgMap#{qos => 0,
|
||||||
|
topic => <<"">>,
|
||||||
|
payload => <<"">>
|
||||||
|
});
|
||||||
|
<<"gooduser">> = From ->
|
||||||
|
MsgMap = emqx_message:to_map(Msg),
|
||||||
|
emqx_message:from_map(
|
||||||
|
MsgMap#{topic => From,
|
||||||
|
payload => From
|
||||||
|
});
|
||||||
|
_ -> Msg
|
||||||
|
end,
|
||||||
|
?assertEqual(ExpectedOutMsg, OutMsg),
|
||||||
|
|
||||||
|
{'on_message_publish', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{message => from_message(Msg)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp)
|
||||||
|
end,
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_message_dropped() ->
|
||||||
|
?ALL({Msg, By, Reason}, {message(), hardcoded, shutdown_reason()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('message.dropped', [Msg, By, Reason]),
|
||||||
|
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
||||||
|
true -> skip;
|
||||||
|
_ ->
|
||||||
|
{'on_message_dropped', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{reason => stringfy(Reason),
|
||||||
|
message => from_message(Msg)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp)
|
||||||
|
end,
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_message_delivered() ->
|
||||||
|
?ALL({ClientInfo, Msg}, {clientinfo(), message()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('message.delivered', [ClientInfo, Msg]),
|
||||||
|
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
||||||
|
true -> skip;
|
||||||
|
_ ->
|
||||||
|
{'on_message_delivered', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{clientinfo => from_clientinfo(ClientInfo),
|
||||||
|
message => from_message(Msg)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp)
|
||||||
|
end,
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
prop_message_acked() ->
|
||||||
|
?ALL({ClientInfo, Msg}, {clientinfo(), message()},
|
||||||
|
begin
|
||||||
|
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
|
||||||
|
case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
||||||
|
true -> skip;
|
||||||
|
_ ->
|
||||||
|
{'on_message_acked', Resp} = emqx_exhook_demo_svr:take(),
|
||||||
|
Expected =
|
||||||
|
#{clientinfo => from_clientinfo(ClientInfo),
|
||||||
|
message => from_message(Msg)
|
||||||
|
},
|
||||||
|
?assertEqual(Expected, Resp)
|
||||||
|
end,
|
||||||
|
true
|
||||||
|
end).
|
||||||
|
|
||||||
|
nodestr() ->
|
||||||
|
stringfy(node()).
|
||||||
|
|
||||||
|
peerhost(#{peername := {Host, _}}) ->
|
||||||
|
ntoa(Host).
|
||||||
|
|
||||||
|
sockport(#{sockname := {_, Port}}) ->
|
||||||
|
Port.
|
||||||
|
|
||||||
|
%% copied from emqx_exhook
|
||||||
|
|
||||||
|
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}));
|
||||||
|
ntoa(IP) ->
|
||||||
|
list_to_binary(inet_parse:ntoa(IP)).
|
||||||
|
|
||||||
|
maybe(undefined) -> <<>>;
|
||||||
|
maybe(B) -> B.
|
||||||
|
|
||||||
|
properties(undefined) -> [];
|
||||||
|
properties(M) when is_map(M) ->
|
||||||
|
maps:fold(fun(K, V, Acc) ->
|
||||||
|
[#{name => stringfy(K),
|
||||||
|
value => stringfy(V)} | Acc]
|
||||||
|
end, [], M).
|
||||||
|
|
||||||
|
topicfilters(Tfs) when is_list(Tfs) ->
|
||||||
|
[#{name => Topic, qos => Qos} || {Topic, #{qos := Qos}} <- Tfs].
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
stringfy(Term) when is_binary(Term) ->
|
||||||
|
Term;
|
||||||
|
stringfy(Term) when is_integer(Term) ->
|
||||||
|
integer_to_binary(Term);
|
||||||
|
stringfy(Term) when is_atom(Term) ->
|
||||||
|
atom_to_binary(Term, utf8);
|
||||||
|
stringfy(Term) ->
|
||||||
|
unicode:characters_to_binary((io_lib:format("~0p", [Term]))).
|
||||||
|
|
||||||
|
subopts(SubOpts) ->
|
||||||
|
#{qos => maps:get(qos, SubOpts, 0),
|
||||||
|
rh => maps:get(rh, SubOpts, 0),
|
||||||
|
rap => maps:get(rap, SubOpts, 0),
|
||||||
|
nl => maps:get(nl, SubOpts, 0),
|
||||||
|
share => maps:get(share, SubOpts, <<>>)
|
||||||
|
}.
|
||||||
|
|
||||||
|
authresult_to_bool(AuthResult) ->
|
||||||
|
maps:get(auth_result, AuthResult, undefined) == success.
|
||||||
|
|
||||||
|
aclresult_to_bool(Result) ->
|
||||||
|
Result == allow.
|
||||||
|
|
||||||
|
pubsub_to_enum(publish) -> 'PUBLISH';
|
||||||
|
pubsub_to_enum(subscribe) -> 'SUBSCRIBE'.
|
||||||
|
|
||||||
|
from_conninfo(ConnInfo) ->
|
||||||
|
#{node => nodestr(),
|
||||||
|
clientid => maps:get(clientid, ConnInfo),
|
||||||
|
username => maybe(maps:get(username, ConnInfo, <<>>)),
|
||||||
|
peerhost => peerhost(ConnInfo),
|
||||||
|
sockport => sockport(ConnInfo),
|
||||||
|
proto_name => maps:get(proto_name, ConnInfo),
|
||||||
|
proto_ver => stringfy(maps:get(proto_ver, ConnInfo)),
|
||||||
|
keepalive => maps:get(keepalive, ConnInfo)
|
||||||
|
}.
|
||||||
|
|
||||||
|
from_clientinfo(ClientInfo) ->
|
||||||
|
#{node => nodestr(),
|
||||||
|
clientid => maps:get(clientid, ClientInfo),
|
||||||
|
username => maybe(maps:get(username, ClientInfo, <<>>)),
|
||||||
|
password => maybe(maps:get(password, ClientInfo, <<>>)),
|
||||||
|
peerhost => ntoa(maps:get(peerhost, ClientInfo)),
|
||||||
|
sockport => maps:get(sockport, ClientInfo),
|
||||||
|
protocol => stringfy(maps:get(protocol, ClientInfo)),
|
||||||
|
mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
|
||||||
|
is_superuser => maps:get(is_superuser, ClientInfo, false),
|
||||||
|
anonymous => maps:get(anonymous, ClientInfo, true),
|
||||||
|
cn => maybe(maps:get(cn, ClientInfo, <<>>)),
|
||||||
|
dn => maybe(maps:get(dn, ClientInfo, <<>>))
|
||||||
|
}.
|
||||||
|
|
||||||
|
from_message(Msg) ->
|
||||||
|
#{node => nodestr(),
|
||||||
|
id => emqx_guid:to_hexstr(emqx_message:id(Msg)),
|
||||||
|
qos => emqx_message:qos(Msg),
|
||||||
|
from => stringfy(emqx_message:from(Msg)),
|
||||||
|
topic => emqx_message:topic(Msg),
|
||||||
|
payload => emqx_message:payload(Msg),
|
||||||
|
timestamp => emqx_message:timestamp(Msg)
|
||||||
|
}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Helper
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
do_setup() ->
|
||||||
|
logger:set_primary_config(#{level => warning}),
|
||||||
|
_ = emqx_exhook_demo_svr:start(),
|
||||||
|
emqx_ct_helpers:start_apps([emqx_exhook], fun set_special_cfgs/1),
|
||||||
|
%% waiting first loaded event
|
||||||
|
{'on_provider_loaded', _} = emqx_exhook_demo_svr:take(),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
do_teardown(_) ->
|
||||||
|
emqx_ct_helpers:stop_apps([emqx_exhook]),
|
||||||
|
%% waiting last unloaded event
|
||||||
|
{'on_provider_unloaded', _} = emqx_exhook_demo_svr:take(),
|
||||||
|
_ = emqx_exhook_demo_svr:stop(),
|
||||||
|
logger:set_primary_config(#{level => notice}),
|
||||||
|
timer:sleep(2000),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
set_special_cfgs(emqx) ->
|
||||||
|
application:set_env(emqx, allow_anonymous, false),
|
||||||
|
application:set_env(emqx, enable_acl_cache, false),
|
||||||
|
application:set_env(emqx, modules_loaded_file, undefined),
|
||||||
|
application:set_env(emqx, plugins_loaded_file,
|
||||||
|
emqx_ct_helpers:deps_path(emqx, "test/emqx_SUITE_data/loaded_plugins"));
|
||||||
|
set_special_cfgs(emqx_exhook) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Generators
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
conn_properties() ->
|
||||||
|
#{}.
|
||||||
|
|
||||||
|
ack_properties() ->
|
||||||
|
#{}.
|
||||||
|
|
||||||
|
sub_properties() ->
|
||||||
|
#{}.
|
||||||
|
|
||||||
|
unsub_properties() ->
|
||||||
|
#{}.
|
||||||
|
|
||||||
|
shutdown_reason() ->
|
||||||
|
oneof([utf8(), {shutdown, emqx_ct_proper_types:limited_atom()}]).
|
||||||
|
|
||||||
|
authresult() ->
|
||||||
|
?LET(RC, connack_return_code(), #{auth_result => RC}).
|
||||||
|
|
||||||
|
inject_magic_into(Key, Object) ->
|
||||||
|
case castspell() of
|
||||||
|
muggles -> Object;
|
||||||
|
Spell ->
|
||||||
|
Object#{Key => Spell}
|
||||||
|
end.
|
||||||
|
|
||||||
|
castspell() ->
|
||||||
|
L = [<<"baduser">>, <<"gooduser">>, <<"normaluser">>, muggles],
|
||||||
|
lists:nth(rand:uniform(length(L)), L).
|
|
@ -1,15 +0,0 @@
|
||||||
##====================================================================
|
|
||||||
## EMQ X Hooks
|
|
||||||
##====================================================================
|
|
||||||
|
|
||||||
##--------------------------------------------------------------------
|
|
||||||
## Server Address
|
|
||||||
|
|
||||||
## The gRPC server url
|
|
||||||
##
|
|
||||||
## exhook.server.$name.url = url()
|
|
||||||
exhook.server.default.url = "http://127.0.0.1:9000"
|
|
||||||
|
|
||||||
#exhook.server.default.ssl.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem"
|
|
||||||
#exhook.server.default.ssl.certfile = "{{ platform_etc_dir }}/certs/cert.pem"
|
|
||||||
#exhook.server.default.ssl.keyfile = "{{ platform_etc_dir }}/certs/key.pem"
|
|
|
@ -1,531 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
||||||
%%
|
|
||||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
%% you may not use this file except in compliance with the License.
|
|
||||||
%% You may obtain a copy of the License at
|
|
||||||
%%
|
|
||||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
%%
|
|
||||||
%% Unless required by applicable law or agreed to in writing, software
|
|
||||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
%% See the License for the specific language governing permissions and
|
|
||||||
%% limitations under the License.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(prop_exhook_hooks).
|
|
||||||
|
|
||||||
% -include_lib("proper/include/proper.hrl").
|
|
||||||
% -include_lib("eunit/include/eunit.hrl").
|
|
||||||
|
|
||||||
% -import(emqx_ct_proper_types,
|
|
||||||
% [ conninfo/0
|
|
||||||
% , clientinfo/0
|
|
||||||
% , sessioninfo/0
|
|
||||||
% , message/0
|
|
||||||
% , connack_return_code/0
|
|
||||||
% , topictab/0
|
|
||||||
% , topic/0
|
|
||||||
% , subopts/0
|
|
||||||
% ]).
|
|
||||||
|
|
||||||
% -define(ALL(Vars, Types, Exprs),
|
|
||||||
% ?SETUP(fun() ->
|
|
||||||
% State = do_setup(),
|
|
||||||
% fun() -> do_teardown(State) end
|
|
||||||
% end, ?FORALL(Vars, Types, Exprs))).
|
|
||||||
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
% %% Properties
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
% prop_client_connect() ->
|
|
||||||
% ?ALL({ConnInfo, ConnProps},
|
|
||||||
% {conninfo(), conn_properties()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('client.connect', [ConnInfo, ConnProps]),
|
|
||||||
% {'on_client_connect', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{props => properties(ConnProps),
|
|
||||||
% conninfo => from_conninfo(ConnInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_client_connack() ->
|
|
||||||
% ?ALL({ConnInfo, Rc, AckProps},
|
|
||||||
% {conninfo(), connack_return_code(), ack_properties()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('client.connack', [ConnInfo, Rc, AckProps]),
|
|
||||||
% {'on_client_connack', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{props => properties(AckProps),
|
|
||||||
% result_code => atom_to_binary(Rc, utf8),
|
|
||||||
% conninfo => from_conninfo(ConnInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_client_authenticate() ->
|
|
||||||
% ?ALL({ClientInfo0, AuthResult},
|
|
||||||
% {clientinfo(), authresult()},
|
|
||||||
% begin
|
|
||||||
% ClientInfo = inject_magic_into(username, ClientInfo0),
|
|
||||||
% OutAuthResult = emqx_hooks:run_fold('client.authenticate', [ClientInfo], AuthResult),
|
|
||||||
% ExpectedAuthResult = case maps:get(username, ClientInfo) of
|
|
||||||
% <<"baduser">> ->
|
|
||||||
% AuthResult#{
|
|
||||||
% auth_result => not_authorized,
|
|
||||||
% anonymous => false};
|
|
||||||
% <<"gooduser">> ->
|
|
||||||
% AuthResult#{
|
|
||||||
% auth_result => success,
|
|
||||||
% anonymous => false};
|
|
||||||
% <<"normaluser">> ->
|
|
||||||
% AuthResult#{
|
|
||||||
% auth_result => success,
|
|
||||||
% anonymous => false};
|
|
||||||
% _ ->
|
|
||||||
% case maps:get(auth_result, AuthResult) of
|
|
||||||
% success ->
|
|
||||||
% #{auth_result => success,
|
|
||||||
% anonymous => false};
|
|
||||||
% _ ->
|
|
||||||
% #{auth_result => not_authorized,
|
|
||||||
% anonymous => false}
|
|
||||||
% end
|
|
||||||
% end,
|
|
||||||
% ?assertEqual(ExpectedAuthResult, OutAuthResult),
|
|
||||||
|
|
||||||
% {'on_client_authenticate', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{result => authresult_to_bool(AuthResult),
|
|
||||||
% clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_client_authorize() ->
|
|
||||||
% ?ALL({ClientInfo0, PubSub, Topic, Result},
|
|
||||||
% {clientinfo(), oneof([publish, subscribe]),
|
|
||||||
% topic(), oneof([allow, deny])},
|
|
||||||
% begin
|
|
||||||
% ClientInfo = inject_magic_into(username, ClientInfo0),
|
|
||||||
% OutResult = emqx_hooks:run_fold(
|
|
||||||
% 'client.authorize',
|
|
||||||
% [ClientInfo, PubSub, Topic],
|
|
||||||
% Result),
|
|
||||||
% ExpectedOutResult = case maps:get(username, ClientInfo) of
|
|
||||||
% <<"baduser">> -> deny;
|
|
||||||
% <<"gooduser">> -> allow;
|
|
||||||
% <<"normaluser">> -> allow;
|
|
||||||
% _ -> Result
|
|
||||||
% end,
|
|
||||||
% ?assertEqual(ExpectedOutResult, OutResult),
|
|
||||||
|
|
||||||
% {'on_client_authorize', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{result => authzresult_to_bool(Result),
|
|
||||||
% type => pubsub_to_enum(PubSub),
|
|
||||||
% topic => Topic,
|
|
||||||
% clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_client_connected() ->
|
|
||||||
% ?ALL({ClientInfo, ConnInfo},
|
|
||||||
% {clientinfo(), conninfo()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('client.connected', [ClientInfo, ConnInfo]),
|
|
||||||
% {'on_client_connected', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_client_disconnected() ->
|
|
||||||
% ?ALL({ClientInfo, Reason, ConnInfo},
|
|
||||||
% {clientinfo(), shutdown_reason(), conninfo()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]),
|
|
||||||
% {'on_client_disconnected', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{reason => stringfy(Reason),
|
|
||||||
% clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_client_subscribe() ->
|
|
||||||
% ?ALL({ClientInfo, SubProps, TopicTab},
|
|
||||||
% {clientinfo(), sub_properties(), topictab()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('client.subscribe', [ClientInfo, SubProps, TopicTab]),
|
|
||||||
% {'on_client_subscribe', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{props => properties(SubProps),
|
|
||||||
% topic_filters => topicfilters(TopicTab),
|
|
||||||
% clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_client_unsubscribe() ->
|
|
||||||
% ?ALL({ClientInfo, UnSubProps, TopicTab},
|
|
||||||
% {clientinfo(), unsub_properties(), topictab()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('client.unsubscribe', [ClientInfo, UnSubProps, TopicTab]),
|
|
||||||
% {'on_client_unsubscribe', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{props => properties(UnSubProps),
|
|
||||||
% topic_filters => topicfilters(TopicTab),
|
|
||||||
% clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_session_created() ->
|
|
||||||
% ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('session.created', [ClientInfo, SessInfo]),
|
|
||||||
% {'on_session_created', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_session_subscribed() ->
|
|
||||||
% ?ALL({ClientInfo, Topic, SubOpts},
|
|
||||||
% {clientinfo(), topic(), subopts()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('session.subscribed', [ClientInfo, Topic, SubOpts]),
|
|
||||||
% {'on_session_subscribed', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{topic => Topic,
|
|
||||||
% subopts => subopts(SubOpts),
|
|
||||||
% clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_session_unsubscribed() ->
|
|
||||||
% ?ALL({ClientInfo, Topic, SubOpts},
|
|
||||||
% {clientinfo(), topic(), subopts()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('session.unsubscribed', [ClientInfo, Topic, SubOpts]),
|
|
||||||
% {'on_session_unsubscribed', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{topic => Topic,
|
|
||||||
% clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_session_resumed() ->
|
|
||||||
% ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('session.resumed', [ClientInfo, SessInfo]),
|
|
||||||
% {'on_session_resumed', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_session_discared() ->
|
|
||||||
% ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('session.discarded', [ClientInfo, SessInfo]),
|
|
||||||
% {'on_session_discarded', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_session_takeovered() ->
|
|
||||||
% ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('session.takeovered', [ClientInfo, SessInfo]),
|
|
||||||
% {'on_session_takeovered', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_session_terminated() ->
|
|
||||||
% ?ALL({ClientInfo, Reason, SessInfo},
|
|
||||||
% {clientinfo(), shutdown_reason(), sessioninfo()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('session.terminated', [ClientInfo, Reason, SessInfo]),
|
|
||||||
% {'on_session_terminated', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{reason => stringfy(Reason),
|
|
||||||
% clientinfo => from_clientinfo(ClientInfo)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp),
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_message_publish() ->
|
|
||||||
% ?ALL(Msg0, message(),
|
|
||||||
% begin
|
|
||||||
% Msg = emqx_message:from_map(
|
|
||||||
% inject_magic_into(from, emqx_message:to_map(Msg0))),
|
|
||||||
% OutMsg= emqx_hooks:run_fold('message.publish', [], Msg),
|
|
||||||
% case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
|
||||||
% true ->
|
|
||||||
% ?assertEqual(Msg, OutMsg),
|
|
||||||
% skip;
|
|
||||||
% _ ->
|
|
||||||
% ExpectedOutMsg = case emqx_message:from(Msg) of
|
|
||||||
% <<"baduser">> ->
|
|
||||||
% MsgMap = emqx_message:to_map(Msg),
|
|
||||||
% emqx_message:from_map(
|
|
||||||
% MsgMap#{qos => 0,
|
|
||||||
% topic => <<"">>,
|
|
||||||
% payload => <<"">>
|
|
||||||
% });
|
|
||||||
% <<"gooduser">> = From ->
|
|
||||||
% MsgMap = emqx_message:to_map(Msg),
|
|
||||||
% emqx_message:from_map(
|
|
||||||
% MsgMap#{topic => From,
|
|
||||||
% payload => From
|
|
||||||
% });
|
|
||||||
% _ -> Msg
|
|
||||||
% end,
|
|
||||||
% ?assertEqual(ExpectedOutMsg, OutMsg),
|
|
||||||
|
|
||||||
% {'on_message_publish', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{message => from_message(Msg)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp)
|
|
||||||
% end,
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_message_dropped() ->
|
|
||||||
% ?ALL({Msg, By, Reason}, {message(), hardcoded, shutdown_reason()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('message.dropped', [Msg, By, Reason]),
|
|
||||||
% case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
|
||||||
% true -> skip;
|
|
||||||
% _ ->
|
|
||||||
% {'on_message_dropped', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{reason => stringfy(Reason),
|
|
||||||
% message => from_message(Msg)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp)
|
|
||||||
% end,
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_message_delivered() ->
|
|
||||||
% ?ALL({ClientInfo, Msg}, {clientinfo(), message()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('message.delivered', [ClientInfo, Msg]),
|
|
||||||
% case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
|
||||||
% true -> skip;
|
|
||||||
% _ ->
|
|
||||||
% {'on_message_delivered', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{clientinfo => from_clientinfo(ClientInfo),
|
|
||||||
% message => from_message(Msg)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp)
|
|
||||||
% end,
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% prop_message_acked() ->
|
|
||||||
% ?ALL({ClientInfo, Msg}, {clientinfo(), message()},
|
|
||||||
% begin
|
|
||||||
% ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
|
|
||||||
% case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
|
|
||||||
% true -> skip;
|
|
||||||
% _ ->
|
|
||||||
% {'on_message_acked', Resp} = emqx_exhook_demo_svr:take(),
|
|
||||||
% Expected =
|
|
||||||
% #{clientinfo => from_clientinfo(ClientInfo),
|
|
||||||
% message => from_message(Msg)
|
|
||||||
% },
|
|
||||||
% ?assertEqual(Expected, Resp)
|
|
||||||
% end,
|
|
||||||
% true
|
|
||||||
% end).
|
|
||||||
|
|
||||||
% nodestr() ->
|
|
||||||
% stringfy(node()).
|
|
||||||
|
|
||||||
% peerhost(#{peername := {Host, _}}) ->
|
|
||||||
% ntoa(Host).
|
|
||||||
|
|
||||||
% sockport(#{sockname := {_, Port}}) ->
|
|
||||||
% Port.
|
|
||||||
|
|
||||||
% %% copied from emqx_exhook
|
|
||||||
|
|
||||||
% 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}));
|
|
||||||
% ntoa(IP) ->
|
|
||||||
% list_to_binary(inet_parse:ntoa(IP)).
|
|
||||||
|
|
||||||
% maybe(undefined) -> <<>>;
|
|
||||||
% maybe(B) -> B.
|
|
||||||
|
|
||||||
% properties(undefined) -> [];
|
|
||||||
% properties(M) when is_map(M) ->
|
|
||||||
% maps:fold(fun(K, V, Acc) ->
|
|
||||||
% [#{name => stringfy(K),
|
|
||||||
% value => stringfy(V)} | Acc]
|
|
||||||
% end, [], M).
|
|
||||||
|
|
||||||
% topicfilters(Tfs) when is_list(Tfs) ->
|
|
||||||
% [#{name => Topic, qos => Qos} || {Topic, #{qos := Qos}} <- Tfs].
|
|
||||||
|
|
||||||
% %% @private
|
|
||||||
% stringfy(Term) when is_binary(Term) ->
|
|
||||||
% Term;
|
|
||||||
% stringfy(Term) when is_integer(Term) ->
|
|
||||||
% integer_to_binary(Term);
|
|
||||||
% stringfy(Term) when is_atom(Term) ->
|
|
||||||
% atom_to_binary(Term, utf8);
|
|
||||||
% stringfy(Term) ->
|
|
||||||
% unicode:characters_to_binary((io_lib:format("~0p", [Term]))).
|
|
||||||
|
|
||||||
% subopts(SubOpts) ->
|
|
||||||
% #{qos => maps:get(qos, SubOpts, 0),
|
|
||||||
% rh => maps:get(rh, SubOpts, 0),
|
|
||||||
% rap => maps:get(rap, SubOpts, 0),
|
|
||||||
% nl => maps:get(nl, SubOpts, 0),
|
|
||||||
% share => maps:get(share, SubOpts, <<>>)
|
|
||||||
% }.
|
|
||||||
|
|
||||||
% authresult_to_bool(AuthResult) ->
|
|
||||||
% maps:get(auth_result, AuthResult, undefined) == success.
|
|
||||||
|
|
||||||
% authzresult_to_bool(Result) ->
|
|
||||||
% Result == allow.
|
|
||||||
|
|
||||||
% pubsub_to_enum(publish) -> 'PUBLISH';
|
|
||||||
% pubsub_to_enum(subscribe) -> 'SUBSCRIBE'.
|
|
||||||
|
|
||||||
% from_conninfo(ConnInfo) ->
|
|
||||||
% #{node => nodestr(),
|
|
||||||
% clientid => maps:get(clientid, ConnInfo),
|
|
||||||
% username => maybe(maps:get(username, ConnInfo, <<>>)),
|
|
||||||
% peerhost => peerhost(ConnInfo),
|
|
||||||
% sockport => sockport(ConnInfo),
|
|
||||||
% proto_name => maps:get(proto_name, ConnInfo),
|
|
||||||
% proto_ver => stringfy(maps:get(proto_ver, ConnInfo)),
|
|
||||||
% keepalive => maps:get(keepalive, ConnInfo)
|
|
||||||
% }.
|
|
||||||
|
|
||||||
% from_clientinfo(ClientInfo) ->
|
|
||||||
% #{node => nodestr(),
|
|
||||||
% clientid => maps:get(clientid, ClientInfo),
|
|
||||||
% username => maybe(maps:get(username, ClientInfo, <<>>)),
|
|
||||||
% password => maybe(maps:get(password, ClientInfo, <<>>)),
|
|
||||||
% peerhost => ntoa(maps:get(peerhost, ClientInfo)),
|
|
||||||
% sockport => maps:get(sockport, ClientInfo),
|
|
||||||
% protocol => stringfy(maps:get(protocol, ClientInfo)),
|
|
||||||
% mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
|
|
||||||
% is_superuser => maps:get(is_superuser, ClientInfo, false),
|
|
||||||
% anonymous => maps:get(anonymous, ClientInfo, true),
|
|
||||||
% cn => maybe(maps:get(cn, ClientInfo, <<>>)),
|
|
||||||
% dn => maybe(maps:get(dn, ClientInfo, <<>>))
|
|
||||||
% }.
|
|
||||||
|
|
||||||
% from_message(Msg) ->
|
|
||||||
% #{node => nodestr(),
|
|
||||||
% id => emqx_guid:to_hexstr(emqx_message:id(Msg)),
|
|
||||||
% qos => emqx_message:qos(Msg),
|
|
||||||
% from => stringfy(emqx_message:from(Msg)),
|
|
||||||
% topic => emqx_message:topic(Msg),
|
|
||||||
% payload => emqx_message:payload(Msg),
|
|
||||||
% timestamp => emqx_message:timestamp(Msg)
|
|
||||||
% }.
|
|
||||||
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
% %% Helper
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
% do_setup() ->
|
|
||||||
% logger:set_primary_config(#{level => warning}),
|
|
||||||
% _ = emqx_exhook_demo_svr:start(),
|
|
||||||
% emqx_ct_helpers:start_apps([emqx_exhook], fun set_special_cfgs/1),
|
|
||||||
% %% waiting first loaded event
|
|
||||||
% {'on_provider_loaded', _} = emqx_exhook_demo_svr:take(),
|
|
||||||
% ok.
|
|
||||||
|
|
||||||
% do_teardown(_) ->
|
|
||||||
% emqx_ct_helpers:stop_apps([emqx_exhook]),
|
|
||||||
% %% waiting last unloaded event
|
|
||||||
% {'on_provider_unloaded', _} = emqx_exhook_demo_svr:take(),
|
|
||||||
% _ = emqx_exhook_demo_svr:stop(),
|
|
||||||
% logger:set_primary_config(#{level => notice}),
|
|
||||||
% timer:sleep(2000),
|
|
||||||
% ok.
|
|
||||||
|
|
||||||
% set_special_cfgs(emqx) ->
|
|
||||||
% application:set_env(emqx, allow_anonymous, false),
|
|
||||||
% application:set_env(emqx, enable_authz_cache, false),
|
|
||||||
% application:set_env(emqx, modules_loaded_file, undefined),
|
|
||||||
% application:set_env(emqx, plugins_loaded_file,
|
|
||||||
% emqx_ct_helpers:deps_path(emqx, "test/emqx_SUITE_data/loaded_plugins"));
|
|
||||||
% set_special_cfgs(emqx_exhook) ->
|
|
||||||
% ok.
|
|
||||||
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
% %% Generators
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
% conn_properties() ->
|
|
||||||
% #{}.
|
|
||||||
|
|
||||||
% ack_properties() ->
|
|
||||||
% #{}.
|
|
||||||
|
|
||||||
% sub_properties() ->
|
|
||||||
% #{}.
|
|
||||||
|
|
||||||
% unsub_properties() ->
|
|
||||||
% #{}.
|
|
||||||
|
|
||||||
% shutdown_reason() ->
|
|
||||||
% oneof([utf8(), {shutdown, emqx_ct_proper_types:limited_atom()}]).
|
|
||||||
|
|
||||||
% authresult() ->
|
|
||||||
% ?LET(RC, connack_return_code(), #{auth_result => RC}).
|
|
||||||
|
|
||||||
% inject_magic_into(Key, Object) ->
|
|
||||||
% case castspell() of
|
|
||||||
% muggles -> Object;
|
|
||||||
% Spell ->
|
|
||||||
% Object#{Key => Spell}
|
|
||||||
% end.
|
|
||||||
|
|
||||||
% castspell() ->
|
|
||||||
% L = [<<"baduser">>, <<"gooduser">>, <<"normaluser">>, muggles],
|
|
||||||
% lists:nth(rand:uniform(length(L)), L).
|
|
|
@ -1,97 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
||||||
%%
|
|
||||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
%% you may not use this file except in compliance with the License.
|
|
||||||
%% You may obtain a copy of the License at
|
|
||||||
%%
|
|
||||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
%%
|
|
||||||
%% Unless required by applicable law or agreed to in writing, software
|
|
||||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
%% See the License for the specific language governing permissions and
|
|
||||||
%% limitations under the License.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(emqx_exhook_SUITE).
|
|
||||||
|
|
||||||
% -compile(export_all).
|
|
||||||
% -compile(nowarn_export_all).
|
|
||||||
|
|
||||||
|
|
||||||
% -include_lib("eunit/include/eunit.hrl").
|
|
||||||
% -include_lib("common_test/include/ct.hrl").
|
|
||||||
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
% %% Setups
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
% all() -> emqx_ct:all(?MODULE).
|
|
||||||
|
|
||||||
% init_per_suite(Cfg) ->
|
|
||||||
% _ = emqx_exhook_demo_svr:start(),
|
|
||||||
% emqx_ct_helpers:start_apps([emqx_exhook], fun set_special_cfgs/1),
|
|
||||||
% Cfg.
|
|
||||||
|
|
||||||
% end_per_suite(_Cfg) ->
|
|
||||||
% emqx_ct_helpers:stop_apps([emqx_exhook]),
|
|
||||||
% emqx_exhook_demo_svr:stop().
|
|
||||||
|
|
||||||
% set_special_cfgs(emqx) ->
|
|
||||||
% application:set_env(emqx, allow_anonymous, false),
|
|
||||||
% application:set_env(emqx, enable_authz_cache, false),
|
|
||||||
% application:set_env(emqx, plugins_loaded_file, undefined),
|
|
||||||
% application:set_env(emqx, modules_loaded_file, undefined);
|
|
||||||
% set_special_cfgs(emqx_exhook) ->
|
|
||||||
% ok.
|
|
||||||
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
% %% Test cases
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
% t_noserver_nohook(_) ->
|
|
||||||
% emqx_exhook:disable(default),
|
|
||||||
% ?assertEqual([], ets:tab2list(emqx_hooks)),
|
|
||||||
|
|
||||||
% Opts = proplists:get_value(
|
|
||||||
% default,
|
|
||||||
% application:get_env(emqx_exhook, servers, [])
|
|
||||||
% ),
|
|
||||||
% ok = emqx_exhook:enable(default, Opts),
|
|
||||||
% ?assertNotEqual([], ets:tab2list(emqx_hooks)).
|
|
||||||
|
|
||||||
% t_cli_list(_) ->
|
|
||||||
% meck_print(),
|
|
||||||
% ?assertEqual( [[emqx_exhook_server:format(Svr) || Svr <- emqx_exhook:list()]]
|
|
||||||
% , emqx_exhook_cli:cli(["server", "list"])
|
|
||||||
% ),
|
|
||||||
% unmeck_print().
|
|
||||||
|
|
||||||
% t_cli_enable_disable(_) ->
|
|
||||||
% meck_print(),
|
|
||||||
% ?assertEqual([already_started], emqx_exhook_cli:cli(["server", "enable", "default"])),
|
|
||||||
% ?assertEqual(ok, emqx_exhook_cli:cli(["server", "disable", "default"])),
|
|
||||||
% ?assertEqual([], emqx_exhook_cli:cli(["server", "list"])),
|
|
||||||
|
|
||||||
% ?assertEqual([not_running], emqx_exhook_cli:cli(["server", "disable", "default"])),
|
|
||||||
% ?assertEqual(ok, emqx_exhook_cli:cli(["server", "enable", "default"])),
|
|
||||||
% unmeck_print().
|
|
||||||
|
|
||||||
% t_cli_stats(_) ->
|
|
||||||
% meck_print(),
|
|
||||||
% _ = emqx_exhook_cli:cli(["server", "stats"]),
|
|
||||||
% _ = emqx_exhook_cli:cli(x),
|
|
||||||
% unmeck_print().
|
|
||||||
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
% %% Utils
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
% meck_print() ->
|
|
||||||
% meck:new(emqx_ctl, [passthrough, no_history, no_link]),
|
|
||||||
% meck:expect(emqx_ctl, print, fun(_) -> ok end),
|
|
||||||
% meck:expect(emqx_ctl, print, fun(_, Args) -> Args end).
|
|
||||||
|
|
||||||
% unmeck_print() ->
|
|
||||||
% meck:unload(emqx_ctl).
|
|
|
@ -1,339 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
||||||
%%
|
|
||||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
%% you may not use this file except in compliance with the License.
|
|
||||||
%% You may obtain a copy of the License at
|
|
||||||
%%
|
|
||||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
%%
|
|
||||||
%% Unless required by applicable law or agreed to in writing, software
|
|
||||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
%% See the License for the specific language governing permissions and
|
|
||||||
%% limitations under the License.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(emqx_exhook_demo_svr).
|
|
||||||
|
|
||||||
% -behavior(emqx_exhook_v_1_hook_provider_bhvr).
|
|
||||||
|
|
||||||
% %%
|
|
||||||
% -export([ start/0
|
|
||||||
% , stop/0
|
|
||||||
% , take/0
|
|
||||||
% , in/1
|
|
||||||
% ]).
|
|
||||||
|
|
||||||
% %% gRPC server HookProvider callbacks
|
|
||||||
% -export([ on_provider_loaded/2
|
|
||||||
% , on_provider_unloaded/2
|
|
||||||
% , on_client_connect/2
|
|
||||||
% , on_client_connack/2
|
|
||||||
% , on_client_connected/2
|
|
||||||
% , on_client_disconnected/2
|
|
||||||
% , on_client_authenticate/2
|
|
||||||
% , on_client_authorize/2
|
|
||||||
% , on_client_subscribe/2
|
|
||||||
% , on_client_unsubscribe/2
|
|
||||||
% , on_session_created/2
|
|
||||||
% , on_session_subscribed/2
|
|
||||||
% , on_session_unsubscribed/2
|
|
||||||
% , on_session_resumed/2
|
|
||||||
% , on_session_discarded/2
|
|
||||||
% , on_session_takeovered/2
|
|
||||||
% , on_session_terminated/2
|
|
||||||
% , on_message_publish/2
|
|
||||||
% , on_message_delivered/2
|
|
||||||
% , on_message_dropped/2
|
|
||||||
% , on_message_acked/2
|
|
||||||
% ]).
|
|
||||||
|
|
||||||
% -define(PORT, 9000).
|
|
||||||
% -define(NAME, ?MODULE).
|
|
||||||
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
% %% Server APIs
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
% start() ->
|
|
||||||
% Pid = spawn(fun mngr_main/0),
|
|
||||||
% register(?MODULE, Pid),
|
|
||||||
% {ok, Pid}.
|
|
||||||
|
|
||||||
% stop() ->
|
|
||||||
% grpc:stop_server(?NAME),
|
|
||||||
% ?MODULE ! stop.
|
|
||||||
|
|
||||||
% take() ->
|
|
||||||
% ?MODULE ! {take, self()},
|
|
||||||
% receive {value, V} -> V
|
|
||||||
% after 5000 -> error(timeout) end.
|
|
||||||
|
|
||||||
% in({FunName, Req}) ->
|
|
||||||
% ?MODULE ! {in, FunName, Req}.
|
|
||||||
|
|
||||||
% mngr_main() ->
|
|
||||||
% application:ensure_all_started(grpc),
|
|
||||||
% Services = #{protos => [emqx_exhook_pb],
|
|
||||||
% services => #{'emqx.exhook.v1.HookProvider' => emqx_exhook_demo_svr}
|
|
||||||
% },
|
|
||||||
% Options = [],
|
|
||||||
% Svr = grpc:start_server(?NAME, ?PORT, Services, Options),
|
|
||||||
% mngr_loop([Svr, queue:new(), queue:new()]).
|
|
||||||
|
|
||||||
% mngr_loop([Svr, Q, Takes]) ->
|
|
||||||
% receive
|
|
||||||
% {in, FunName, Req} ->
|
|
||||||
% {NQ1, NQ2} = reply(queue:in({FunName, Req}, Q), Takes),
|
|
||||||
% mngr_loop([Svr, NQ1, NQ2]);
|
|
||||||
% {take, From} ->
|
|
||||||
% {NQ1, NQ2} = reply(Q, queue:in(From, Takes)),
|
|
||||||
% mngr_loop([Svr, NQ1, NQ2]);
|
|
||||||
% stop ->
|
|
||||||
% exit(normal)
|
|
||||||
% end.
|
|
||||||
|
|
||||||
% reply(Q1, Q2) ->
|
|
||||||
% case queue:len(Q1) =:= 0 orelse
|
|
||||||
% queue:len(Q2) =:= 0 of
|
|
||||||
% true -> {Q1, Q2};
|
|
||||||
% _ ->
|
|
||||||
% {{value, {Name, V}}, NQ1} = queue:out(Q1),
|
|
||||||
% {{value, From}, NQ2} = queue:out(Q2),
|
|
||||||
% From ! {value, {Name, V}},
|
|
||||||
% {NQ1, NQ2}
|
|
||||||
% end.
|
|
||||||
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
% %% callbacks
|
|
||||||
% %%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
% -spec on_provider_loaded(emqx_exhook_pb:provider_loaded_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:loaded_response(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
|
|
||||||
% on_provider_loaded(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{hooks => [
|
|
||||||
% #{name => <<"client.connect">>},
|
|
||||||
% #{name => <<"client.connack">>},
|
|
||||||
% #{name => <<"client.connected">>},
|
|
||||||
% #{name => <<"client.disconnected">>},
|
|
||||||
% #{name => <<"client.authenticate">>},
|
|
||||||
% #{name => <<"client.authorize">>},
|
|
||||||
% #{name => <<"client.subscribe">>},
|
|
||||||
% #{name => <<"client.unsubscribe">>},
|
|
||||||
% #{name => <<"session.created">>},
|
|
||||||
% #{name => <<"session.subscribed">>},
|
|
||||||
% #{name => <<"session.unsubscribed">>},
|
|
||||||
% #{name => <<"session.resumed">>},
|
|
||||||
% #{name => <<"session.discarded">>},
|
|
||||||
% #{name => <<"session.takeovered">>},
|
|
||||||
% #{name => <<"session.terminated">>},
|
|
||||||
% #{name => <<"message.publish">>},
|
|
||||||
% #{name => <<"message.delivered">>},
|
|
||||||
% #{name => <<"message.acked">>},
|
|
||||||
% #{name => <<"message.dropped">>}]}, 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) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_client_connect(emqx_exhook_pb:client_connect_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_client_connect(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_client_connack(emqx_exhook_pb:client_connack_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_client_connack(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_client_connected(emqx_exhook_pb:client_connected_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_client_connected(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_client_disconnected(emqx_exhook_pb:client_disconnected_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_client_disconnected(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_client_authenticate(emqx_exhook_pb:client_authenticate_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_client_authenticate(#{clientinfo := #{username := Username}} = Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% %% some cases for testing
|
|
||||||
% case Username of
|
|
||||||
% <<"baduser">> ->
|
|
||||||
% {ok, #{type => 'STOP_AND_RETURN',
|
|
||||||
% value => {bool_result, false}}, Md};
|
|
||||||
% <<"gooduser">> ->
|
|
||||||
% {ok, #{type => 'STOP_AND_RETURN',
|
|
||||||
% value => {bool_result, true}}, Md};
|
|
||||||
% <<"normaluser">> ->
|
|
||||||
% {ok, #{type => 'CONTINUE',
|
|
||||||
% value => {bool_result, true}}, Md};
|
|
||||||
% _ ->
|
|
||||||
% {ok, #{type => 'IGNORE'}, Md}
|
|
||||||
% end.
|
|
||||||
|
|
||||||
% -spec on_client_authorize(emqx_exhook_pb:client_authorize_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_client_authorize(#{clientinfo := #{username := Username}} = Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% %% some cases for testing
|
|
||||||
% case Username of
|
|
||||||
% <<"baduser">> ->
|
|
||||||
% {ok, #{type => 'STOP_AND_RETURN',
|
|
||||||
% value => {bool_result, false}}, Md};
|
|
||||||
% <<"gooduser">> ->
|
|
||||||
% {ok, #{type => 'STOP_AND_RETURN',
|
|
||||||
% value => {bool_result, true}}, Md};
|
|
||||||
% <<"normaluser">> ->
|
|
||||||
% {ok, #{type => 'CONTINUE',
|
|
||||||
% value => {bool_result, true}}, Md};
|
|
||||||
% _ ->
|
|
||||||
% {ok, #{type => 'IGNORE'}, Md}
|
|
||||||
% end.
|
|
||||||
|
|
||||||
% -spec on_client_subscribe(emqx_exhook_pb:client_subscribe_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_client_subscribe(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_client_unsubscribe(emqx_exhook_pb:client_unsubscribe_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_client_unsubscribe(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_session_created(emqx_exhook_pb:session_created_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_session_created(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_session_subscribed(emqx_exhook_pb:session_subscribed_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_session_subscribed(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_session_unsubscribed(emqx_exhook_pb:session_unsubscribed_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_session_unsubscribed(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_session_resumed(emqx_exhook_pb:session_resumed_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_session_resumed(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_session_discarded(emqx_exhook_pb:session_discarded_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_session_discarded(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_session_takeovered(emqx_exhook_pb:session_takeovered_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_session_takeovered(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_session_terminated(emqx_exhook_pb:session_terminated_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_session_terminated(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_message_publish(emqx_exhook_pb:message_publish_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_message_publish(#{message := #{from := From} = Msg} = Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% %% some cases for testing
|
|
||||||
% case From of
|
|
||||||
% <<"baduser">> ->
|
|
||||||
% NMsg = Msg#{qos => 0,
|
|
||||||
% topic => <<"">>,
|
|
||||||
% payload => <<"">>
|
|
||||||
% },
|
|
||||||
% {ok, #{type => 'STOP_AND_RETURN',
|
|
||||||
% value => {message, NMsg}}, Md};
|
|
||||||
% <<"gooduser">> ->
|
|
||||||
% NMsg = Msg#{topic => From,
|
|
||||||
% payload => From},
|
|
||||||
% {ok, #{type => 'STOP_AND_RETURN',
|
|
||||||
% value => {message, NMsg}}, Md};
|
|
||||||
% _ ->
|
|
||||||
% {ok, #{type => 'IGNORE'}, Md}
|
|
||||||
% end.
|
|
||||||
|
|
||||||
% -spec on_message_delivered(emqx_exhook_pb:message_delivered_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_message_delivered(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_message_dropped(emqx_exhook_pb:message_dropped_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_message_dropped(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
||||||
|
|
||||||
% -spec on_message_acked(emqx_exhook_pb:message_acked_request(), grpc:metadata())
|
|
||||||
% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
|
|
||||||
% | {error, grpc_cowboy_h:error_response()}.
|
|
||||||
% on_message_acked(Req, Md) ->
|
|
||||||
% ?MODULE:in({?FUNCTION_NAME, Req}),
|
|
||||||
% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
|
|
||||||
% {ok, #{}, Md}.
|
|
|
@ -275,6 +275,7 @@ relx_apps(ReleaseType) ->
|
||||||
, emqx_authn
|
, emqx_authn
|
||||||
, emqx_authz
|
, emqx_authz
|
||||||
, emqx_gateway
|
, emqx_gateway
|
||||||
|
, {emqx_exhook, load}
|
||||||
, emqx_data_bridge
|
, emqx_data_bridge
|
||||||
, emqx_rule_engine
|
, emqx_rule_engine
|
||||||
, emqx_rule_actions
|
, emqx_rule_actions
|
||||||
|
|
Loading…
Reference in New Issue