refactor(gw): single instance support only

This commit is contained in:
JianBo He 2021-08-13 19:13:14 +08:00
parent 50ee840220
commit be4d2495f0
21 changed files with 647 additions and 809 deletions

View File

@ -4,7 +4,8 @@
gateway: { gateway: {
stomp.1: { stomp: {
frame: { frame: {
max_headers: 10 max_headers: 10
max_headers_length: 1024 max_headers_length: 1024
@ -37,7 +38,8 @@ gateway: {
} }
} }
coap.1: { coap: {
enable_stats: false enable_stats: false
authentication: { authentication: {
@ -63,7 +65,7 @@ gateway: {
} }
} }
mqttsn.1: { mqttsn: {
## The MQTT-SN Gateway ID in ADVERTISE message. ## The MQTT-SN Gateway ID in ADVERTISE message.
gateway_id: 1 gateway_id: 1
@ -107,8 +109,7 @@ gateway: {
} }
## Extension Protocol Gateway ## Extension Protocol Gateway
exproto.1: { exproto: {
## The gRPC server to accept requests ## The gRPC server to accept requests
server: { server: {
bind: 9100 bind: 9100
@ -138,9 +139,10 @@ gateway: {
#listener.dtls.1: {} #listener.dtls.1: {}
} }
lwm2m_xml_dir: "{{ platform_etc_dir }}/lwm2m_xml"
lwm2m.1: { lwm2m: {
xml_dir: "{{ platform_etc_dir }}/lwm2m_xml"
lifetime_min: 1s lifetime_min: 1s

View File

@ -20,15 +20,13 @@
-type instance_id() :: atom(). -type instance_id() :: atom().
-type gateway_type() :: atom(). -type gateway_type() :: atom().
%% @doc The Gateway Instace defination %% @doc The Gateway defination
-type instance() :: -type gateway() ::
#{ id := instance_id() #{ type := gateway_type()
, type := gateway_type()
, name := binary()
, descr => binary() | undefined , descr => binary() | undefined
%% Appears only in creating or detailed info %% Appears only in creating or detailed info
, rawconf => map() , rawconf => map()
%% Appears only in getting instance status/info %% Appears only in getting gateway status/info
, status => stopped | running , status => stopped | running
}. }.

View File

@ -22,29 +22,21 @@
-type reason() :: any(). -type reason() :: any().
%% @doc %% @doc
-callback init(Options :: list()) -> {error, reason()} | {ok, GwState :: state()}. -callback on_gateway_load(Gateway :: gateway(),
Ctx :: emqx_gateway_ctx:context())
%% @doc
-callback on_insta_create(Insta :: instance(),
Ctx :: emqx_gateway_ctx:context(),
GwState :: state()
)
-> {error, reason()} -> {error, reason()}
| {ok, [GwInstaPid :: pid()], GwInstaState :: state()} | {ok, [ChildPid :: pid()], GwState :: state()}
%% TODO: v0.2 The child spec is better for restarting child process %% TODO: v0.2 The child spec is better for restarting child process
| {ok, [Childspec :: supervisor:child_spec()], GwInstaState :: state()}. | {ok, [Childspec :: supervisor:child_spec()], GwState :: state()}.
%% @doc %% @doc
-callback on_insta_update(NewInsta :: instance(), -callback on_gateway_update(NewGateway :: gateway(),
OldInsta :: instance(), OldGateway :: gateway(),
GwInstaState :: state(),
GwState :: state()) GwState :: state())
-> ok -> ok
| {ok, [GwInstaPid :: pid()], GwInstaState :: state()} | {ok, [ChildPid :: pid()], NGwState :: state()}
| {ok, [Childspec :: supervisor:child_spec()], GwInstaState :: state()} | {ok, [Childspec :: supervisor:child_spec()], NGwState :: state()}
| {error, reason()}. | {error, reason()}.
%% @doc %% @doc
-callback on_insta_destroy(Insta :: instance(), -callback on_gateway_unload(Gateway :: gateway(), GwState :: state()) -> ok.
GwInstaState :: state(),
GwState :: state()) -> ok.

View File

@ -21,95 +21,85 @@
-behavior(emqx_gateway_impl). -behavior(emqx_gateway_impl).
%% APIs %% APIs
-export([ load/0 -export([ reg/0
, unload/0 , unreg/0
]). ]).
-export([ init/1 -export([ on_gateway_load/2
, on_insta_create/3 , on_gateway_update/3
, on_insta_update/4 , on_gateway_unload/2
, on_insta_destroy/3
]). ]).
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-dialyzer({nowarn_function, [load/0]}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
load() -> reg() ->
RegistryOptions = [ {cbkmod, ?MODULE} RegistryOptions = [ {cbkmod, ?MODULE}
], ],
Options = [], emqx_gateway_registry:reg(coap, RegistryOptions).
emqx_gateway_registry:load(coap, RegistryOptions, Options).
unload() -> unreg() ->
emqx_gateway_registry:unload(coap). emqx_gateway_registry:unreg(coap).
init([]) ->
GwState = #{},
{ok, GwState}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% emqx_gateway_registry callbacks %% emqx_gateway_registry callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
on_insta_create(_Insta = #{id := InstaId, on_gateway_load(_Gateway = #{type := GwType,
rawconf := RawConf rawconf := RawConf
}, Ctx, _GwState) -> }, Ctx) ->
Listeners = emqx_gateway_utils:normalize_rawconf(RawConf), Listeners = emqx_gateway_utils:normalize_rawconf(RawConf),
ListenerPids = lists:map(fun(Lis) -> ListenerPids = lists:map(fun(Lis) ->
start_listener(InstaId, Ctx, Lis) start_listener(GwType, Ctx, Lis)
end, Listeners), end, Listeners),
{ok, ListenerPids, #{ctx => Ctx}}. {ok, ListenerPids, #{ctx => Ctx}}.
on_insta_update(NewInsta, OldInsta, GwInstaState = #{ctx := Ctx}, GwState) -> on_gateway_update(NewGateway, OldGateway, GwState = #{ctx := Ctx}) ->
InstaId = maps:get(id, NewInsta), GwType = maps:get(type, NewGateway),
try try
%% XXX: 1. How hot-upgrade the changes ??? %% XXX: 1. How hot-upgrade the changes ???
%% XXX: 2. Check the New confs first before destroy old instance ??? %% XXX: 2. Check the New confs first before destroy old instance ???
on_insta_destroy(OldInsta, GwInstaState, GwState), on_gateway_unload(OldGateway, GwState),
on_insta_create(NewInsta, Ctx, GwState) on_gateway_load(NewGateway, Ctx)
catch catch
Class : Reason : Stk -> Class : Reason : Stk ->
logger:error("Failed to update coap instance ~s; " logger:error("Failed to update ~s; "
"reason: {~0p, ~0p} stacktrace: ~0p", "reason: {~0p, ~0p} stacktrace: ~0p",
[InstaId, Class, Reason, Stk]), [GwType, Class, Reason, Stk]),
{error, {Class, Reason}} {error, {Class, Reason}}
end. end.
on_insta_destroy(_Insta = #{ id := InstaId, on_gateway_unload(_Gateway = #{ type := GwType,
rawconf := RawConf rawconf := RawConf
}, }, _GwState) ->
_GwInstaState,
_GWState) ->
Listeners = emqx_gateway_utils:normalize_rawconf(RawConf), Listeners = emqx_gateway_utils:normalize_rawconf(RawConf),
lists:foreach(fun(Lis) -> lists:foreach(fun(Lis) ->
stop_listener(InstaId, Lis) stop_listener(GwType, Lis)
end, Listeners). end, Listeners).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal funcs %% Internal funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_listener(InstaId, Ctx, {Type, ListenOn, SocketOpts, Cfg}) -> start_listener(GwType, Ctx, {Type, ListenOn, SocketOpts, Cfg}) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) of case start_listener(GwType, Ctx, Type, ListenOn, SocketOpts, Cfg) of
{ok, Pid} -> {ok, Pid} ->
?ULOG("Start coap ~s:~s listener on ~s successfully.~n", ?ULOG("Start ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]), [GwType, Type, ListenOnStr]),
Pid; Pid;
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start coap ~s:~s listener on ~s: ~0p~n", ?ELOG("Failed to start ~s:~s listener on ~s: ~0p~n",
[InstaId, Type, ListenOnStr, Reason]), [GwType, Type, ListenOnStr, Reason]),
throw({badconf, Reason}) throw({badconf, Reason})
end. end.
start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) -> start_listener(GwType, Ctx, Type, ListenOn, SocketOpts, Cfg) ->
Name = name(InstaId, Type), Name = name(GwType, Type),
NCfg = Cfg#{ NCfg = Cfg#{
ctx => Ctx, ctx => Ctx,
frame_mod => emqx_coap_frame, frame_mod => emqx_coap_frame,
@ -124,21 +114,21 @@ do_start_listener(udp, Name, ListenOn, SocketOpts, MFA) ->
do_start_listener(dtls, Name, ListenOn, SocketOpts, MFA) -> do_start_listener(dtls, Name, ListenOn, SocketOpts, MFA) ->
esockd:open_dtls(Name, ListenOn, SocketOpts, MFA). esockd:open_dtls(Name, ListenOn, SocketOpts, MFA).
name(InstaId, Type) -> name(GwType, Type) ->
list_to_atom(lists:concat([InstaId, ":", Type])). list_to_atom(lists:concat([GwType, ":", Type])).
stop_listener(InstaId, {Type, ListenOn, SocketOpts, Cfg}) -> stop_listener(GwType, {Type, ListenOn, SocketOpts, Cfg}) ->
StopRet = stop_listener(InstaId, Type, ListenOn, SocketOpts, Cfg), StopRet = stop_listener(GwType, Type, ListenOn, SocketOpts, Cfg),
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of case StopRet of
ok -> ?ULOG("Stop coap ~s:~s listener on ~s successfully.~n", ok -> ?ULOG("Stop ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]); [GwType, Type, ListenOnStr]);
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to stop coap ~s:~s listener on ~s: ~0p~n", ?ELOG("Failed to stop ~s:~s listener on ~s: ~0p~n",
[InstaId, Type, ListenOnStr, Reason]) [GwType, Type, ListenOnStr, Reason])
end, end,
StopRet. StopRet.
stop_listener(InstaId, Type, ListenOn, _SocketOpts, _Cfg) -> stop_listener(GwType, Type, ListenOn, _SocketOpts, _Cfg) ->
Name = name(InstaId, Type), Name = name(GwType, Type),
esockd:close(Name, ListenOn). esockd:close(Name, ListenOn).

View File

@ -20,8 +20,8 @@
%% APIs %% APIs
-export([ registered_gateway/0 -export([ registered_gateway/0
, create/4 , load/2
, remove/1 , unload/1
, lookup/1 , lookup/1
, update/1 , update/1
, start/1 , start/1
@ -37,48 +37,40 @@ registered_gateway() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Gateway Instace APIs %% Gateway Instace APIs
-spec list() -> [instance()]. -spec list() -> [gateway()].
list() -> list() ->
lists:append(lists:map( emqx_gateway_sup:list_gateway_insta().
fun({_, Insta}) -> Insta end,
emqx_gateway_sup:list_gateway_insta()
)).
-spec create(gateway_type(), binary(), binary(), map()) -spec load(gateway_type(), map())
-> {ok, pid()} -> {ok, pid()}
| {error, any()}. | {error, any()}.
create(Type, Name, Descr, RawConf) -> load(GwType, RawConf) ->
Insta = #{ id => clacu_insta_id(Type, Name) Gateway = #{ type => GwType
, type => Type , descr => undefined
, name => Name
, descr => Descr
, rawconf => RawConf , rawconf => RawConf
}, },
emqx_gateway_sup:create_gateway_insta(Insta). emqx_gateway_sup:load_gateway(Gateway).
-spec remove(instance_id()) -> ok | {error, any()}. -spec unload(gateway_type()) -> ok | {error, any()}.
remove(InstaId) -> unload(GwType) ->
emqx_gateway_sup:remove_gateway_insta(InstaId). emqx_gateway_sup:unload_gateway(GwType).
-spec lookup(instance_id()) -> instance() | undefined. -spec lookup(gateway_type()) -> gateway() | undefined.
lookup(InstaId) -> lookup(GwType) ->
emqx_gateway_sup:lookup_gateway_insta(InstaId). emqx_gateway_sup:lookup_gateway(GwType).
-spec update(instance()) -> ok | {error, any()}. -spec update(gateway()) -> ok | {error, any()}.
update(NewInsta) -> update(NewGateway) ->
emqx_gateway_sup:update_gateway_insta(NewInsta). emqx_gateway_sup:update_gateway(NewGateway).
-spec start(instance_id()) -> ok | {error, any()}. -spec start(gateway_type()) -> ok | {error, any()}.
start(InstaId) -> start(GwType) ->
emqx_gateway_sup:start_gateway_insta(InstaId). emqx_gateway_sup:start_gateway_insta(GwType).
-spec stop(instance_id()) -> ok | {error, any()}. -spec stop(gateway_type()) -> ok | {error, any()}.
stop(InstaId) -> stop(GwType) ->
emqx_gateway_sup:stop_gateway_insta(InstaId). emqx_gateway_sup:stop_gateway_insta(GwType).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal funcs %% Internal funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
clacu_insta_id(Type, Name) when is_binary(Name) ->
list_to_atom(lists:concat([Type, "#", binary_to_list(Name)])).

View File

@ -27,7 +27,7 @@ start(_StartType, _StartArgs) ->
{ok, Sup} = emqx_gateway_sup:start_link(), {ok, Sup} = emqx_gateway_sup:start_link(),
emqx_gateway_cli:load(), emqx_gateway_cli:load(),
load_default_gateway_applications(), load_default_gateway_applications(),
create_gateway_by_default(), load_gateway_by_default(),
{ok, Sup}. {ok, Sup}.
stop(_State) -> stop(_State) ->
@ -40,56 +40,43 @@ stop(_State) ->
load_default_gateway_applications() -> load_default_gateway_applications() ->
Apps = gateway_type_searching(), Apps = gateway_type_searching(),
?LOG(info, "Starting the default gateway types: ~p", [Apps]), ?LOG(info, "Starting the default gateway types: ~p", [Apps]),
lists:foreach(fun load/1, Apps). lists:foreach(fun reg/1, Apps).
gateway_type_searching() -> gateway_type_searching() ->
%% FIXME: Hardcoded apps %% FIXME: Hardcoded apps
[emqx_stomp_impl, emqx_sn_impl, emqx_exproto_impl, [emqx_stomp_impl, emqx_sn_impl, emqx_exproto_impl,
emqx_coap_impl, emqx_lwm2m_impl]. emqx_coap_impl, emqx_lwm2m_impl].
load(Mod) -> reg(Mod) ->
try try
Mod:load(), Mod:reg(),
?LOG(info, "Load ~s gateway application successfully!", [Mod]) ?LOG(info, "Register ~s gateway application successfully!", [Mod])
catch catch
Class : Reason -> Class : Reason : Stk ->
?LOG(error, "Load ~s gateway application failed: {~p, ~p}", ?LOG(error, "Failed to register ~s gateway application: {~p, ~p}\n"
[Mod, Class, Reason]) "Stacktrace: ~0p",
[Mod, Class, Reason, Stk])
end. end.
create_gateway_by_default() -> load_gateway_by_default() ->
create_gateway_by_default(zipped_confs()). load_gateway_by_default(confs()).
create_gateway_by_default([]) -> load_gateway_by_default([]) ->
ok; ok;
create_gateway_by_default([{Type, Name, Confs}|More]) -> load_gateway_by_default([{Type, Confs}|More]) ->
case emqx_gateway_registry:lookup(Type) of case emqx_gateway_registry:lookup(Type) of
undefined -> undefined ->
?LOG(error, "Skip to start ~s#~s: not_registred_type", ?LOG(error, "Skip to load ~s gateway, because it is not registered",
[Type, Name]); [Type]);
_ -> _ ->
case emqx_gateway:create(Type, case emqx_gateway:load(Type, Confs) of
atom_to_binary(Name, utf8),
<<>>,
Confs) of
{ok, _} -> {ok, _} ->
?LOG(debug, "Start ~s#~s successfully!", [Type, Name]); ?LOG(debug, "Load ~s gateway successfully!", [Type]);
{error, Reason} -> {error, Reason} ->
?LOG(error, "Start ~s#~s failed: ~0p", ?LOG(error, "Failed to load ~s gateway: ~0p", [Type, Reason])
[Type, Name, Reason])
end end
end, end,
create_gateway_by_default(More). load_gateway_by_default(More).
zipped_confs() -> confs() ->
All = maps:to_list( maps:to_list(emqx_config:get([gateway], [])).
maps:without(exclude_options(), emqx_config:get([gateway]))),
lists:append(lists:foldr(
fun({Type, Gws}, Acc) ->
{Names, Confs} = lists:unzip(maps:to_list(Gws)),
Types = [ Type || _ <- lists:seq(1, length(Names))],
[lists:zip3(Types, Names, Confs) | Acc]
end, [], All)).
exclude_options() ->
[lwm2m_xml_dir].

View File

@ -15,6 +15,10 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc The Gateway Top supervisor. %% @doc The Gateway Top supervisor.
%%
%% This supervisor has monitor a bunch of process/resources depended by
%% gateway runtime
%%
-module(emqx_gateway_gw_sup). -module(emqx_gateway_gw_sup).
-behaviour(supervisor). -behaviour(supervisor).
@ -41,64 +45,62 @@
start_link(Type) -> start_link(Type) ->
supervisor:start_link({local, Type}, ?MODULE, [Type]). supervisor:start_link({local, Type}, ?MODULE, [Type]).
-spec create_insta(pid(), instance(), map()) -> {ok, GwInstaPid :: pid()} | {error, any()}. -spec create_insta(pid(), gateway(), map()) -> {ok, GwInstaPid :: pid()} | {error, any()}.
create_insta(Sup, Insta = #{id := InstaId}, GwDscrptr) -> create_insta(Sup, Gateway = #{type := GwType}, GwDscrptr) ->
case emqx_gateway_utils:find_sup_child(Sup, InstaId) of case emqx_gateway_utils:find_sup_child(Sup, GwType) of
{ok, _GwInstaPid} -> {error, alredy_existed}; {ok, _GwInstaPid} -> {error, alredy_existed};
false -> false ->
%% XXX: More instances options to it? Ctx = ctx(Sup, GwType),
%%
Ctx = ctx(Sup, InstaId),
%% %%
ChildSpec = emqx_gateway_utils:childspec( ChildSpec = emqx_gateway_utils:childspec(
InstaId, GwType,
worker, worker,
emqx_gateway_insta_sup, emqx_gateway_insta_sup,
[Insta, Ctx, GwDscrptr] [Gateway, Ctx, GwDscrptr]
), ),
emqx_gateway_utils:supervisor_ret( emqx_gateway_utils:supervisor_ret(
supervisor:start_child(Sup, ChildSpec) supervisor:start_child(Sup, ChildSpec)
) )
end. end.
-spec remove_insta(pid(), InstaId :: atom()) -> ok | {error, any()}. -spec remove_insta(pid(), GwType :: gateway_type()) -> ok | {error, any()}.
remove_insta(Sup, InstaId) -> remove_insta(Sup, GwType) ->
case emqx_gateway_utils:find_sup_child(Sup, InstaId) of case emqx_gateway_utils:find_sup_child(Sup, GwType) of
false -> ok; false -> ok;
{ok, _GwInstaPid} -> {ok, _GwInstaPid} ->
ok = supervisor:terminate_child(Sup, InstaId), ok = supervisor:terminate_child(Sup, GwType),
ok = supervisor:delete_child(Sup, InstaId) ok = supervisor:delete_child(Sup, GwType)
end. end.
-spec update_insta(pid(), NewInsta :: instance()) -> ok | {error, any()}. -spec update_insta(pid(), NewGateway :: gateway()) -> ok | {error, any()}.
update_insta(Sup, NewInsta = #{id := InstaId}) -> update_insta(Sup, NewGateway = #{type := GwType}) ->
case emqx_gateway_utils:find_sup_child(Sup, InstaId) of case emqx_gateway_utils:find_sup_child(Sup, GwType) of
false -> {error, not_found}; false -> {error, not_found};
{ok, GwInstaPid} -> {ok, GwInstaPid} ->
emqx_gateway_insta_sup:update(GwInstaPid, NewInsta) emqx_gateway_insta_sup:update(GwInstaPid, NewGateway)
end. end.
-spec start_insta(pid(), atom()) -> ok | {error, any()}. -spec start_insta(pid(), gateway_type()) -> ok | {error, any()}.
start_insta(Sup, InstaId) -> start_insta(Sup, GwType) ->
case emqx_gateway_utils:find_sup_child(Sup, InstaId) of case emqx_gateway_utils:find_sup_child(Sup, GwType) of
false -> {error, not_found}; false -> {error, not_found};
{ok, GwInstaPid} -> {ok, GwInstaPid} ->
emqx_gateway_insta_sup:enable(GwInstaPid) emqx_gateway_insta_sup:enable(GwInstaPid)
end. end.
-spec stop_insta(pid(), atom()) -> ok | {error, any()}. -spec stop_insta(pid(), gateway_type()) -> ok | {error, any()}.
stop_insta(Sup, InstaId) -> stop_insta(Sup, GwType) ->
case emqx_gateway_utils:find_sup_child(Sup, InstaId) of case emqx_gateway_utils:find_sup_child(Sup, GwType) of
false -> {error, not_found}; false -> {error, not_found};
{ok, GwInstaPid} -> {ok, GwInstaPid} ->
emqx_gateway_insta_sup:disable(GwInstaPid) emqx_gateway_insta_sup:disable(GwInstaPid)
end. end.
-spec list_insta(pid()) -> [instance()]. -spec list_insta(pid()) -> [gateway()].
list_insta(Sup) -> list_insta(Sup) ->
lists:filtermap( lists:filtermap(
fun({InstaId, GwInstaPid, _Type, _Mods}) -> fun({GwType, GwInstaPid, _Type, _Mods}) ->
is_gateway_insta_id(InstaId) is_gateway_insta_id(GwType)
andalso {true, emqx_gateway_insta_sup:info(GwInstaPid)} andalso {true, emqx_gateway_insta_sup:info(GwInstaPid)}
end, supervisor:which_children(Sup)). end, supervisor:which_children(Sup)).
@ -119,10 +121,10 @@ init([Type]) ->
%% Internal funcs %% Internal funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
ctx(Sup, InstaId) -> ctx(Sup, GwType) ->
{_, Type} = erlang:process_info(Sup, registered_name), {_, Type} = erlang:process_info(Sup, registered_name),
{ok, CM} = emqx_gateway_utils:find_sup_child(Sup, emqx_gateway_cm), {ok, CM} = emqx_gateway_utils:find_sup_child(Sup, emqx_gateway_cm),
#{ instid => InstaId #{ instid => GwType
, type => Type , type => Type
, cm => CM , cm => CM
}. }.

View File

@ -14,7 +14,7 @@
%% limitations under the License. %% limitations under the License.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc The gateway instance management %% @doc The gateway runtime
-module(emqx_gateway_insta_sup). -module(emqx_gateway_insta_sup).
-behaviour(gen_server). -behaviour(gen_server).
@ -40,42 +40,42 @@
]). ]).
-record(state, { -record(state, {
insta :: instance(), gw :: gateway(),
ctx :: emqx_gateway_ctx:context(), ctx :: emqx_gateway_ctx:context(),
status :: stopped | running, status :: stopped | running,
child_pids :: [pid()], child_pids :: [pid()],
insta_state :: emqx_gateway_impl:state() | undefined gw_state :: emqx_gateway_impl:state() | undefined
}). }).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_link(Insta, Ctx, GwDscrptr) -> start_link(Gateway, Ctx, GwDscrptr) ->
gen_server:start_link( gen_server:start_link(
?MODULE, ?MODULE,
[Insta, Ctx, GwDscrptr], [Gateway, Ctx, GwDscrptr],
[] []
). ).
-spec info(pid()) -> instance(). -spec info(pid()) -> gateway().
info(Pid) -> info(Pid) ->
gen_server:call(Pid, info). gen_server:call(Pid, info).
%% @doc Stop instance %% @doc Stop gateway
-spec disable(pid()) -> ok | {error, any()}. -spec disable(pid()) -> ok | {error, any()}.
disable(Pid) -> disable(Pid) ->
call(Pid, disable). call(Pid, disable).
%% @doc Start instance %% @doc Start gateway
-spec enable(pid()) -> ok | {error, any()}. -spec enable(pid()) -> ok | {error, any()}.
enable(Pid) -> enable(Pid) ->
call(Pid, enable). call(Pid, enable).
%% @doc Update the gateway instance configurations %% @doc Update the gateway configurations
-spec update(pid(), instance()) -> ok | {error, any()}. -spec update(pid(), gateway()) -> ok | {error, any()}.
update(Pid, NewInsta) -> update(Pid, NewGateway) ->
call(Pid, {update, NewInsta}). call(Pid, {update, NewGateway}).
call(Pid, Req) -> call(Pid, Req) ->
gen_server:call(Pid, Req, 5000). gen_server:call(Pid, Req, 5000).
@ -84,30 +84,29 @@ call(Pid, Req) ->
%% gen_server callbacks %% gen_server callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([Insta, Ctx0, _GwDscrptr]) -> init([Gateway, Ctx0, _GwDscrptr]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
#{id := InstaId, rawconf := RawConf} = Insta, #{type := GwType, rawconf := RawConf} = Gateway,
Ctx = do_init_context(InstaId, RawConf, Ctx0), Ctx = do_init_context(GwType, RawConf, Ctx0),
State = #state{ State = #state{
insta = Insta, gw = Gateway,
ctx = Ctx, ctx = Ctx,
child_pids = [], child_pids = [],
status = stopped status = stopped
}, },
case cb_insta_create(State) of case cb_gateway_load(State) of
{error, _Reason} -> {error, Reason} ->
do_deinit_context(Ctx), do_deinit_context(Ctx),
%% XXX: Return Reason?? {stop, {load_gateway_failure, Reason}};
{stop, create_gateway_instance_failed};
{ok, NState} -> {ok, NState} ->
{ok, NState} {ok, NState}
end. end.
do_init_context(InstaId, RawConf, Ctx) -> do_init_context(GwType, RawConf, Ctx) ->
Auth = case maps:get(authentication, RawConf, #{enable => false}) of Auth = case maps:get(authentication, RawConf, #{enable => false}) of
#{enable := true, #{enable := true,
authenticators := AuthCfgs} when is_list(AuthCfgs) -> authenticators := AuthCfgs} when is_list(AuthCfgs) ->
create_authenticators_for_gateway_insta(InstaId, AuthCfgs); create_authenticators_for_gateway_insta(GwType, AuthCfgs);
_ -> _ ->
undefined undefined
end, end,
@ -117,13 +116,13 @@ do_deinit_context(Ctx) ->
cleanup_authenticators_for_gateway_insta(maps:get(auth, Ctx)), cleanup_authenticators_for_gateway_insta(maps:get(auth, Ctx)),
ok. ok.
handle_call(info, _From, State = #state{insta = Insta}) -> handle_call(info, _From, State = #state{gw = Gateway}) ->
{reply, Insta, State}; {reply, Gateway, State};
handle_call(disable, _From, State = #state{status = Status}) -> handle_call(disable, _From, State = #state{status = Status}) ->
case Status of case Status of
running -> running ->
case cb_insta_destroy(State) of case cb_gateway_unload(State) of
{ok, NState} -> {ok, NState} ->
{reply, ok, NState}; {reply, ok, NState};
{error, Reason} -> {error, Reason} ->
@ -136,7 +135,7 @@ handle_call(disable, _From, State = #state{status = Status}) ->
handle_call(enable, _From, State = #state{status = Status}) -> handle_call(enable, _From, State = #state{status = Status}) ->
case Status of case Status of
stopped -> stopped ->
case cb_insta_create(State) of case cb_gateway_load(State) of
{error, Reason} -> {error, Reason} ->
{reply, {error, Reason}, State}; {reply, {error, Reason}, State};
{ok, NState} -> {ok, NState} ->
@ -147,28 +146,30 @@ handle_call(enable, _From, State = #state{status = Status}) ->
end; end;
%% Stopped -> update %% Stopped -> update
handle_call({update, NewInsta}, _From, State = #state{insta = Insta, handle_call({update, NewGateway}, _From, State = #state{gw = Gateway,
status = stopped}) -> status = stopped}) ->
case maps:get(id, NewInsta, undefined) == maps:get(id, Insta, undefined) of case maps:get(type, NewGateway, undefined)
== maps:get(type, Gateway, undefined) of
true -> true ->
{reply, ok, State#state{insta = NewInsta}}; {reply, ok, State#state{gw = NewGateway}};
false -> false ->
{reply, {error, bad_instan_id}, State} {reply, {error, gateway_type_not_match}, State}
end; end;
%% Running -> update %% Running -> update
handle_call({update, NewInsta}, _From, State = #state{insta = Insta, handle_call({update, NewGateway}, _From, State = #state{gw = Gateway,
status = running}) -> status = running}) ->
case maps:get(id, NewInsta, undefined) == maps:get(id, Insta, undefined) of case maps:get(type, NewGateway, undefined)
== maps:get(type, Gateway, undefined) of
true -> true ->
case cb_insta_update(NewInsta, State) of case cb_gateway_update(NewGateway, State) of
{ok, NState} -> {ok, NState} ->
{reply, ok, NState}; {reply, ok, NState};
{error, Reason} -> {error, Reason} ->
{reply, {error, Reason}, State} {reply, {error, Reason}, State}
end; end;
false -> false ->
{reply, {error, bad_instan_id}, State} {reply, {error, gateway_type_not_match}, State}
end; end;
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
@ -187,7 +188,7 @@ handle_info({'EXIT', Pid, Reason}, State = #state{child_pids = Pids}) ->
logger:error("All child process exited!"), logger:error("All child process exited!"),
{noreply, State#state{status = stopped, {noreply, State#state{status = stopped,
child_pids = [], child_pids = [],
insta_state = undefined}}; gw_state = undefined}};
RemainPids -> RemainPids ->
{noreply, State#state{child_pids = RemainPids}} {noreply, State#state{child_pids = RemainPids}}
end; end;
@ -201,10 +202,7 @@ handle_info(Info, State) ->
{noreply, State}. {noreply, State}.
terminate(_Reason, State = #state{ctx = Ctx, child_pids = Pids}) -> terminate(_Reason, State = #state{ctx = Ctx, child_pids = Pids}) ->
%% Cleanup instances Pids /= [] andalso (_ = cb_gateway_unload(State)),
%% Step1. Destory instance
Pids /= [] andalso (_ = cb_insta_destroy(State)),
%% Step2. Delete authenticator resources
_ = do_deinit_context(Ctx), _ = do_deinit_context(Ctx),
ok. ok.
@ -217,8 +215,8 @@ code_change(_OldVsn, State, _Extra) ->
%% @doc AuthCfgs is a array of authenticatior configurations, %% @doc AuthCfgs is a array of authenticatior configurations,
%% see: emqx_authn_schema:authenticators/1 %% see: emqx_authn_schema:authenticators/1
create_authenticators_for_gateway_insta(InstaId0, AuthCfgs) -> create_authenticators_for_gateway_insta(GwType, AuthCfgs) ->
ChainId = atom_to_binary(InstaId0, utf8), ChainId = atom_to_binary(GwType, utf8),
case emqx_authn:create_chain(#{id => ChainId}) of case emqx_authn:create_chain(#{id => ChainId}) of
{ok, _ChainInfo} -> {ok, _ChainInfo} ->
Results = lists:map(fun(AuthCfg = #{name := Name}) -> Results = lists:map(fun(AuthCfg = #{name := Name}) ->
@ -245,88 +243,85 @@ cleanup_authenticators_for_gateway_insta(ChainId) ->
case emqx_authn:delete_chain(ChainId) of case emqx_authn:delete_chain(ChainId) of
ok -> ok; ok -> ok;
{error, {not_found, _}} -> {error, {not_found, _}} ->
logger:warning("Failed clean authenticator chain: ~s, " logger:warning("Failed to clean authenticator chain: ~s, "
"reason: not_found", [ChainId]); "reason: not_found", [ChainId]);
{error, Reason} -> {error, Reason} ->
logger:error("Failed clean authenticator chain: ~s, " logger:error("Failed to clean authenticator chain: ~s, "
"reason: ~p", [ChainId, Reason]) "reason: ~p", [ChainId, Reason])
end. end.
cb_insta_destroy(State = #state{insta = Insta = #{type := Type}, cb_gateway_unload(State = #state{gw = Gateway = #{type := GwType},
insta_state = InstaState}) -> gw_state = GwState}) ->
try try
#{cbkmod := CbMod, #{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwType),
state := GwState} = emqx_gateway_registry:lookup(Type), CbMod:on_gateway_unload(Gateway, GwState, GwState),
CbMod:on_insta_destroy(Insta, InstaState, GwState),
{ok, State#state{child_pids = [], {ok, State#state{child_pids = [],
insta_state = undefined, gw_state = undefined,
status = stopped}} status = stopped}}
catch catch
Class : Reason : Stk -> Class : Reason : Stk ->
logger:error("Destroy instance (~0p, ~0p, _) crashed: " logger:error("Failed to unload gateway (~0p, ~0p) crashed: "
"{~p, ~p}, stacktrace: ~0p", "{~p, ~p}, stacktrace: ~0p",
[Insta, InstaState, [Gateway, GwState,
Class, Reason, Stk]), Class, Reason, Stk]),
{error, {Class, Reason, Stk}} {error, {Class, Reason, Stk}}
end. end.
cb_insta_create(State = #state{insta = Insta = #{type := Type}, cb_gateway_load(State = #state{gw = Gateway = #{type := GwType},
ctx = Ctx}) -> ctx = Ctx}) ->
try try
#{cbkmod := CbMod, #{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwType),
state := GwState} = emqx_gateway_registry:lookup(Type), case CbMod:on_gateway_load(Gateway, Ctx) of
case CbMod:on_insta_create(Insta, Ctx, GwState) of
{error, Reason} -> throw({callback_return_error, Reason}); {error, Reason} -> throw({callback_return_error, Reason});
{ok, InstaPidOrSpecs, InstaState} -> {ok, ChildPidOrSpecs, GwState} ->
ChildPids = start_child_process(InstaPidOrSpecs), ChildPids = start_child_process(ChildPidOrSpecs),
{ok, State#state{ {ok, State#state{
status = running, status = running,
child_pids = ChildPids, child_pids = ChildPids,
insta_state = InstaState gw_state = GwState
}} }}
end end
catch catch
Class : Reason1 : Stk -> Class : Reason1 : Stk ->
logger:error("Create instance (~0p, ~0p, _) crashed: " logger:error("Failed to load ~s gateway (~0p, ~0p) crashed: "
"{~p, ~p}, stacktrace: ~0p", "{~p, ~p}, stacktrace: ~0p",
[Insta, Ctx, [GwType, Gateway, Ctx,
Class, Reason1, Stk]), Class, Reason1, Stk]),
{error, {Class, Reason1, Stk}} {error, {Class, Reason1, Stk}}
end. end.
cb_insta_update(NewInsta, cb_gateway_update(NewGateway,
State = #state{insta = Insta = #{type := Type}, State = #state{gw = Gateway = #{type := GwType},
ctx = Ctx, ctx = Ctx,
insta_state = GwInstaState}) -> gw_state = GwState}) ->
try try
#{cbkmod := CbMod, #{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwType),
state := GwState} = emqx_gateway_registry:lookup(Type), case CbMod:on_gateway_update(NewGateway, Gateway, GwState) of
case CbMod:on_insta_update(NewInsta, Insta, GwInstaState, GwState) of
{error, Reason} -> throw({callback_return_error, Reason}); {error, Reason} -> throw({callback_return_error, Reason});
{ok, InstaPidOrSpecs, InstaState} -> {ok, ChildPidOrSpecs, NGwState} ->
%% XXX: Hot-upgrade ??? %% XXX: Hot-upgrade ???
ChildPids = start_child_process(InstaPidOrSpecs), ChildPids = start_child_process(ChildPidOrSpecs),
{ok, State#state{ {ok, State#state{
status = running, status = running,
child_pids = ChildPids, child_pids = ChildPids,
insta_state = InstaState gw_state = NGwState
}} }}
end end
catch catch
Class : Reason1 : Stk -> Class : Reason1 : Stk ->
logger:error("Update instance (~0p, ~0p, ~0p, _) crashed: " logger:error("Failed to update gateway (~0p, ~0p, ~0p) crashed: "
"{~p, ~p}, stacktrace: ~0p", "{~p, ~p}, stacktrace: ~0p",
[NewInsta, Insta, Ctx, [NewGateway, Gateway, Ctx,
Class, Reason1, Stk]), Class, Reason1, Stk]),
{error, {Class, Reason1, Stk}} {error, {Class, Reason1, Stk}}
end. end.
start_child_process([Indictor|_] = InstaPidOrSpecs) -> start_child_process([Indictor|_] = ChildPidOrSpecs) ->
case erlang:is_pid(Indictor) of case erlang:is_pid(Indictor) of
true -> true ->
InstaPidOrSpecs; ChildPidOrSpecs;
_ -> _ ->
do_start_child_process(InstaPidOrSpecs) do_start_child_process(ChildPidOrSpecs)
end. end.
do_start_child_process(ChildSpecs) when is_list(ChildSpecs) -> do_start_child_process(ChildSpecs) when is_list(ChildSpecs) ->

