fix(gateway): improve gateway schema modules

1. enhances the gateway name as an enum
2. make the schema more flexible and extensible without some hardcode
This commit is contained in:
firest 2023-11-01 17:47:56 +08:00
parent 5e314d4ef1
commit 30a72f557f
11 changed files with 107 additions and 74 deletions

View File

@ -93,10 +93,9 @@ gateways(get, Request) ->
gateway(get, #{bindings := #{name := Name}}) -> gateway(get, #{bindings := #{name := Name}}) ->
try try
GwName = gw_name(Name), case emqx_gateway:lookup(Name) of
case emqx_gateway:lookup(GwName) of
undefined -> undefined ->
{200, #{name => GwName, status => unloaded}}; {200, #{name => Name, status => unloaded}};
Gateway -> Gateway ->
GwConf = emqx_gateway_conf:gateway(Name), GwConf = emqx_gateway_conf:gateway(Name),
GwInfo0 = emqx_gateway_utils:unix_ts_to_rfc3339( GwInfo0 = emqx_gateway_utils:unix_ts_to_rfc3339(
@ -125,15 +124,14 @@ gateway(put, #{
}) -> }) ->
GwConf = maps:without([<<"name">>], GwConf0), GwConf = maps:without([<<"name">>], GwConf0),
try try
GwName = gw_name(Name),
LoadOrUpdateF = LoadOrUpdateF =
case emqx_gateway:lookup(GwName) of case emqx_gateway:lookup(Name) of
undefined -> undefined ->
fun emqx_gateway_conf:load_gateway/2; fun emqx_gateway_conf:load_gateway/2;
_ -> _ ->
fun emqx_gateway_conf:update_gateway/2 fun emqx_gateway_conf:update_gateway/2
end, end,
case LoadOrUpdateF(GwName, GwConf) of case LoadOrUpdateF(Name, GwConf) of
{ok, _} -> {ok, _} ->
{204}; {204};
{error, Reason} -> {error, Reason} ->
@ -148,12 +146,11 @@ gateway(put, #{
gateway_enable(put, #{bindings := #{name := Name, enable := Enable}}) -> gateway_enable(put, #{bindings := #{name := Name, enable := Enable}}) ->
try try
GwName = gw_name(Name), case emqx_gateway:lookup(Name) of
case emqx_gateway:lookup(GwName) of
undefined -> undefined ->
return_http_error(404, <<"NOT FOUND">>); return_http_error(404, <<"NOT FOUND">>);
_Gateway -> _Gateway ->
{ok, _} = emqx_gateway_conf:update_gateway(GwName, #{<<"enable">> => Enable}), {ok, _} = emqx_gateway_conf:update_gateway(Name, #{<<"enable">> => Enable}),
{204} {204}
end end
catch catch
@ -161,15 +158,6 @@ gateway_enable(put, #{bindings := #{name := Name, enable := Enable}}) ->
return_http_error(404, <<"NOT FOUND">>) return_http_error(404, <<"NOT FOUND">>)
end. end.
-spec gw_name(binary()) -> stomp | coap | lwm2m | mqttsn | exproto | gbt32960 | no_return().
gw_name(<<"stomp">>) -> stomp;
gw_name(<<"coap">>) -> coap;
gw_name(<<"lwm2m">>) -> lwm2m;
gw_name(<<"mqttsn">>) -> mqttsn;
gw_name(<<"exproto">>) -> exproto;
gw_name(<<"gbt32960">>) -> gbt32960;
gw_name(_Else) -> throw(not_found).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Swagger defines %% Swagger defines
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -250,7 +238,7 @@ params_gateway_name_in_path() ->
[ [
{name, {name,
mk( mk(
binary(), hoconsc:enum(emqx_gateway_schema:gateway_names()),
#{ #{
in => path, in => path,
desc => ?DESC(gateway_name_in_qs), desc => ?DESC(gateway_name_in_qs),
@ -450,33 +438,30 @@ fields(gateway_stats) ->
[{key, mk(binary(), #{})}]. [{key, mk(binary(), #{})}].
schema_load_or_update_gateways_conf() -> schema_load_or_update_gateways_conf() ->
Names = emqx_gateway_schema:gateway_names(),
emqx_dashboard_swagger:schema_with_examples( emqx_dashboard_swagger:schema_with_examples(
hoconsc:union([ hoconsc:union(
ref(?MODULE, stomp), [
ref(?MODULE, mqttsn), ref(?MODULE, Name)
ref(?MODULE, coap), || Name <-
ref(?MODULE, lwm2m), Names ++
ref(?MODULE, exproto), [
ref(?MODULE, update_stomp), erlang:list_to_existing_atom("update_" ++ erlang:atom_to_list(Name))
ref(?MODULE, update_mqttsn), || Name <- Names
ref(?MODULE, update_coap), ]
ref(?MODULE, update_lwm2m), ]
ref(?MODULE, update_exproto), ),
ref(?MODULE, update_gbt32960)
]),
examples_update_gateway_confs() examples_update_gateway_confs()
). ).
schema_gateways_conf() -> schema_gateways_conf() ->
emqx_dashboard_swagger:schema_with_examples( emqx_dashboard_swagger:schema_with_examples(
hoconsc:union([ hoconsc:union(
ref(?MODULE, stomp), [
ref(?MODULE, mqttsn), ref(?MODULE, Name)
ref(?MODULE, coap), || Name <- emqx_gateway_schema:gateway_names()
ref(?MODULE, lwm2m), ]
ref(?MODULE, exproto), ),
ref(?MODULE, gbt32960)
]),
examples_gateway_confs() examples_gateway_confs()
). ).

View File

@ -327,7 +327,7 @@ params_gateway_name_in_path() ->
[ [
{name, {name,
mk( mk(
binary(), hoconsc:enum(emqx_gateway_schema:gateway_names()),
#{ #{
in => path, in => path,
desc => ?DESC(emqx_gateway_api, gateway_name_in_qs), desc => ?DESC(emqx_gateway_api, gateway_name_in_qs),

View File

@ -52,7 +52,7 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
api_spec() -> api_spec() ->
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => false}). emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}).
paths() -> paths() ->
[ [
@ -157,7 +157,7 @@ params_gateway_name_in_path() ->
[ [
{name, {name,
mk( mk(
binary(), hoconsc:enum(emqx_gateway_schema:gateway_names()),
#{ #{
in => path, in => path,
desc => ?DESC(emqx_gateway_api, gateway_name_in_qs), desc => ?DESC(emqx_gateway_api, gateway_name_in_qs),

View File

@ -700,7 +700,7 @@ params_gateway_name_in_path() ->
[ [
{name, {name,
mk( mk(
binary(), hoconsc:enum(emqx_gateway_schema:gateway_names()),
#{ #{
in => path, in => path,
desc => ?DESC(emqx_gateway_api, gateway_name) desc => ?DESC(emqx_gateway_api, gateway_name)

View File

@ -609,7 +609,7 @@ params_gateway_name_in_path() ->
[ [
{name, {name,
mk( mk(
binary(), hoconsc:enum(emqx_gateway_schema:gateway_names()),
#{ #{
in => path, in => path,
desc => ?DESC(emqx_gateway_api, gateway_name_in_qs), desc => ?DESC(emqx_gateway_api, gateway_name_in_qs),

View File

@ -513,29 +513,23 @@ codestr(501) -> 'NOT_IMPLEMENTED'.
fmtstr(Fmt, Args) -> fmtstr(Fmt, Args) ->
lists:flatten(io_lib:format(Fmt, Args)). lists:flatten(io_lib:format(Fmt, Args)).
-spec with_authn(binary(), function()) -> any(). -spec with_authn(atom(), function()) -> any().
with_authn(GwName0, Fun) -> with_authn(GwName0, Fun) ->
with_gateway(GwName0, fun(GwName, _GwConf) -> with_gateway(GwName0, fun(GwName, _GwConf) ->
Authn = emqx_gateway_http:authn(GwName), Authn = emqx_gateway_http:authn(GwName),
Fun(GwName, Authn) Fun(GwName, Authn)
end). end).
-spec with_listener_authn(binary(), binary(), function()) -> any(). -spec with_listener_authn(atom(), binary(), function()) -> any().
with_listener_authn(GwName0, Id, Fun) -> with_listener_authn(GwName0, Id, Fun) ->
with_gateway(GwName0, fun(GwName, _GwConf) -> with_gateway(GwName0, fun(GwName, _GwConf) ->
Authn = emqx_gateway_http:authn(GwName, Id), Authn = emqx_gateway_http:authn(GwName, Id),
Fun(GwName, Authn) Fun(GwName, Authn)
end). end).
-spec with_gateway(binary(), function()) -> any(). -spec with_gateway(atom(), function()) -> any().
with_gateway(GwName0, Fun) -> with_gateway(GwName, Fun) ->
try try
GwName =
try
binary_to_existing_atom(GwName0)
catch
_:_ -> error(badname)
end,
case emqx_gateway:lookup(GwName) of case emqx_gateway:lookup(GwName) of
undefined -> undefined ->
return_http_error(404, "Gateway not loaded"); return_http_error(404, "Gateway not loaded");

View File

@ -48,12 +48,13 @@
ip_port/0 ip_port/0
]). ]).
-elvis([{elvis_style, dont_repeat_yourself, disable}]). -elvis([{elvis_style, dont_repeat_yourself, disable}]).
-elvis([{elvis_style, invalid_dynamic_call, disable}]).
-export([namespace/0, roots/0, fields/1, desc/1, tags/0]). -export([namespace/0, roots/0, fields/1, desc/1, tags/0]).
-export([proxy_protocol_opts/0]). -export([proxy_protocol_opts/0]).
-export([mountpoint/0, mountpoint/1, gateway_common_options/0, gateway_schema/1]). -export([mountpoint/0, mountpoint/1, gateway_common_options/0, gateway_schema/1, gateway_names/0]).
namespace() -> gateway. namespace() -> gateway.
@ -337,13 +338,21 @@ proxy_protocol_opts() ->
%% dynamic schemas %% dynamic schemas
%% FIXME: don't hardcode the gateway names %% FIXME: don't hardcode the gateway names
gateway_schema(stomp) -> emqx_stomp_schema:fields(stomp); gateway_schema(Name) ->
gateway_schema(mqttsn) -> emqx_mqttsn_schema:fields(mqttsn); case emqx_gateway_utils:find_gateway_definition(Name) of
gateway_schema(coap) -> emqx_coap_schema:fields(coap); {ok, #{config_schema_module := SchemaMod}} ->
gateway_schema(lwm2m) -> emqx_lwm2m_schema:fields(lwm2m); SchemaMod:fields(Name);
gateway_schema(exproto) -> emqx_exproto_schema:fields(exproto); {error, _} = Error ->
gateway_schema(gbt32960) -> emqx_gbt32960_schema:fields(gbt32960). throw(Error)
end.
gateway_names() ->
Definations = emqx_gateway_utils:find_gateway_definitions(),
[
Name
|| #{name := Name} = Defination <- Definations,
emqx_gateway_utils:check_gateway_edition(Defination)
].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% helpers %% helpers

View File

@ -45,8 +45,10 @@
global_chain/1, global_chain/1,
listener_chain/3, listener_chain/3,
find_gateway_definitions/0, find_gateway_definitions/0,
find_gateway_definition/1,
plus_max_connections/2, plus_max_connections/2,
random_clientid/1 random_clientid/1,
check_gateway_edition/1
]). ]).
-export([stringfy/1]). -export([stringfy/1]).
@ -538,6 +540,32 @@ find_gateway_definitions() ->
) )
). ).
-spec find_gateway_definition(atom()) -> {ok, map()} | {error, term()}.
find_gateway_definition(Name) ->
ensure_gateway_loaded(),
find_gateway_definition(Name, ignore_lib_apps(application:loaded_applications())).
-dialyzer({no_match, [find_gateway_definition/2]}).
find_gateway_definition(Name, [App | T]) ->
Attrs = find_attrs(App, gateway),
SearchFun = fun({_App, _Mod, #{name := GwName}}) ->
GwName =:= Name
end,
case lists:search(SearchFun, Attrs) of
{value, {_App, _Mod, Defination}} ->
case check_gateway_edition(Defination) of
true ->
{ok, Defination};
_ ->
{error, invalid_edition}
end;
false ->
find_gateway_definition(Name, T)
end;
find_gateway_definition(_Name, []) ->
{error, not_found}.
-dialyzer({no_match, [gateways/1]}).
gateways([]) -> gateways([]) ->
[]; [];
gateways([ gateways([
@ -550,7 +578,20 @@ gateways([
}} }}
| More | More
]) when is_atom(Name), is_atom(CbMod), is_atom(SchemaMod) -> ]) when is_atom(Name), is_atom(CbMod), is_atom(SchemaMod) ->
[Defination | gateways(More)]. case check_gateway_edition(Defination) of
true ->
[Defination | gateways(More)];
_ ->
gateways(More)
end.
-if(?EMQX_RELEASE_EDITION == ee).
check_gateway_edition(_Defination) ->
true.
-else.
check_gateway_edition(Defination) ->
ce == maps:get(edition, Defination, ce).
-endif.
find_attrs(App, Def) -> find_attrs(App, Def) ->
[ [

View File

@ -96,10 +96,8 @@ t_gateways(_) ->
ok. ok.
t_gateway(_) -> t_gateway(_) ->
{404, GwNotFoundReq1} = request(get, "/gateways/not_a_known_atom"), ?assertMatch({400, #{code := <<"BAD_REQUEST">>}}, request(get, "/gateways/not_a_known_atom")),
assert_not_found(GwNotFoundReq1), ?assertMatch({400, #{code := <<"BAD_REQUEST">>}}, request(get, "/gateways/undefined")),
{404, GwNotFoundReq2} = request(get, "/gateways/undefined"),
assert_not_found(GwNotFoundReq2),
{204, _} = request(put, "/gateways/stomp", #{}), {204, _} = request(put, "/gateways/stomp", #{}),
{200, StompGw} = request(get, "/gateways/stomp"), {200, StompGw} = request(get, "/gateways/stomp"),
assert_fields_exist( assert_fields_exist(
@ -110,7 +108,7 @@ t_gateway(_) ->
{200, #{enable := true}} = request(get, "/gateways/stomp"), {200, #{enable := true}} = request(get, "/gateways/stomp"),
{204, _} = request(put, "/gateways/stomp", #{enable => false}), {204, _} = request(put, "/gateways/stomp", #{enable => false}),
{200, #{enable := false}} = request(get, "/gateways/stomp"), {200, #{enable := false}} = request(get, "/gateways/stomp"),
{404, _} = request(put, "/gateways/undefined", #{}), ?assertMatch({400, #{code := <<"BAD_REQUEST">>}}, request(put, "/gateways/undefined", #{})),
{400, _} = request(put, "/gateways/stomp", #{bad_key => "foo"}), {400, _} = request(put, "/gateways/stomp", #{bad_key => "foo"}),
ok. ok.
@ -129,8 +127,14 @@ t_gateway_enable(_) ->
{200, #{enable := NotEnable}} = request(get, "/gateways/stomp"), {200, #{enable := NotEnable}} = request(get, "/gateways/stomp"),
{204, _} = request(put, "/gateways/stomp/enable/" ++ atom_to_list(Enable), undefined), {204, _} = request(put, "/gateways/stomp/enable/" ++ atom_to_list(Enable), undefined),
{200, #{enable := Enable}} = request(get, "/gateways/stomp"), {200, #{enable := Enable}} = request(get, "/gateways/stomp"),
{404, _} = request(put, "/gateways/undefined/enable/true", undefined), ?assertMatch(
{404, _} = request(put, "/gateways/not_a_known_atom/enable/true", undefined), {400, #{code := <<"BAD_REQUEST">>}},
request(put, "/gateways/undefined/enable/true", undefined)
),
?assertMatch(
{400, #{code := <<"BAD_REQUEST">>}},
request(put, "/gateways/not_a_known_atom/enable/true", undefined)
),
{404, _} = request(put, "/gateways/coap/enable/true", undefined), {404, _} = request(put, "/gateways/coap/enable/true", undefined),
ok. ok.

View File

@ -12,7 +12,8 @@
-gateway(#{ -gateway(#{
name => gbt32960, name => gbt32960,
callback_module => ?MODULE, callback_module => ?MODULE,
config_schema_module => emqx_gbt32960_schema config_schema_module => emqx_gbt32960_schema,
edition => ee
}). }).
%% callback_module must implement the emqx_gateway_impl behaviour %% callback_module must implement the emqx_gateway_impl behaviour

View File

@ -37,8 +37,7 @@ gateway_name.desc:
"""Gateway Name""" """Gateway Name"""
gateway_name_in_qs.desc: gateway_name_in_qs.desc:
"""Gateway Name.<br/> """Gateway Name"""
It's enum with `stomp`, `mqttsn`, `coap`, `lwm2m`, `exproto`, `gbt32960`"""
gateway_node_status.desc: gateway_node_status.desc:
"""The status of the gateway on each node in the cluster""" """The status of the gateway on each node in the cluster"""