View File

@ -23,11 +23,9 @@
-behavior(gen_server). -behavior(gen_server).
%% APIs for Impl. %% APIs for Impl.
-export([ load/3 -export([ reg/2
, unload/1 , unreg/1
]). , list/0
-export([ list/0
, lookup/1 , lookup/1
]). ]).
@ -44,9 +42,17 @@
]). ]).
-record(state, { -record(state, {
loaded = #{} :: #{ gateway_type() => descriptor() } reged = #{} :: #{ gateway_type() => descriptor() }
}). }).
-type registry_options() :: [registry_option()].
-type registry_option() :: {cbkmod, atom()}.
-type descriptor() :: #{ cbkmod := atom()
, rgopts := registry_options()
}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -58,37 +64,24 @@ start_link() ->
%% Mgmt %% Mgmt
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-type registry_options() :: [registry_option()]. -spec reg(gateway_type(), registry_options())
-type registry_option() :: {cbkmod, atom()}.
-type gateway_options() :: list().
-type descriptor() :: #{ cbkmod := atom()
, rgopts := registry_options()
, gwopts := gateway_options()
, state => any()
}.
-spec load(gateway_type(), registry_options(), gateway_options())
-> ok -> ok
| {error, any()}. | {error, any()}.
load(Type, RgOpts, GwOpts) -> reg(Type, RgOpts) ->
CbMod = proplists:get_value(cbkmod, RgOpts, Type), CbMod = proplists:get_value(cbkmod, RgOpts, Type),
Dscrptr = #{ cbkmod => CbMod Dscrptr = #{ cbkmod => CbMod
, rgopts => RgOpts , rgopts => RgOpts
, gwopts => GwOpts
}, },
call({load, Type, Dscrptr}). call({reg, Type, Dscrptr}).
-spec unload(gateway_type()) -> ok | {error, any()}. -spec unreg(gateway_type()) -> ok | {error, any()}.
unload(Type) -> unreg(Type) ->
%% TODO: Checking ALL INSTACE HAS STOPPED %% TODO: Checking ALL INSTACE HAS STOPPED
call({unload, Type}). call({unreg, Type}).
%% TODO: %% TODO:
%unload(Type, Force) -> %unreg(Type, Force) ->
% call({unload, Type, Froce}). % call({unreg, Type, Froce}).
%% @doc Return all registered protocol gateway implementation %% @doc Return all registered protocol gateway implementation
-spec list() -> [{gateway_type(), descriptor()}]. -spec list() -> [{gateway_type(), descriptor()}].
@ -109,41 +102,30 @@ call(Req) ->
init([]) -> init([]) ->
%% TODO: Metrics ??? %% TODO: Metrics ???
process_flag(trap_exit, true), process_flag(trap_exit, true),
{ok, #state{loaded = #{}}}. {ok, #state{reged = #{}}}.
handle_call({load, Type, Dscrptr}, _From, State = #state{loaded = Gateways}) -> handle_call({reg, Type, Dscrptr}, _From, State = #state{reged = Gateways}) ->
case maps:get(Type, Gateways, notfound) of case maps:get(Type, Gateways, notfound) of
notfound -> notfound ->
try NGateways = maps:put(Type, Dscrptr, Gateways),
GwOpts = maps:get(gwopts, Dscrptr), {reply, ok, State#state{reged = NGateways}};
CbMod = maps:get(cbkmod, Dscrptr),
{ok, GwState} = CbMod:init(GwOpts),
NDscrptr = maps:put(state, GwState, Dscrptr),
NGateways = maps:put(Type, NDscrptr, Gateways),
{reply, ok, State#state{loaded = NGateways}}
catch
Class : Reason : Stk ->
logger:error("Load ~s crashed {~p, ~p}; stacktrace: ~0p",
[Type, Class, Reason, Stk]),
{reply, {error, {Class, Reason}}, State}
end;
_ -> _ ->
{reply, {error, already_existed}, State} {reply, {error, already_existed}, State}
end; end;
handle_call({unload, Type}, _From, State = #state{loaded = Gateways}) -> handle_call({unreg, Type}, _From, State = #state{reged = Gateways}) ->
case maps:get(Type, Gateways, undefined) of case maps:get(Type, Gateways, undefined) of
undefined -> undefined ->
{reply, ok, State}; {reply, ok, State};
_ -> _ ->
emqx_gateway_sup:stop_all_suptree(Type), emqx_gateway_sup:unload_gateway(Type),
{reply, ok, State#state{loaded = maps:remove(Type, Gateways)}} {reply, ok, State#state{reged = maps:remove(Type, Gateways)}}
end; end;
handle_call(all, _From, State = #state{loaded = Gateways}) -> handle_call(all, _From, State = #state{reged = Gateways}) ->
{reply, maps:to_list(Gateways), State}; {reply, maps:to_list(Gateways), State};
handle_call({lookup, Type}, _From, State = #state{loaded = Gateways}) -> handle_call({lookup, Type}, _From, State = #state{reged = Gateways}) ->
Reply = maps:get(Type, Gateways, undefined), Reply = maps:get(Type, Gateways, undefined),
{reply, Reply, State}; {reply, Reply, State};

View File

@ -32,17 +32,13 @@
structs() -> ["gateway"]. structs() -> ["gateway"].
fields("gateway") -> fields("gateway") ->
[{stomp, t(ref(stomp))}, [{stomp, t(ref(stomp_structs))},
{mqttsn, t(ref(mqttsn))}, {mqttsn, t(ref(mqttsn_structs))},
{coap, t(ref(coap))}, {coap, t(ref(coap_structs))},
{lwm2m, t(ref(lwm2m))}, {lwm2m, t(ref(lwm2m_structs))},
{lwm2m_xml_dir, t(string())}, {exproto, t(ref(exproto_structs))}
{exproto, t(ref(exproto))}
]; ];
fields(stomp) ->
[{"$id", t(ref(stomp_structs))}];
fields(stomp_structs) -> fields(stomp_structs) ->
[ {frame, t(ref(stomp_frame))} [ {frame, t(ref(stomp_frame))}
, {clientinfo_override, t(ref(clientinfo_override))} , {clientinfo_override, t(ref(clientinfo_override))}
@ -56,9 +52,6 @@ fields(stomp_frame) ->
, {max_body_length, t(integer(), undefined, 8192)} , {max_body_length, t(integer(), undefined, 8192)}
]; ];
fields(mqttsn) ->
[{"$id", t(ref(mqttsn_structs))}];
fields(mqttsn_structs) -> fields(mqttsn_structs) ->
[ {gateway_id, t(integer())} [ {gateway_id, t(integer())}
, {broadcast, t(boolean())} , {broadcast, t(boolean())}
@ -76,12 +69,9 @@ fields(mqttsn_predefined) ->
, {topic, t(string())} , {topic, t(string())}
]; ];
fields(lwm2m) ->
[{"$id", t(ref(lwm2m_structs))}
];
fields(lwm2m_structs) -> fields(lwm2m_structs) ->
[ {lifetime_min, t(duration())} [ {xml_dir, t(string())}
, {lifetime_min, t(duration())}
, {lifetime_max, t(duration())} , {lifetime_max, t(duration())}
, {qmode_time_windonw, t(integer())} , {qmode_time_windonw, t(integer())}
, {auto_observe, t(boolean())} , {auto_observe, t(boolean())}
@ -91,9 +81,6 @@ fields(lwm2m_structs) ->
, {listener, t(ref(udp_listener_group))} , {listener, t(ref(udp_listener_group))}
]; ];
fields(exproto) ->
[{"$id", t(ref(exproto_structs))}];
fields(exproto_structs) -> fields(exproto_structs) ->
[ {server, t(ref(exproto_grpc_server))} [ {server, t(ref(exproto_grpc_server))}
, {handler, t(ref(exproto_grpc_handler))} , {handler, t(ref(exproto_grpc_handler))}

View File

@ -22,22 +22,16 @@
-export([start_link/0]). -export([start_link/0]).
%% Gateway Instance APIs %% Gateway APIs
-export([ create_gateway_insta/1 -export([ load_gateway/1
, remove_gateway_insta/1 , unload_gateway/1
, lookup_gateway_insta/1 , lookup_gateway/1
, update_gateway_insta/1 , update_gateway/1
, start_gateway_insta/1 , start_gateway_insta/1
, stop_gateway_insta/1 , stop_gateway_insta/1
, list_gateway_insta/1
, list_gateway_insta/0 , list_gateway_insta/0
]). ]).
%% Gateway APs
-export([ list_started_gateway/0
, stop_all_suptree/1
]).
%% supervisor callbacks %% supervisor callbacks
-export([init/1]). -export([init/1]).
@ -48,88 +42,71 @@
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec create_gateway_insta(instance()) -> {ok, pid()} | {error, any()}.
create_gateway_insta(Insta = #{type := Type}) -> -spec load_gateway(gateway()) -> {ok, pid()} | {error, any()}.
case emqx_gateway_registry:lookup(Type) of load_gateway(Gateway = #{type := GwType}) ->
undefined -> {error, {unknown_gateway_id, Type}}; case emqx_gateway_registry:lookup(GwType) of
undefined -> {error, {unknown_gateway_type, GwType}};
GwDscrptr -> GwDscrptr ->
{ok, GwSup} = ensure_gateway_suptree_ready(gatewayid(Type)), {ok, GwSup} = ensure_gateway_suptree_ready(GwType),
emqx_gateway_gw_sup:create_insta(GwSup, Insta, GwDscrptr) emqx_gateway_gw_sup:create_insta(GwSup, Gateway, GwDscrptr)
end. end.
-spec remove_gateway_insta(instance_id()) -> ok | {error, any()}. -spec unload_gateway(gateway_type()) -> ok | {error, not_found}.
remove_gateway_insta(InstaId) -> unload_gateway(GwType) ->
case search_gateway_insta_proc(InstaId) of case lists:keyfind(GwType, 1, supervisor:which_children(?MODULE)) of
{ok, {GwSup, _}} -> false -> {error, not_found};
emqx_gateway_gw_sup:remove_insta(GwSup, InstaId);
_ -> _ ->
_ = supervisor:terminate_child(?MODULE, GwType),
_ = supervisor:delete_child(?MODULE, GwType),
ok ok
end. end.
-spec lookup_gateway_insta(instance_id()) -> instance() | undefined. -spec lookup_gateway(gateway_type()) -> gateway() | undefined.
lookup_gateway_insta(InstaId) -> lookup_gateway(GwType) ->
case search_gateway_insta_proc(InstaId) of case search_gateway_insta_proc(GwType) of
{ok, {_, GwInstaPid}} -> {ok, {_, GwInstaPid}} ->
emqx_gateway_insta_sup:info(GwInstaPid); emqx_gateway_insta_sup:info(GwInstaPid);
_ -> _ ->
undefined undefined
end. end.
-spec update_gateway_insta(instance()) -spec update_gateway(gateway_type())
-> ok -> ok
| {error, any()}. | {error, any()}.
update_gateway_insta(NewInsta = #{type := Type}) -> update_gateway(NewGateway = #{type := GwType}) ->
case emqx_gateway_utils:find_sup_child(?MODULE, gatewayid(Type)) of case emqx_gateway_utils:find_sup_child(?MODULE, GwType) of
{ok, GwSup} -> {ok, GwSup} ->
emqx_gateway_gw_sup:update_insta(GwSup, NewInsta); emqx_gateway_gw_sup:update_insta(GwSup, NewGateway);
_ -> {error, not_found} _ -> {error, not_found}
end. end.
start_gateway_insta(InstaId) -> start_gateway_insta(GwType) ->
case search_gateway_insta_proc(InstaId) of case search_gateway_insta_proc(GwType) of
{ok, {GwSup, _}} -> {ok, {GwSup, _}} ->
emqx_gateway_gw_sup:start_insta(GwSup, InstaId); emqx_gateway_gw_sup:start_insta(GwSup, GwType);
_ -> {error, not_found} _ -> {error, not_found}
end. end.
-spec stop_gateway_insta(instance_id()) -> ok | {error, any()}. -spec stop_gateway_insta(gateway_type()) -> ok | {error, any()}.
stop_gateway_insta(InstaId) -> stop_gateway_insta(GwType) ->
case search_gateway_insta_proc(InstaId) of case search_gateway_insta_proc(GwType) of
{ok, {GwSup, _}} -> {ok, {GwSup, _}} ->
emqx_gateway_gw_sup:stop_insta(GwSup, InstaId); emqx_gateway_gw_sup:stop_insta(GwSup, GwType);
_ -> {error, not_found} _ -> {error, not_found}
end. end.
-spec list_gateway_insta(gateway_type()) -> {ok, [instance()]} | {error, any()}. -spec list_gateway_insta() -> [gateway()].
list_gateway_insta(Type) ->
case emqx_gateway_utils:find_sup_child(?MODULE, gatewayid(Type)) of
{ok, GwSup} ->
{ok, emqx_gateway_gw_sup:list_insta(GwSup)};
_ -> {error, not_found}
end.
-spec list_gateway_insta() -> [{gateway_type(), instance()}].
list_gateway_insta() -> list_gateway_insta() ->
lists:map( lists:append(lists:map(
fun(SupId) -> fun(SupId) ->
Instas = emqx_gateway_gw_sup:list_insta(SupId), emqx_gateway_gw_sup:list_insta(SupId)
{SupId, Instas} end, list_started_gateway())).
end, list_started_gateway()).
-spec list_started_gateway() -> [gateway_type()]. -spec list_started_gateway() -> [gateway_type()].
list_started_gateway() -> list_started_gateway() ->
started_gateway_type(). started_gateway_type().
-spec stop_all_suptree(atom()) -> ok.
stop_all_suptree(Type) ->
case lists:keyfind(Type, 1, supervisor:which_children(?MODULE)) of
false -> ok;
_ ->
_ = supervisor:terminate_child(?MODULE, Type),
_ = supervisor:delete_child(?MODULE, Type),
ok
end.
%% Supervisor callback %% Supervisor callback
init([]) -> init([]) ->
@ -145,17 +122,14 @@ init([]) ->
%% Internal funcs %% Internal funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
gatewayid(Type) -> ensure_gateway_suptree_ready(GwType) ->
list_to_atom(lists:concat([Type])). case lists:keyfind(GwType, 1, supervisor:which_children(?MODULE)) of
ensure_gateway_suptree_ready(Type) ->
case lists:keyfind(Type, 1, supervisor:which_children(?MODULE)) of
false -> false ->
ChildSpec = emqx_gateway_utils:childspec( ChildSpec = emqx_gateway_utils:childspec(
Type, GwType,
supervisor, supervisor,
emqx_gateway_gw_sup, emqx_gateway_gw_sup,
[Type] [GwType]
), ),
emqx_gateway_utils:supervisor_ret( emqx_gateway_utils:supervisor_ret(
supervisor:start_child(?MODULE, ChildSpec) supervisor:start_child(?MODULE, ChildSpec)
@ -190,4 +164,3 @@ started_gateway_pid() ->
is_a_gateway_id(Id) -> is_a_gateway_id(Id) ->
Id /= emqx_gateway_registry. Id /= emqx_gateway_registry.

View File

@ -20,16 +20,13 @@
-behavior(emqx_gateway_impl). -behavior(emqx_gateway_impl).
%% APIs %% APIs
-export([ load/0 -export([ reg/0
, unload/0 , unreg/0
]). ]).
-export([]). -export([ on_gateway_load/2
, on_gateway_update/3
-export([ init/1 , on_gateway_unload/2
, on_insta_create/3
, on_insta_update/4
, on_insta_destroy/3
]). ]).
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
@ -38,24 +35,19 @@
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
load() -> reg() ->
RegistryOptions = [ {cbkmod, ?MODULE} RegistryOptions = [ {cbkmod, ?MODULE}
], ],
emqx_gateway_registry:load(exproto, RegistryOptions, []). emqx_gateway_registry:reg(exproto, RegistryOptions).
unload() ->
emqx_gateway_registry:unload(exproto).
init(_) ->
GwState = #{},
{ok, GwState}.
unreg() ->
emqx_gateway_registry:unreg(exproto).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% emqx_gateway_registry callbacks %% emqx_gateway_registry callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_grpc_server(InstaId, Options = #{bind := ListenOn}) -> start_grpc_server(GwType, Options = #{bind := ListenOn}) ->
Services = #{protos => [emqx_exproto_pb], Services = #{protos => [emqx_exproto_pb],
services => #{ services => #{
'emqx.exproto.v1.ConnectionAdapter' => emqx_exproto_gsvr} 'emqx.exproto.v1.ConnectionAdapter' => emqx_exproto_gsvr}
@ -65,10 +57,10 @@ start_grpc_server(InstaId, Options = #{bind := ListenOn}) ->
SslOpts -> SslOpts ->
[{ssl_options, SslOpts}] [{ssl_options, SslOpts}]
end, end,
_ = grpc:start_server(InstaId, ListenOn, Services, SvrOptions), _ = grpc:start_server(GwType, ListenOn, Services, SvrOptions),
?ULOG("Start ~s gRPC server on ~p successfully.~n", [InstaId, ListenOn]). ?ULOG("Start ~s gRPC server on ~p successfully.~n", [GwType, ListenOn]).
start_grpc_client_channel(InstaId, Options = #{address := UriStr}) -> start_grpc_client_channel(GwType, Options = #{address := UriStr}) ->
UriMap = uri_string:parse(UriStr), UriMap = uri_string:parse(UriStr),
Scheme = maps:get(scheme, UriMap), Scheme = maps:get(scheme, UriMap),
Host = maps:get(host, UriMap), Host = maps:get(host, UriMap),
@ -85,79 +77,79 @@ start_grpc_client_channel(InstaId, Options = #{address := UriStr}) ->
transport_opts => SslOpts}}; transport_opts => SslOpts}};
_ -> #{} _ -> #{}
end, end,
grpc_client_sup:create_channel_pool(InstaId, SvrAddr, ClientOpts). grpc_client_sup:create_channel_pool(GwType, SvrAddr, ClientOpts).
on_insta_create(_Insta = #{ id := InstaId, on_gateway_load(_Gateway = #{ type := GwType,
rawconf := RawConf rawconf := RawConf
}, Ctx, _GwState) -> }, Ctx) ->
%% XXX: How to monitor it ? %% XXX: How to monitor it ?
%% Start grpc client pool & client channel %% Start grpc client pool & client channel
PoolName = pool_name(InstaId), PoolName = pool_name(GwType),
PoolSize = emqx_vm:schedulers() * 2, PoolSize = emqx_vm:schedulers() * 2,
{ok, _} = emqx_pool_sup:start_link(PoolName, hash, PoolSize, {ok, _} = emqx_pool_sup:start_link(PoolName, hash, PoolSize,
{emqx_exproto_gcli, start_link, []}), {emqx_exproto_gcli, start_link, []}),
_ = start_grpc_client_channel(InstaId, maps:get(handler, RawConf)), _ = start_grpc_client_channel(GwType, maps:get(handler, RawConf)),
%% XXX: How to monitor it ? %% XXX: How to monitor it ?
_ = start_grpc_server(InstaId, maps:get(server, RawConf)), _ = start_grpc_server(GwType, maps:get(server, RawConf)),
NRawConf = maps:without( NRawConf = maps:without(
[server, handler], [server, handler],
RawConf#{pool_name => PoolName} RawConf#{pool_name => PoolName}
), ),
Listeners = emqx_gateway_utils:normalize_rawconf( Listeners = emqx_gateway_utils:normalize_rawconf(
NRawConf#{handler => InstaId} NRawConf#{handler => GwType}
), ),
ListenerPids = lists:map(fun(Lis) -> ListenerPids = lists:map(fun(Lis) ->
start_listener(InstaId, Ctx, Lis) start_listener(GwType, Ctx, Lis)
end, Listeners), end, Listeners),
{ok, ListenerPids, _InstaState = #{ctx => Ctx}}. {ok, ListenerPids, _GwState = #{ctx => Ctx}}.
on_insta_update(NewInsta, OldInsta, GwInstaState = #{ctx := Ctx}, GwState) -> on_gateway_update(NewGateway, OldGateway, GwState = #{ctx := Ctx}) ->
InstaId = maps:get(id, NewInsta), GwType = maps:get(type, NewGateway),
try try
%% XXX: 1. How hot-upgrade the changes ??? %% XXX: 1. How hot-upgrade the changes ???
%% XXX: 2. Check the New confs first before destroy old instance ??? %% XXX: 2. Check the New confs first before destroy old instance ???
on_insta_destroy(OldInsta, GwInstaState, GwState), on_gateway_unload(OldGateway, GwState),
on_insta_create(NewInsta, Ctx, GwState) on_gateway_load(NewGateway, Ctx)
catch catch
Class : Reason : Stk -> Class : Reason : Stk ->
logger:error("Failed to update exproto instance ~s; " logger:error("Failed to update ~s; "
"reason: {~0p, ~0p} stacktrace: ~0p", "reason: {~0p, ~0p} stacktrace: ~0p",
[InstaId, Class, Reason, Stk]), [GwType, Class, Reason, Stk]),
{error, {Class, Reason}} {error, {Class, Reason}}
end. end.
on_insta_destroy(_Insta = #{ id := InstaId, on_gateway_unload(_Gateway = #{ type := GwType,
rawconf := RawConf rawconf := RawConf
}, _GwInstaState, _GwState) -> }, _GwState) ->
Listeners = emqx_gateway_utils:normalize_rawconf(RawConf), Listeners = emqx_gateway_utils:normalize_rawconf(RawConf),
lists:foreach(fun(Lis) -> lists:foreach(fun(Lis) ->
stop_listener(InstaId, Lis) stop_listener(GwType, Lis)
end, Listeners). end, Listeners).
pool_name(InstaId) -> pool_name(GwType) ->
list_to_atom(lists:concat([InstaId, "_gcli_pool"])). list_to_atom(lists:concat([GwType, "_gcli_pool"])).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal funcs %% Internal funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_listener(InstaId, Ctx, {Type, ListenOn, SocketOpts, Cfg}) -> start_listener(GwType, Ctx, {Type, ListenOn, SocketOpts, Cfg}) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) of case start_listener(GwType, Ctx, Type, ListenOn, SocketOpts, Cfg) of
{ok, Pid} -> {ok, Pid} ->
?ULOG("Start exproto ~s:~s listener on ~s successfully.~n", ?ULOG("Start ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]), [GwType, Type, ListenOnStr]),
Pid; Pid;
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start exproto ~s:~s listener on ~s: ~0p~n", ?ELOG("Failed to start ~s:~s listener on ~s: ~0p~n",
[InstaId, Type, ListenOnStr, Reason]), [GwType, Type, ListenOnStr, Reason]),
throw({badconf, Reason}) throw({badconf, Reason})
end. end.
start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) -> start_listener(GwType, Ctx, Type, ListenOn, SocketOpts, Cfg) ->
Name = name(InstaId, Type), Name = name(GwType, Type),
NCfg = Cfg#{ NCfg = Cfg#{
ctx => Ctx, ctx => Ctx,
frame_mod => emqx_exproto_frame, frame_mod => emqx_exproto_frame,
@ -176,8 +168,8 @@ do_start_listener(udp, Name, ListenOn, Opts, MFA) ->
do_start_listener(dtls, Name, ListenOn, Opts, MFA) -> do_start_listener(dtls, Name, ListenOn, Opts, MFA) ->
esockd:open_dtls(Name, ListenOn, Opts, MFA). esockd:open_dtls(Name, ListenOn, Opts, MFA).
name(InstaId, Type) -> name(GwType, Type) ->
list_to_atom(lists:concat([InstaId, ":", Type])). list_to_atom(lists:concat([GwType, ":", Type])).
merge_default_by_type(Type, Options) when Type =:= tcp; merge_default_by_type(Type, Options) when Type =:= tcp;
Type =:= ssl -> Type =:= ssl ->
@ -200,18 +192,18 @@ merge_default_by_type(Type, Options) when Type =:= udp;
[{udp_options, Default} | Options] [{udp_options, Default} | Options]
end. end.
stop_listener(InstaId, {Type, ListenOn, SocketOpts, Cfg}) -> stop_listener(GwType, {Type, ListenOn, SocketOpts, Cfg}) ->
StopRet = stop_listener(InstaId, Type, ListenOn, SocketOpts, Cfg), StopRet = stop_listener(GwType, Type, ListenOn, SocketOpts, Cfg),
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of case StopRet of
ok -> ?ULOG("Stop exproto ~s:~s listener on ~s successfully.~n", ok -> ?ULOG("Stop ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]); [GwType, Type, ListenOnStr]);
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to stop exproto ~s:~s listener on ~s: ~0p~n", ?ELOG("Failed to stop ~s:~s listener on ~s: ~0p~n",
[InstaId, Type, ListenOnStr, Reason]) [GwType, Type, ListenOnStr, Reason])
end, end,
StopRet. StopRet.
stop_listener(InstaId, Type, ListenOn, _SocketOpts, _Cfg) -> stop_listener(GwType, Type, ListenOn, _SocketOpts, _Cfg) ->
Name = name(InstaId, Type), Name = name(GwType, Type),
esockd:close(Name, ListenOn). esockd:close(Name, ListenOn).

View File

@ -20,36 +20,37 @@
-behavior(emqx_gateway_impl). -behavior(emqx_gateway_impl).
%% APIs %% APIs
-export([ load/0 -export([ reg/0
, unload/0 , unreg/0
]). ]).
-export([]). -export([ on_gateway_load/2
, on_gateway_update/3
-export([ init/1 , on_gateway_unload/2
, on_insta_create/3
, on_insta_update/4
, on_insta_destroy/3
]). ]).
-include_lib("emqx/include/logger.hrl").
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
load() -> reg() ->
RegistryOptions = [ {cbkmod, ?MODULE} RegistryOptions = [ {cbkmod, ?MODULE}
], ],
emqx_gateway_registry:load(lwm2m, RegistryOptions, []). emqx_gateway_registry:reg(lwm2m, RegistryOptions).
unload() -> unreg() ->
%% XXX: emqx_gateway_registry:unreg(lwm2m).
lwm2m_coap_server_registry:remove_handler(
[<<"rd">>], %%--------------------------------------------------------------------
emqx_lwm2m_coap_resource, undefined %% emqx_gateway_registry callbacks
), %%--------------------------------------------------------------------
emqx_gateway_registry:unload(lwm2m).
on_gateway_load(_Gateway = #{ type := GwType,
rawconf := RawConf
}, Ctx) ->
init(_) ->
%% Handler %% Handler
_ = lwm2m_coap_server:start_registry(), _ = lwm2m_coap_server:start_registry(),
lwm2m_coap_server_registry:add_handler( lwm2m_coap_server_registry:add_handler(
@ -57,75 +58,66 @@ init(_) ->
emqx_lwm2m_coap_resource, undefined emqx_lwm2m_coap_resource, undefined
), ),
%% Xml registry %% Xml registry
{ok, _} = emqx_lwm2m_xml_object_db:start_link( {ok, _} = emqx_lwm2m_xml_object_db:start_link(maps:get(xml_dir, RawConf)),
emqx_config:get([gateway, lwm2m_xml_dir])
),
%% XXX: Self managed table? %% XXX: Self managed table?
%% TODO: Improve it later %% TODO: Improve it later
{ok, _} = emqx_lwm2m_cm:start_link(), {ok, _} = emqx_lwm2m_cm:start_link(),
GwState = #{},
{ok, GwState}.
%% TODO: deinit
%%--------------------------------------------------------------------
%% emqx_gateway_registry callbacks
%%--------------------------------------------------------------------
on_insta_create(_Insta = #{ id := InstaId,
rawconf := RawConf
}, Ctx, _GwState) ->
Listeners = emqx_gateway_utils:normalize_rawconf(RawConf), Listeners = emqx_gateway_utils:normalize_rawconf(RawConf),
ListenerPids = lists:map(fun(Lis) -> ListenerPids = lists:map(fun(Lis) ->
start_listener(InstaId, Ctx, Lis) start_listener(GwType, Ctx, Lis)
end, Listeners), end, Listeners),
{ok, ListenerPids, _InstaState = #{ctx => Ctx}}. {ok, ListenerPids, _GwState = #{ctx => Ctx}}.
on_insta_update(NewInsta, OldInsta, GwInstaState = #{ctx := Ctx}, GwState) -> on_gateway_update(NewGateway, OldGateway, GwState = #{ctx := Ctx}) ->
InstaId = maps:get(id, NewInsta), GwType = maps:get(type, NewGateway),
try try
%% XXX: 1. How hot-upgrade the changes ??? %% XXX: 1. How hot-upgrade the changes ???
%% XXX: 2. Check the New confs first before destroy old instance ??? %% XXX: 2. Check the New confs first before destroy old instance ???
on_insta_destroy(OldInsta, GwInstaState, GwState), on_gateway_unload(OldGateway, GwState),
on_insta_create(NewInsta, Ctx, GwState) on_gateway_load(NewGateway, Ctx)
catch catch
Class : Reason : Stk -> Class : Reason : Stk ->
logger:error("Failed to update stomp instance ~s; " logger:error("Failed to update ~s; "
"reason: {~0p, ~0p} stacktrace: ~0p", "reason: {~0p, ~0p} stacktrace: ~0p",
[InstaId, Class, Reason, Stk]), [GwType, Class, Reason, Stk]),
{error, {Class, Reason}} {error, {Class, Reason}}
end. end.
on_insta_destroy(_Insta = #{ id := InstaId, on_gateway_unload(_Gateway = #{ type := GwType,
rawconf := RawConf rawconf := RawConf
}, _GwInstaState, _GwState) -> }, _GwState) ->
%% XXX:
lwm2m_coap_server_registry:remove_handler(
[<<"rd">>],
emqx_lwm2m_coap_resource, undefined
),
Listeners = emqx_gateway_utils:normalize_rawconf(RawConf), Listeners = emqx_gateway_utils:normalize_rawconf(RawConf),
lists:foreach(fun(Lis) -> lists:foreach(fun(Lis) ->
stop_listener(InstaId, Lis) stop_listener(GwType, Lis)
end, Listeners). end, Listeners).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal funcs %% Internal funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_listener(InstaId, Ctx, {Type, ListenOn, SocketOpts, Cfg}) -> start_listener(GwType, Ctx, {Type, ListenOn, SocketOpts, Cfg}) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) of case start_listener(GwType, Ctx, Type, ListenOn, SocketOpts, Cfg) of
{ok, Pid} -> {ok, Pid} ->
io:format("Start lwm2m ~s:~s listener on ~s successfully.~n", ?ULOG("Start ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]), [GwType, Type, ListenOnStr]),
Pid; Pid;
{error, Reason} -> {error, Reason} ->
io:format(standard_error, ?ELOG("Failed to start ~s:~s listener on ~s: ~0p~n",
"Failed to start lwm2m ~s:~s listener on ~s: ~0p~n", [GwType, Type, ListenOnStr, Reason]),
[InstaId, Type, ListenOnStr, Reason]),
throw({badconf, Reason}) throw({badconf, Reason})
end. end.
start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) -> start_listener(GwType, Ctx, Type, ListenOn, SocketOpts, Cfg) ->
Name = name(InstaId, udp), Name = name(GwType, udp),
NCfg = Cfg#{ctx => Ctx}, NCfg = Cfg#{ctx => Ctx},
NSocketOpts = merge_default(SocketOpts), NSocketOpts = merge_default(SocketOpts),
Options = [{config, NCfg}|NSocketOpts], Options = [{config, NCfg}|NSocketOpts],
@ -136,8 +128,8 @@ start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) ->
lwm2m_coap_server:start_dtls(Name, ListenOn, Options) lwm2m_coap_server:start_dtls(Name, ListenOn, Options)
end. end.
name(InstaId, Type) -> name(GwType, Type) ->
list_to_atom(lists:concat([InstaId, ":", Type])). list_to_atom(lists:concat([GwType, ":", Type])).
merge_default(Options) -> merge_default(Options) ->
Default = emqx_gateway_utils:default_udp_options(), Default = emqx_gateway_utils:default_udp_options(),
@ -149,22 +141,20 @@ merge_default(Options) ->
[{udp_options, Default} | Options] [{udp_options, Default} | Options]
end. end.
stop_listener(InstaId, {Type, ListenOn, SocketOpts, Cfg}) -> stop_listener(GwType, {Type, ListenOn, SocketOpts, Cfg}) ->
StopRet = stop_listener(InstaId, Type, ListenOn, SocketOpts, Cfg), StopRet = stop_listener(GwType, Type, ListenOn, SocketOpts, Cfg),
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of case StopRet of
ok -> io:format("Stop lwm2m ~s:~s listener on ~s successfully.~n", ok -> ?ULOG("Stop ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]); [GwType, Type, ListenOnStr]);
{error, Reason} -> {error, Reason} ->
io:format(standard_error, ?ELOG("Failed to stop ~s:~s listener on ~s: ~0p~n",
"Failed to stop lwm2m ~s:~s listener on ~s: ~0p~n", [GwType, Type, ListenOnStr, Reason])
[InstaId, Type, ListenOnStr, Reason]
)
end, end,
StopRet. StopRet.
stop_listener(InstaId, Type, ListenOn, _SocketOpts, _Cfg) -> stop_listener(GwType, Type, ListenOn, _SocketOpts, _Cfg) ->
Name = name(InstaId, Type), Name = name(GwType, Type),
case Type of case Type of
udp -> udp ->
lwm2m_coap_server:stop_udp(Name, ListenOn); lwm2m_coap_server:stop_udp(Name, ListenOn);

View File

@ -20,16 +20,13 @@
-behavior(emqx_gateway_impl). -behavior(emqx_gateway_impl).
%% APIs %% APIs
-export([ load/0 -export([ reg/0
, unload/0 , unreg/0
]). ]).
-export([]). -export([ on_gateway_load/2
, on_gateway_update/3
-export([ init/1 , on_gateway_unload/2
, on_insta_create/3
, on_insta_update/4
, on_insta_destroy/3
]). ]).
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
@ -38,25 +35,21 @@
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
load() -> reg() ->
RegistryOptions = [ {cbkmod, ?MODULE} RegistryOptions = [ {cbkmod, ?MODULE}
], ],
emqx_gateway_registry:load(mqttsn, RegistryOptions, []). emqx_gateway_registry:reg(mqttsn, RegistryOptions).
unload() -> unreg() ->
emqx_gateway_registry:unload(mqttsn). emqx_gateway_registry:unreg(mqttsn).
init(_) ->
GwState = #{},
{ok, GwState}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% emqx_gateway_registry callbacks %% emqx_gateway_registry callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
on_insta_create(_Insta = #{ id := InstaId, on_gateway_load(_Gateway = #{ type := GwType,
rawconf := RawConf rawconf := RawConf
}, Ctx, _GwState) -> }, Ctx) ->
%% We Also need to start `emqx_sn_broadcast` & %% We Also need to start `emqx_sn_broadcast` &
%% `emqx_sn_registry` process %% `emqx_sn_registry` process
@ -71,7 +64,7 @@ on_insta_create(_Insta = #{ id := InstaId,
end, end,
PredefTopics = maps:get(predefined, RawConf), PredefTopics = maps:get(predefined, RawConf),
{ok, RegistrySvr} = emqx_sn_registry:start_link(InstaId, PredefTopics), {ok, RegistrySvr} = emqx_sn_registry:start_link(GwType, PredefTopics),
NRawConf = maps:without( NRawConf = maps:without(
[broadcast, predefined], [broadcast, predefined],
@ -80,52 +73,52 @@ on_insta_create(_Insta = #{ id := InstaId,
Listeners = emqx_gateway_utils:normalize_rawconf(NRawConf), Listeners = emqx_gateway_utils:normalize_rawconf(NRawConf),
ListenerPids = lists:map(fun(Lis) -> ListenerPids = lists:map(fun(Lis) ->
start_listener(InstaId, Ctx, Lis) start_listener(GwType, Ctx, Lis)
end, Listeners), end, Listeners),
{ok, ListenerPids, _InstaState = #{ctx => Ctx}}. {ok, ListenerPids, _InstaState = #{ctx => Ctx}}.
on_insta_update(NewInsta, OldInsta, GwInstaState = #{ctx := Ctx}, GwState) -> on_gateway_update(NewGateway = #{type := GwType}, OldGateway,
InstaId = maps:get(id, NewInsta), GwState = #{ctx := Ctx}) ->
try try
%% XXX: 1. How hot-upgrade the changes ??? %% XXX: 1. How hot-upgrade the changes ???
%% XXX: 2. Check the New confs first before destroy old instance ??? %% XXX: 2. Check the New confs first before destroy old instance ???
on_insta_destroy(OldInsta, GwInstaState, GwState), on_gateway_unload(OldGateway, GwState),
on_insta_create(NewInsta, Ctx, GwState) on_gateway_load(NewGateway, Ctx)
catch catch
Class : Reason : Stk -> Class : Reason : Stk ->
logger:error("Failed to update stomp instance ~s; " logger:error("Failed to update ~s; "
"reason: {~0p, ~0p} stacktrace: ~0p", "reason: {~0p, ~0p} stacktrace: ~0p",
[InstaId, Class, Reason, Stk]), [GwType, Class, Reason, Stk]),
{error, {Class, Reason}} {error, {Class, Reason}}
end. end.
on_insta_destroy(_Insta = #{ id := InstaId, on_gateway_unload(_Insta = #{ type := GwType,
rawconf := RawConf rawconf := RawConf
}, _GwInstaState, _GwState) -> }, _GwState) ->
Listeners = emqx_gateway_utils:normalize_rawconf(RawConf), Listeners = emqx_gateway_utils:normalize_rawconf(RawConf),
lists:foreach(fun(Lis) -> lists:foreach(fun(Lis) ->
stop_listener(InstaId, Lis) stop_listener(GwType, Lis)
end, Listeners). end, Listeners).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal funcs %% Internal funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_listener(InstaId, Ctx, {Type, ListenOn, SocketOpts, Cfg}) -> start_listener(GwType, Ctx, {Type, ListenOn, SocketOpts, Cfg}) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) of case start_listener(GwType, Ctx, Type, ListenOn, SocketOpts, Cfg) of
{ok, Pid} -> {ok, Pid} ->
?ULOG("Start mqttsn ~s:~s listener on ~s successfully.~n", ?ULOG("Start ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]), [GwType, Type, ListenOnStr]),
Pid; Pid;
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start mqttsn ~s:~s listener on ~s: ~0p~n", ?ELOG("Failed to start ~s:~s listener on ~s: ~0p~n",
[InstaId, Type, ListenOnStr, Reason]), [GwType, Type, ListenOnStr, Reason]),
throw({badconf, Reason}) throw({badconf, Reason})
end. end.
start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) -> start_listener(GwType, Ctx, Type, ListenOn, SocketOpts, Cfg) ->
Name = name(InstaId, Type), Name = name(GwType, Type),
NCfg = Cfg#{ NCfg = Cfg#{
ctx => Ctx, ctx => Ctx,
frame_mod => emqx_sn_frame, frame_mod => emqx_sn_frame,
@ -134,8 +127,8 @@ start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) ->
esockd:open_udp(Name, ListenOn, merge_default(SocketOpts), esockd:open_udp(Name, ListenOn, merge_default(SocketOpts),
{emqx_gateway_conn, start_link, [NCfg]}). {emqx_gateway_conn, start_link, [NCfg]}).
name(InstaId, Type) -> name(GwType, Type) ->
list_to_atom(lists:concat([InstaId, ":", Type])). list_to_atom(lists:concat([GwType, ":", Type])).
merge_default(Options) -> merge_default(Options) ->
Default = emqx_gateway_utils:default_udp_options(), Default = emqx_gateway_utils:default_udp_options(),
@ -147,18 +140,18 @@ merge_default(Options) ->
[{udp_options, Default} | Options] [{udp_options, Default} | Options]
end. end.
stop_listener(InstaId, {Type, ListenOn, SocketOpts, Cfg}) -> stop_listener(GwType, {Type, ListenOn, SocketOpts, Cfg}) ->
StopRet = stop_listener(InstaId, Type, ListenOn, SocketOpts, Cfg), StopRet = stop_listener(GwType, Type, ListenOn, SocketOpts, Cfg),
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of case StopRet of
ok -> ?ULOG("Stop mqttsn ~s:~s listener on ~s successfully.~n", ok -> ?ULOG("Stop ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]); [GwType, Type, ListenOnStr]);
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to stop mqttsn ~s:~s listener on ~s: ~0p~n", ?ELOG("Failed to stop ~s:~s listener on ~s: ~0p~n",
[InstaId, Type, ListenOnStr, Reason]) [GwType, Type, ListenOnStr, Reason])
end, end,
StopRet. StopRet.
stop_listener(InstaId, Type, ListenOn, _SocketOpts, _Cfg) -> stop_listener(GwType, Type, ListenOn, _SocketOpts, _Cfg) ->
Name = name(InstaId, Type), Name = name(GwType, Type),
esockd:close(Name, ListenOn). esockd:close(Name, ListenOn).

View File

@ -19,14 +19,13 @@
-behavior(emqx_gateway_impl). -behavior(emqx_gateway_impl).
%% APIs %% APIs
-export([ load/0 -export([ reg/0
, unload/0 , unreg/0
]). ]).
-export([ init/1 -export([ on_gateway_load/2
, on_insta_create/3 , on_gateway_update/3
, on_insta_update/4 , on_gateway_unload/2
, on_insta_destroy/3
]). ]).
-include_lib("emqx_gateway/include/emqx_gateway.hrl"). -include_lib("emqx_gateway/include/emqx_gateway.hrl").
@ -36,79 +35,75 @@
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec load() -> ok | {error, any()}. -spec reg() -> ok | {error, any()}.
load() -> reg() ->
RegistryOptions = [ {cbkmod, ?MODULE} RegistryOptions = [ {cbkmod, ?MODULE}
], ],
emqx_gateway_registry:load(stomp, RegistryOptions, []). emqx_gateway_registry:reg(stomp, RegistryOptions).
-spec unload() -> ok | {error, any()}. -spec unreg() -> ok | {error, any()}.
unload() -> unreg() ->
emqx_gateway_registry:unload(stomp). emqx_gateway_registry:unreg(stomp).
init(_) ->
GwState = #{},
{ok, GwState}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% emqx_gateway_registry callbacks %% emqx_gateway_registry callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
on_insta_create(_Insta = #{ id := InstaId, on_gateway_load(_Gateway = #{ type := GwType,
rawconf := RawConf rawconf := RawConf
}, Ctx, _GwState) -> }, Ctx) ->
%% Step1. Fold the rawconfs to listeners %% Step1. Fold the rawconfs to listeners
Listeners = emqx_gateway_utils:normalize_rawconf(RawConf), Listeners = emqx_gateway_utils:normalize_rawconf(RawConf),
%% Step2. Start listeners or escokd:specs %% Step2. Start listeners or escokd:specs
ListenerPids = lists:map(fun(Lis) -> ListenerPids = lists:map(fun(Lis) ->
start_listener(InstaId, Ctx, Lis) start_listener(GwType, Ctx, Lis)
end, Listeners), end, Listeners),
%% FIXME: How to throw an exception to interrupt the restart logic ? %% FIXME: How to throw an exception to interrupt the restart logic ?
%% FIXME: Assign ctx to InstaState %% FIXME: Assign ctx to GwState
{ok, ListenerPids, _InstaState = #{ctx => Ctx}}. {ok, ListenerPids, _GwState = #{ctx => Ctx}}.
on_insta_update(NewInsta, OldInsta, GwInstaState = #{ctx := Ctx}, GwState) -> on_gateway_update(NewGateway, OldGateway, GwState = #{ctx := Ctx}) ->
InstaId = maps:get(id, NewInsta), GwType = maps:get(type, NewGateway),
try try
%% XXX: 1. How hot-upgrade the changes ??? %% XXX: 1. How hot-upgrade the changes ???
%% XXX: 2. Check the New confs first before destroy old instance ??? %% XXX: 2. Check the New confs first before destroy old state???
on_insta_destroy(OldInsta, GwInstaState, GwState), on_gateway_unload(OldGateway, GwState),
on_insta_create(NewInsta, Ctx, GwState) on_gateway_load(NewGateway, Ctx)
catch catch
Class : Reason : Stk -> Class : Reason : Stk ->
logger:error("Failed to update stomp instance ~s; " logger:error("Failed to update ~s; "
"reason: {~0p, ~0p} stacktrace: ~0p", "reason: {~0p, ~0p} stacktrace: ~0p",
[InstaId, Class, Reason, Stk]), [GwType, Class, Reason, Stk]),
{error, {Class, Reason}} {error, {Class, Reason}}
end. end.
on_insta_destroy(_Insta = #{ id := InstaId, on_gateway_unload(_Gateway = #{ type := GwType,
rawconf := RawConf rawconf := RawConf
}, _GwInstaState, _GwState) -> }, _GwState) ->
Listeners = emqx_gateway_utils:normalize_rawconf(RawConf), Listeners = emqx_gateway_utils:normalize_rawconf(RawConf),
lists:foreach(fun(Lis) -> lists:foreach(fun(Lis) ->
stop_listener(InstaId, Lis) stop_listener(GwType, Lis)
end, Listeners). end, Listeners).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal funcs %% Internal funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_listener(InstaId, Ctx, {Type, ListenOn, SocketOpts, Cfg}) -> start_listener(GwType, Ctx, {Type, ListenOn, SocketOpts, Cfg}) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) of case start_listener(GwType, Ctx, Type, ListenOn, SocketOpts, Cfg) of
{ok, Pid} -> {ok, Pid} ->
?ULOG("Start stomp ~s:~s listener on ~s successfully.~n", ?ULOG("Start ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]), [GwType, Type, ListenOnStr]),
Pid; Pid;
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start stomp ~s:~s listener on ~s: ~0p~n", ?ELOG("Failed to start ~s:~s listener on ~s: ~0p~n",
[InstaId, Type, ListenOnStr, Reason]), [GwType, Type, ListenOnStr, Reason]),
throw({badconf, Reason}) throw({badconf, Reason})
end. end.
start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) -> start_listener(GwType, Ctx, Type, ListenOn, SocketOpts, Cfg) ->
Name = name(InstaId, Type), Name = name(GwType, Type),
NCfg = Cfg#{ NCfg = Cfg#{
ctx => Ctx, ctx => Ctx,
frame_mod => emqx_stomp_frame, frame_mod => emqx_stomp_frame,
@ -117,8 +112,8 @@ start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) ->
esockd:open(Name, ListenOn, merge_default(SocketOpts), esockd:open(Name, ListenOn, merge_default(SocketOpts),
{emqx_gateway_conn, start_link, [NCfg]}). {emqx_gateway_conn, start_link, [NCfg]}).
name(InstaId, Type) -> name(GwType, Type) ->
list_to_atom(lists:concat([InstaId, ":", Type])). list_to_atom(lists:concat([GwType, ":", Type])).
merge_default(Options) -> merge_default(Options) ->
Default = emqx_gateway_utils:default_tcp_options(), Default = emqx_gateway_utils:default_tcp_options(),
@ -130,18 +125,18 @@ merge_default(Options) ->
[{tcp_options, Default} | Options] [{tcp_options, Default} | Options]
end. end.
stop_listener(InstaId, {Type, ListenOn, SocketOpts, Cfg}) -> stop_listener(GwType, {Type, ListenOn, SocketOpts, Cfg}) ->
StopRet = stop_listener(InstaId, Type, ListenOn, SocketOpts, Cfg), StopRet = stop_listener(GwType, Type, ListenOn, SocketOpts, Cfg),
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of case StopRet of
ok -> ?ULOG("Stop stomp ~s:~s listener on ~s successfully.~n", ok -> ?ULOG("Stop ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]); [GwType, Type, ListenOnStr]);
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to stop stomp ~s:~s listener on ~s: ~0p~n", ?ELOG("Failed to stop ~s:~s listener on ~s: ~0p~n",
[InstaId, Type, ListenOnStr, Reason]) [GwType, Type, ListenOnStr, Reason])
end, end,
StopRet. StopRet.
stop_listener(InstaId, Type, ListenOn, _SocketOpts, _Cfg) -> stop_listener(GwType, Type, ListenOn, _SocketOpts, _Cfg) ->
Name = name(InstaId, Type), Name = name(GwType, Type),
esockd:close(Name, ListenOn). esockd:close(Name, ListenOn).

View File

@ -67,18 +67,17 @@ set_special_cfg(emqx_gateway) ->
LisType = get(grpname), LisType = get(grpname),
emqx_config:put( emqx_config:put(
[gateway, exproto], [gateway, exproto],
#{'1' =>
#{authenticator => allow_anonymous, #{authenticator => allow_anonymous,
server => #{bind => 9100}, server => #{bind => 9100},
handler => #{address => "http://127.0.0.1:9001"}, handler => #{address => "http://127.0.0.1:9001"},
listener => listener_confs(LisType) listener => listener_confs(LisType)
}}); });
set_special_cfg(_App) -> set_special_cfg(_App) ->
ok. ok.
listener_confs(Type) -> listener_confs(Type) ->
Default = #{bind => 7993, acceptors => 8}, Default = #{bind => 7993, acceptors => 8},
#{Type => #{'1' => maps:merge(Default, maps:from_list(socketopts(Type)))}}. #{Type => maps:merge(Default, maps:from_list(socketopts(Type)))}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Tests cases %% Tests cases

View File

@ -23,7 +23,7 @@
-define(CONF_DEFAULT, <<""" -define(CONF_DEFAULT, <<"""
gateway: { gateway: {
stomp.1 {} stomp {}
} }
""">>). """>>).

View File

@ -30,8 +30,8 @@
-define(CONF_DEFAULT, <<" -define(CONF_DEFAULT, <<"
gateway: { gateway: {
lwm2m_xml_dir: \"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml\" lwm2m: {
lwm2m.1: { xml_dir: \"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml\"
lifetime_min: 1s lifetime_min: 1s
lifetime_max: 86400s lifetime_max: 86400s
qmode_time_windonw: 22 qmode_time_windonw: 22

View File

@ -51,9 +51,9 @@
-define(CLIENTID, iolist_to_binary([atom_to_list(?FUNCTION_NAME), "-", -define(CLIENTID, iolist_to_binary([atom_to_list(?FUNCTION_NAME), "-",
integer_to_list(erlang:system_time())])). integer_to_list(erlang:system_time())])).
-define(CONF_DEFAULT, <<""" -define(CONF_DEFAULT, <<"
gateway: { gateway: {
mqttsn.1: { mqttsn: {
gateway_id: 1 gateway_id: 1
broadcast: true broadcast: true
enable_stats: true enable_stats: true
@ -73,7 +73,7 @@ gateway: {
} }
} }
} }
""">>). ">>).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Setups %% Setups
@ -90,35 +90,6 @@ init_per_suite(Config) ->
end_per_suite(_) -> end_per_suite(_) ->
emqx_ct_helpers:stop_apps([emqx_gateway]). emqx_ct_helpers:stop_apps([emqx_gateway]).
set_special_confs(emqx_gateway) ->
emqx_config:put(
[gateway],
#{ mqttsn =>
#{'1' =>
#{broadcast => true,
clientinfo_override =>
#{password => "pw123",
username => "user1"
},
enable_qos3 => true,
enable_stats => true,
gateway_id => 1,
idle_timeout => 30000,
listener =>
#{udp =>
#{'1' =>
#{acceptors => 8,active_n => 100,backlog => 1024,bind => 1884,
high_watermark => 1048576,max_conn_rate => 1000,
max_connections => 10240000,send_timeout => 15000,
send_timeout_close => true}}},
predefined =>
[#{id => ?PREDEF_TOPIC_ID1, topic => ?PREDEF_TOPIC_NAME1},
#{id => ?PREDEF_TOPIC_ID2, topic => ?PREDEF_TOPIC_NAME2}]}}
});
set_special_confs(_App) ->
ok.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Test cases %% Test cases
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -26,8 +26,6 @@
-define(PREDEF_TOPICS, [#{id => 1, topic => <<"/predefined/topic/name/hello">>}, -define(PREDEF_TOPICS, [#{id => 1, topic => <<"/predefined/topic/name/hello">>},
#{id => 2, topic => <<"/predefined/topic/name/nice">>}]). #{id => 2, topic => <<"/predefined/topic/name/nice">>}]).
-define(INSTA_ID, 'mqttsn#1').
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Setups %% Setups
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -45,7 +43,7 @@ end_per_suite(_Config) ->
ok. ok.
init_per_testcase(_TestCase, Config) -> init_per_testcase(_TestCase, Config) ->
{ok, Pid} = ?REGISTRY:start_link(?INSTA_ID, ?PREDEF_TOPICS), {ok, Pid} = ?REGISTRY:start_link('mqttsn', ?PREDEF_TOPICS),
{Tab, Pid} = ?REGISTRY:lookup_name(Pid), {Tab, Pid} = ?REGISTRY:lookup_name(Pid),
[{reg, {Tab, Pid}} | Config]. [{reg, {Tab, Pid}} | Config].

View File

@ -23,9 +23,9 @@
-define(HEARTBEAT, <<$\n>>). -define(HEARTBEAT, <<$\n>>).
-define(CONF_DEFAULT, <<""" -define(CONF_DEFAULT, <<"
gateway: { gateway: {
stomp.1: { stomp: {
clientinfo_override: { clientinfo_override: {
username: \"${Packet.headers.login}\" username: \"${Packet.headers.login}\"
password: \"${Packet.headers.passcode}\" password: \"${Packet.headers.passcode}\"
@ -35,7 +35,7 @@ gateway: {
} }
} }
} }
""">>). ">>).
all() -> emqx_ct:all(?MODULE). all() -> emqx_ct:all(?MODULE).