diff --git a/apps/emqx_gateway/i18n/emqx_gateway_api_i18n.conf b/apps/emqx_gateway/i18n/emqx_gateway_api_i18n.conf index 34e9b8567..197e6a5ed 100644 --- a/apps/emqx_gateway/i18n/emqx_gateway_api_i18n.conf +++ b/apps/emqx_gateway/i18n/emqx_gateway_api_i18n.conf @@ -57,6 +57,14 @@ It's enum with `stomp`, `mqttsn`, `coap`, `lwm2m`, `exproto` } } + gateway_enable_in_path { + desc { + en: """Whether or not gateway is enabled""" + + zh: """是否开启此网关""" + } + } + gateway_status { desc { en: """Gateway status""" diff --git a/apps/emqx_gateway/src/emqx_gateway.app.src b/apps/emqx_gateway/src/emqx_gateway.app.src index 491d0242a..24cb76630 100644 --- a/apps/emqx_gateway/src/emqx_gateway.app.src +++ b/apps/emqx_gateway/src/emqx_gateway.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_gateway, [ {description, "The Gateway management application"}, - {vsn, "0.1.7"}, + {vsn, "0.1.8"}, {registered, []}, {mod, {emqx_gateway_app, []}}, {applications, [kernel, stdlib, grpc, emqx, emqx_authn]}, diff --git a/apps/emqx_gateway/src/emqx_gateway_api.erl b/apps/emqx_gateway/src/emqx_gateway_api.erl index 6a9744d75..e06748034 100644 --- a/apps/emqx_gateway/src/emqx_gateway_api.erl +++ b/apps/emqx_gateway/src/emqx_gateway_api.erl @@ -19,8 +19,6 @@ -include("emqx_gateway_http.hrl"). -include_lib("typerefl/include/types.hrl"). -include_lib("hocon/include/hoconsc.hrl"). --include_lib("emqx/include/emqx_placeholder.hrl"). --include_lib("emqx/include/emqx_authentication.hrl"). -behaviour(minirest_api). @@ -49,8 +47,9 @@ %% http handlers -export([ + gateways/2, gateway/2, - gateway_insta/2 + gateway_enable/2 ]). -define(KNOWN_GATEWAY_STATUSES, [<<"running">>, <<"stopped">>, <<"unloaded">>]). @@ -66,13 +65,14 @@ api_spec() -> paths() -> emqx_gateway_utils:make_deprecated_paths([ "/gateways", - "/gateways/:name" + "/gateways/:name", + "/gateways/:name/enable/:enable" ]). %%-------------------------------------------------------------------- %% http handlers -gateway(get, Request) -> +gateways(get, Request) -> Params = maps:get(query_string, Request, #{}), Status = maps:get(<<"status">>, Params, <<"all">>), case lists:member(Status, [<<"all">> | ?KNOWN_GATEWAY_STATUSES]) of @@ -89,84 +89,85 @@ gateway(get, Request) -> lists:join(", ", ?KNOWN_GATEWAY_STATUSES) ] ) - end; -gateway(post, Request) -> - Body = maps:get(body, Request, #{}), - try - Name0 = maps:get(<<"name">>, Body), - GwName = binary_to_existing_atom(Name0), - case emqx_gateway_registry:lookup(GwName) of - undefined -> - error(badarg); - _ -> - GwConf = maps:without([<<"name">>], Body), - case emqx_gateway_conf:load_gateway(GwName, GwConf) of - {ok, NGwConf} -> - {201, NGwConf}; - {error, Reason} -> - emqx_gateway_http:reason2resp(Reason) - end - end - catch - error:{badkey, K} -> - return_http_error(400, [K, " is required"]); - error:{badconf, _} = Reason1 -> - emqx_gateway_http:reason2resp(Reason1); - error:badarg -> - return_http_error(404, "Bad gateway name") end. -gateway_insta(delete, #{bindings := #{name := Name0}}) -> - with_gateway(Name0, fun(GwName, _) -> - case emqx_gateway_conf:unload_gateway(GwName) of - ok -> +gateway(get, #{bindings := #{name := Name}}) -> + try + GwName = gw_name(Name), + case emqx_gateway:lookup(GwName) of + undefined -> + {200, #{name => GwName, status => unloaded}}; + Gateway -> + GwConf = emqx_gateway_conf:gateway(Name), + GwInfo0 = emqx_gateway_utils:unix_ts_to_rfc3339( + [created_at, started_at, stopped_at], + Gateway + ), + GwInfo1 = maps:with( + [ + name, + status, + created_at, + started_at, + stopped_at + ], + GwInfo0 + ), + {200, maps:merge(GwConf, GwInfo1)} + end + catch + throw:not_found -> + return_http_error(404, <<"NOT FOUND">>) + end; +gateway(put, #{ + body := GwConf0, + bindings := #{name := Name} +}) -> + GwConf = maps:without([<<"name">>], GwConf0), + try + GwName = gw_name(Name), + LoadOrUpdateF = + case emqx_gateway:lookup(GwName) of + undefined -> + fun emqx_gateway_conf:load_gateway/2; + _ -> + fun emqx_gateway_conf:update_gateway/2 + end, + case LoadOrUpdateF(GwName, GwConf) of + {ok, _} -> {204}; {error, Reason} -> emqx_gateway_http:reason2resp(Reason) end - end); -gateway_insta(get, #{bindings := #{name := Name0}}) -> - try binary_to_existing_atom(Name0) of - GwName -> - case emqx_gateway:lookup(GwName) of - undefined -> - {200, #{name => GwName, status => unloaded}}; - Gateway -> - GwConf = emqx_gateway_conf:gateway(Name0), - GwInfo0 = emqx_gateway_utils:unix_ts_to_rfc3339( - [created_at, started_at, stopped_at], - Gateway - ), - GwInfo1 = maps:with( - [ - name, - status, - created_at, - started_at, - stopped_at - ], - GwInfo0 - ), - {200, maps:merge(GwConf, GwInfo1)} - end catch - error:badarg -> - return_http_error(404, "Bad gateway name") - end; -gateway_insta(put, #{ - body := GwConf0, - bindings := #{name := Name0} -}) -> - with_gateway(Name0, fun(GwName, _) -> - %% XXX: Clear the unused fields - GwConf = maps:without([<<"name">>], GwConf0), - case emqx_gateway_conf:update_gateway(GwName, GwConf) of - {ok, Gateway} -> - {200, Gateway}; - {error, Reason} -> - emqx_gateway_http:reason2resp(Reason) + error:{badconf, _} = Reason1 -> + emqx_gateway_http:reason2resp(Reason1); + throw:not_found -> + return_http_error(404, <<"NOT FOUND">>) + end. + +gateway_enable(put, #{bindings := #{name := Name, enable := Enable}}) -> + try + GwName = gw_name(Name), + case emqx_gateway:lookup(GwName) of + undefined -> + return_http_error(404, <<"NOT FOUND">>); + _Gateway -> + {ok, _} = emqx_gateway_conf:update_gateway(GwName, #{<<"enable">> => Enable}), + {204} end - end). + catch + throw:not_found -> + return_http_error(404, <<"NOT FOUND">>) + end. + +-spec gw_name(binary()) -> stomp | coap | lwm2m | mqttsn | exproto | no_return(). +gw_name(<<"stomp">>) -> stomp; +gw_name(<<"coap">>) -> coap; +gw_name(<<"lwm2m">>) -> lwm2m; +gw_name(<<"mqttsn">>) -> mqttsn; +gw_name(<<"exproto">>) -> exproto; +gw_name(_Else) -> throw(not_found). %%-------------------------------------------------------------------- %% Swagger defines @@ -174,7 +175,7 @@ gateway_insta(put, #{ schema("/gateways") -> #{ - 'operationId' => gateway, + 'operationId' => gateways, get => #{ tags => ?TAGS, @@ -182,29 +183,20 @@ schema("/gateways") -> summary => <<"List All Gateways">>, parameters => params_gateway_status_in_qs(), responses => - ?STANDARD_RESP( - #{ - 200 => emqx_dashboard_swagger:schema_with_example( - hoconsc:array(ref(gateway_overview)), - examples_gateway_overview() - ) - } - ) - }, - post => - #{ - tags => ?TAGS, - desc => ?DESC(enable_gateway), - summary => <<"Enable a Gateway">>, - %% TODO: distinguish create & response swagger schema - 'requestBody' => schema_gateways_conf(), - responses => - ?STANDARD_RESP(#{201 => schema_gateways_conf()}) + #{ + 200 => emqx_dashboard_swagger:schema_with_example( + hoconsc:array(ref(gateway_overview)), + examples_gateway_overview() + ), + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad request">> + ) + } } }; schema("/gateways/:name") -> #{ - 'operationId' => gateway_insta, + 'operationId' => gateway, get => #{ tags => ?TAGS, @@ -212,26 +204,41 @@ schema("/gateways/:name") -> summary => <<"Get the Gateway">>, parameters => params_gateway_name_in_path(), responses => - ?STANDARD_RESP(#{200 => schema_gateways_conf()}) - }, - delete => - #{ - tags => ?TAGS, - desc => ?DESC(delete_gateway), - summary => <<"Unload the gateway">>, - parameters => params_gateway_name_in_path(), - responses => - ?STANDARD_RESP(#{204 => <<"Deleted">>}) + #{ + 200 => schema_gateways_conf(), + 404 => emqx_dashboard_swagger:error_codes( + [?NOT_FOUND, ?RESOURCE_NOT_FOUND], <<"Not Found">> + ) + } }, put => #{ tags => ?TAGS, desc => ?DESC(update_gateway), - summary => <<"Update the gateway confs">>, + % [FIXME] add proper desc + summary => <<"Load or update the gateway confs">>, parameters => params_gateway_name_in_path(), - 'requestBody' => schema_update_gateways_conf(), + 'requestBody' => schema_load_or_update_gateways_conf(), responses => - ?STANDARD_RESP(#{200 => schema_gateways_conf()}) + ?STANDARD_RESP(#{204 => <<"Gateway configuration updated">>}) + } + }; +schema("/gateways/:name/enable/:enable") -> + #{ + 'operationId' => gateway_enable, + put => + #{ + tags => ?TAGS, + desc => ?DESC(update_gateway), + summary => <<"Enable or disable gateway">>, + parameters => params_gateway_name_in_path() ++ params_gateway_enable_in_path(), + responses => + #{ + 204 => <<"Gateway configuration updated">>, + 404 => emqx_dashboard_swagger:error_codes( + [?NOT_FOUND, ?RESOURCE_NOT_FOUND], <<"Not Found">> + ) + } } }; schema(Path) -> @@ -268,6 +275,18 @@ params_gateway_status_in_qs() -> )} ]. +params_gateway_enable_in_path() -> + [ + {enable, + mk( + boolean(), + #{ + in => path, + desc => ?DESC(gateway_enable_in_path), + example => true + } + )} + ]. %%-------------------------------------------------------------------- %% schemas @@ -377,8 +396,6 @@ fields(Gw) when -> [{name, mk(Gw, #{desc => ?DESC(gateway_name)})}] ++ convert_listener_struct(emqx_gateway_schema:fields(Gw)); -fields(update_disable_enable_only) -> - [{enable, mk(boolean(), #{desc => <<"Enable/Disable the gateway">>})}]; fields(Gw) when Gw == update_stomp; Gw == update_mqttsn; @@ -431,15 +448,19 @@ fields(Listener) when fields(gateway_stats) -> [{key, mk(binary(), #{})}]. -schema_update_gateways_conf() -> +schema_load_or_update_gateways_conf() -> emqx_dashboard_swagger:schema_with_examples( hoconsc:union([ + ref(?MODULE, stomp), + ref(?MODULE, mqttsn), + ref(?MODULE, coap), + ref(?MODULE, lwm2m), + ref(?MODULE, exproto), ref(?MODULE, update_stomp), ref(?MODULE, update_mqttsn), ref(?MODULE, update_coap), ref(?MODULE, update_lwm2m), - ref(?MODULE, update_exproto), - ref(?MODULE, update_disable_enable_only) + ref(?MODULE, update_exproto) ]), examples_update_gateway_confs() ). diff --git a/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl index c4a6758a3..7d58d14b6 100644 --- a/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl @@ -23,7 +23,7 @@ emqx_gateway_test_utils, [ assert_confs/2, - assert_feilds_apperence/2, + assert_fields_exist/2, request/2, request/3, ssl_server_opts/0, @@ -32,6 +32,7 @@ ). -include_lib("eunit/include/eunit.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). %% this parses to #{}, will not cause config cleanup %% so we will need call emqx_config:erase @@ -55,32 +56,68 @@ end_per_suite(Conf) -> emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_authn, emqx_conf]), Conf. +init_per_testcase(t_gateway_fail, Config) -> + meck:expect( + emqx_gateway_conf, + update_gateway, + fun + (stomp, V) -> {error, {badconf, #{key => gw, value => V, reason => test_error}}}; + (coap, V) -> error({badconf, #{key => gw, value => V, reason => test_crash}}) + end + ), + Config; +init_per_testcase(_, Config) -> + Config. + +end_per_testcase(TestCase, Config) -> + case TestCase of + t_gateway_fail -> meck:unload(emqx_gateway_conf); + _ -> ok + end, + [emqx_gateway_conf:unload_gateway(GwName) || GwName <- [stomp, mqttsn, coap, lwm2m, exproto]], + Config. + %%-------------------------------------------------------------------- %% Cases %%-------------------------------------------------------------------- -t_gateway(_) -> +t_gateways(_) -> {200, Gateways} = request(get, "/gateways"), lists:foreach(fun assert_gw_unloaded/1, Gateways), {200, UnloadedGateways} = request(get, "/gateways?status=unloaded"), lists:foreach(fun assert_gw_unloaded/1, UnloadedGateways), {200, NoRunningGateways} = request(get, "/gateways?status=running"), ?assertEqual([], NoRunningGateways), - {404, GwNotFoundReq} = request(get, "/gateways/unknown_gateway"), - assert_not_found(GwNotFoundReq), {400, BadReqInvalidStatus} = request(get, "/gateways?status=invalid_status"), assert_bad_request(BadReqInvalidStatus), {400, BadReqUCStatus} = request(get, "/gateways?status=UNLOADED"), assert_bad_request(BadReqUCStatus), - {201, _} = request(post, "/gateways", #{name => <<"stomp">>}), - {200, StompGw1} = request(get, "/gateways/stomp"), - assert_feilds_apperence( + ok. + +t_gateway(_) -> + {404, GwNotFoundReq1} = request(get, "/gateways/not_a_known_atom"), + assert_not_found(GwNotFoundReq1), + {404, GwNotFoundReq2} = request(get, "/gateways/undefined"), + assert_not_found(GwNotFoundReq2), + {204, _} = request(put, "/gateways/stomp", #{}), + {200, StompGw} = request(get, "/gateways/stomp"), + assert_fields_exist( [name, status, enable, created_at, started_at], - StompGw1 + StompGw ), - {204, _} = request(delete, "/gateways/stomp"), - {200, StompGw2} = request(get, "/gateways/stomp"), - assert_gw_unloaded(StompGw2), + {204, _} = request(put, "/gateways/stomp", #{enable => true}), + {200, #{enable := true}} = request(get, "/gateway/stomp"), + {204, _} = request(put, "/gateways/stomp", #{enable => false}), + {200, #{enable := false}} = request(get, "/gateway/stomp"), + {404, _} = request(put, "/gateways/undefined", #{}), + {400, _} = request(put, "/gateways/stomp", #{bad_key => "foo"}), + ok. + +t_gateway_fail(_) -> + {204, _} = request(put, "/gateways/stomp", #{}), + {400, _} = request(put, "/gateways/stomp", #{}), + {204, _} = request(put, "/gateways/coap", #{}), + {400, _} = request(put, "/gateways/coap", #{}), ok. t_deprecated_gateway(_) -> @@ -88,21 +125,30 @@ t_deprecated_gateway(_) -> lists:foreach(fun assert_gw_unloaded/1, Gateways), {404, NotFoundReq} = request(get, "/gateway/uname_gateway"), assert_not_found(NotFoundReq), - {201, _} = request(post, "/gateway", #{name => <<"stomp">>}), - {200, StompGw1} = request(get, "/gateway/stomp"), - assert_feilds_apperence( + {204, _} = request(put, "/gateway/stomp", #{}), + {200, StompGw} = request(get, "/gateway/stomp"), + assert_fields_exist( [name, status, enable, created_at, started_at], - StompGw1 + StompGw ), - {204, _} = request(delete, "/gateway/stomp"), - {200, StompGw2} = request(get, "/gateway/stomp"), - assert_gw_unloaded(StompGw2), + ok. + +t_gateway_enable(_) -> + {204, _} = request(put, "/gateways/stomp", #{}), + {200, #{enable := Enable}} = request(get, "/gateway/stomp"), + NotEnable = not Enable, + {204, _} = request(put, "/gateways/stomp/enable/" ++ atom_to_list(NotEnable), undefined), + {200, #{enable := NotEnable}} = request(get, "/gateway/stomp"), + {204, _} = request(put, "/gateways/stomp/enable/" ++ atom_to_list(Enable), undefined), + {200, #{enable := Enable}} = request(get, "/gateway/stomp"), + {404, _} = request(put, "/gateways/undefined/enable/true", undefined), + {404, _} = request(put, "/gateways/not_a_known_atom/enable/true", undefined), + {404, _} = request(put, "/gateways/coap/enable/true", undefined), ok. t_gateway_stomp(_) -> {200, Gw} = request(get, "/gateways/stomp"), assert_gw_unloaded(Gw), - %% post GwConf = #{ name => <<"stomp">>, frame => #{ @@ -114,20 +160,18 @@ t_gateway_stomp(_) -> #{name => <<"def">>, type => <<"tcp">>, bind => <<"61613">>} ] }, - {201, _} = request(post, "/gateways", GwConf), + {204, _} = request(put, "/gateways/stomp", GwConf), {200, ConfResp} = request(get, "/gateways/stomp"), assert_confs(GwConf, ConfResp), - %% put GwConf2 = emqx_map_lib:deep_merge(GwConf, #{frame => #{max_headers => 10}}), - {200, _} = request(put, "/gateways/stomp", maps:without([name, listeners], GwConf2)), + {204, _} = request(put, "/gateways/stomp", maps:without([name, listeners], GwConf2)), {200, ConfResp2} = request(get, "/gateways/stomp"), assert_confs(GwConf2, ConfResp2), - {204, _} = request(delete, "/gateways/stomp"). + ok. t_gateway_mqttsn(_) -> {200, Gw} = request(get, "/gateways/mqttsn"), assert_gw_unloaded(Gw), - %% post GwConf = #{ name => <<"mqttsn">>, gateway_id => 1, @@ -138,20 +182,18 @@ t_gateway_mqttsn(_) -> #{name => <<"def">>, type => <<"udp">>, bind => <<"1884">>} ] }, - {201, _} = request(post, "/gateways", GwConf), + {204, _} = request(put, "/gateways/mqttsn", GwConf), {200, ConfResp} = request(get, "/gateways/mqttsn"), assert_confs(GwConf, ConfResp), - %% put GwConf2 = emqx_map_lib:deep_merge(GwConf, #{predefined => []}), - {200, _} = request(put, "/gateways/mqttsn", maps:without([name, listeners], GwConf2)), + {204, _} = request(put, "/gateways/mqttsn", maps:without([name, listeners], GwConf2)), {200, ConfResp2} = request(get, "/gateways/mqttsn"), assert_confs(GwConf2, ConfResp2), - {204, _} = request(delete, "/gateways/mqttsn"). + ok. t_gateway_coap(_) -> {200, Gw} = request(get, "/gateways/coap"), assert_gw_unloaded(Gw), - %% post GwConf = #{ name => <<"coap">>, heartbeat => <<"60s">>, @@ -160,20 +202,18 @@ t_gateway_coap(_) -> #{name => <<"def">>, type => <<"udp">>, bind => <<"5683">>} ] }, - {201, _} = request(post, "/gateways", GwConf), + {204, _} = request(put, "/gateways/coap", GwConf), {200, ConfResp} = request(get, "/gateways/coap"), assert_confs(GwConf, ConfResp), - %% put GwConf2 = emqx_map_lib:deep_merge(GwConf, #{heartbeat => <<"10s">>}), - {200, _} = request(put, "/gateways/coap", maps:without([name, listeners], GwConf2)), + {204, _} = request(put, "/gateways/coap", maps:without([name, listeners], GwConf2)), {200, ConfResp2} = request(get, "/gateways/coap"), assert_confs(GwConf2, ConfResp2), - {204, _} = request(delete, "/gateways/coap"). + ok. t_gateway_lwm2m(_) -> {200, Gw} = request(get, "/gateways/lwm2m"), assert_gw_unloaded(Gw), - %% post GwConf = #{ name => <<"lwm2m">>, xml_dir => <<"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml">>, @@ -192,20 +232,18 @@ t_gateway_lwm2m(_) -> #{name => <<"def">>, type => <<"udp">>, bind => <<"5783">>} ] }, - {201, _} = request(post, "/gateways", GwConf), + {204, _} = request(put, "/gateways/lwm2m", GwConf), {200, ConfResp} = request(get, "/gateways/lwm2m"), assert_confs(GwConf, ConfResp), - %% put GwConf2 = emqx_map_lib:deep_merge(GwConf, #{qmode_time_window => <<"10s">>}), - {200, _} = request(put, "/gateways/lwm2m", maps:without([name, listeners], GwConf2)), + {204, _} = request(put, "/gateways/lwm2m", maps:without([name, listeners], GwConf2)), {200, ConfResp2} = request(get, "/gateways/lwm2m"), assert_confs(GwConf2, ConfResp2), - {204, _} = request(delete, "/gateways/lwm2m"). + ok. t_gateway_exproto(_) -> {200, Gw} = request(get, "/gateways/exproto"), assert_gw_unloaded(Gw), - %% post GwConf = #{ name => <<"exproto">>, server => #{bind => <<"9100">>}, @@ -214,15 +252,14 @@ t_gateway_exproto(_) -> #{name => <<"def">>, type => <<"tcp">>, bind => <<"7993">>} ] }, - {201, _} = request(post, "/gateways", GwConf), + {204, _} = request(put, "/gateways/exproto", GwConf), {200, ConfResp} = request(get, "/gateways/exproto"), assert_confs(GwConf, ConfResp), - %% put GwConf2 = emqx_map_lib:deep_merge(GwConf, #{server => #{bind => <<"9200">>}}), - {200, _} = request(put, "/gateways/exproto", maps:without([name, listeners], GwConf2)), + {204, _} = request(put, "/gateways/exproto", maps:without([name, listeners], GwConf2)), {200, ConfResp2} = request(get, "/gateways/exproto"), assert_confs(GwConf2, ConfResp2), - {204, _} = request(delete, "/gateways/exproto"). + ok. t_gateway_exproto_with_ssl(_) -> {200, Gw} = request(get, "/gateways/exproto"), @@ -230,7 +267,6 @@ t_gateway_exproto_with_ssl(_) -> SslSvrOpts = ssl_server_opts(), SslCliOpts = ssl_client_opts(), - %% post GwConf = #{ name => <<"exproto">>, server => #{ @@ -245,27 +281,22 @@ t_gateway_exproto_with_ssl(_) -> #{name => <<"def">>, type => <<"tcp">>, bind => <<"7993">>} ] }, - {201, _} = request(post, "/gateways", GwConf), + {204, _} = request(put, "/gateways/exproto", GwConf), {200, ConfResp} = request(get, "/gateways/exproto"), assert_confs(GwConf, ConfResp), - %% put GwConf2 = emqx_map_lib:deep_merge(GwConf, #{ server => #{ bind => <<"9200">>, ssl_options => SslCliOpts } }), - {200, _} = request(put, "/gateways/exproto", maps:without([name, listeners], GwConf2)), + {204, _} = request(put, "/gateways/exproto", maps:without([name, listeners], GwConf2)), {200, ConfResp2} = request(get, "/gateways/exproto"), assert_confs(GwConf2, ConfResp2), - {204, _} = request(delete, "/gateways/exproto"). + ok. t_authn(_) -> - GwConf = #{name => <<"stomp">>}, - {201, _} = request(post, "/gateways", GwConf), - ct:sleep(500), - {204, _} = request(get, "/gateways/stomp/authentication"), - + init_gw("stomp"), AuthConf = #{ mechanism => <<"password_based">>, backend => <<"built_in_database">>, @@ -283,22 +314,18 @@ t_authn(_) -> {204, _} = request(delete, "/gateways/stomp/authentication"), {204, _} = request(get, "/gateways/stomp/authentication"), - {204, _} = request(delete, "/gateways/stomp"). + ok. t_authn_data_mgmt(_) -> - GwConf = #{name => <<"stomp">>}, - {201, _} = request(post, "/gateways", GwConf), - ct:sleep(500), - {204, _} = request(get, "/gateways/stomp/authentication"), - + init_gw("stomp"), AuthConf = #{ mechanism => <<"password_based">>, backend => <<"built_in_database">>, user_id_type => <<"clientid">> }, {201, _} = request(post, "/gateways/stomp/authentication", AuthConf), - ct:sleep(500), - {200, ConfResp} = request(get, "/gateways/stomp/authentication"), + {200, ConfResp} = + ?retry(10, 10, {200, _} = request(get, "/gateways/stomp/authentication")), assert_confs(AuthConf, ConfResp), User1 = #{ @@ -358,11 +385,10 @@ t_authn_data_mgmt(_) -> {204, _} = request(delete, "/gateways/stomp/authentication"), {204, _} = request(get, "/gateways/stomp/authentication"), - {204, _} = request(delete, "/gateways/stomp"). + ok. t_listeners_tcp(_) -> - GwConf = #{name => <<"stomp">>}, - {201, _} = request(post, "/gateways", GwConf), + {204, _} = request(put, "/gateways/stomp", #{}), {404, _} = request(get, "/gateways/stomp/listeners"), LisConf = #{ name => <<"def">>, @@ -387,7 +413,7 @@ t_listeners_tcp(_) -> {204, _} = request(delete, "/gateways/stomp/listeners/stomp:tcp:def"), {404, _} = request(get, "/gateways/stomp/listeners/stomp:tcp:def"), - {204, _} = request(delete, "/gateways/stomp"). + ok. t_listeners_authn(_) -> GwConf = #{ @@ -400,9 +426,7 @@ t_listeners_authn(_) -> } ] }, - {201, _} = request(post, "/gateways", GwConf), - ct:sleep(500), - {200, ConfResp} = request(get, "/gateways/stomp"), + ConfResp = init_gw("stomp", GwConf), assert_confs(GwConf, ConfResp), AuthConf = #{ @@ -424,7 +448,7 @@ t_listeners_authn(_) -> {204, _} = request(delete, Path), %% FIXME: 204? {204, _} = request(get, Path), - {204, _} = request(delete, "/gateways/stomp"). + ok. t_listeners_authn_data_mgmt(_) -> GwConf = #{ @@ -437,7 +461,7 @@ t_listeners_authn_data_mgmt(_) -> } ] }, - {201, _} = request(post, "/gateways", GwConf), + {204, _} = request(put, "/gateways/stomp", GwConf), {200, ConfResp} = request(get, "/gateways/stomp"), assert_confs(GwConf, ConfResp), @@ -514,13 +538,10 @@ t_listeners_authn_data_mgmt(_) -> {filename, "user-credentials.csv", CSVData} ]), - {204, _} = request(delete, "/gateways/stomp"). + ok. t_authn_fuzzy_search(_) -> - GwConf = #{name => <<"stomp">>}, - {201, _} = request(post, "/gateways", GwConf), - {204, _} = request(get, "/gateways/stomp/authentication"), - + init_gw("stomp"), AuthConf = #{ mechanism => <<"password_based">>, backend => <<"built_in_database">>, @@ -561,7 +582,25 @@ t_authn_fuzzy_search(_) -> {204, _} = request(delete, "/gateways/stomp/authentication"), {204, _} = request(get, "/gateways/stomp/authentication"), - {204, _} = request(delete, "/gateways/stomp"). + ok. + +%%-------------------------------------------------------------------- +%% Helpers + +init_gw(GwName) -> + init_gw(GwName, #{}). + +init_gw(GwName, GwConf) -> + {204, _} = request(put, "/gateways/" ++ GwName, GwConf), + ?retry( + 10, + 10, + begin + {200, #{status := Status} = RespConf} = request(get, "/gateways/" ++ GwName), + false = (Status == <<"unloaded">>), + RespConf + end + ). %%-------------------------------------------------------------------- %% Asserts diff --git a/apps/emqx_gateway/test/emqx_gateway_test_utils.erl b/apps/emqx_gateway/test/emqx_gateway_test_utils.erl index 0fed97517..4682aa02e 100644 --- a/apps/emqx_gateway/test/emqx_gateway_test_utils.erl +++ b/apps/emqx_gateway/test/emqx_gateway_test_utils.erl @@ -94,7 +94,7 @@ maybe_unconvert_listeners(Conf) when is_map(Conf) -> maybe_unconvert_listeners(Conf) -> Conf. -assert_feilds_apperence(Ks, Map) -> +assert_fields_exist(Ks, Map) -> lists:foreach( fun(K) -> _ = maps:get(K, Map) diff --git a/apps/emqx_gateway/test/emqx_stomp_SUITE.erl b/apps/emqx_gateway/test/emqx_stomp_SUITE.erl index 7299159ab..ca0dfe3c1 100644 --- a/apps/emqx_gateway/test/emqx_stomp_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_stomp_SUITE.erl @@ -25,7 +25,7 @@ -import( emqx_gateway_test_utils, [ - assert_feilds_apperence/2, + assert_fields_exist/2, request/2, request/3 ] @@ -730,7 +730,7 @@ t_rest_clienit_info(_) -> binary_to_list(ClientId), {200, StompClient1} = request(get, ClientPath), ?assertEqual(StompClient, StompClient1), - assert_feilds_apperence( + assert_fields_exist( [ proto_name, awaiting_rel_max, @@ -787,7 +787,7 @@ t_rest_clienit_info(_) -> {200, Subs} = request(get, ClientPath ++ "/subscriptions"), ?assertEqual(1, length(Subs)), - assert_feilds_apperence([topic, qos], lists:nth(1, Subs)), + assert_fields_exist([topic, qos], lists:nth(1, Subs)), {201, _} = request( post, diff --git a/changes/v5.0.11-en.md b/changes/v5.0.11-en.md index 55226faf1..15188d97e 100644 --- a/changes/v5.0.11-en.md +++ b/changes/v5.0.11-en.md @@ -10,6 +10,11 @@ - Enhance the `banned` feature [#9367](https://github.com/emqx/emqx/pull/9367). Now the corresponding session will be kicked when client is banned by `clientid`. +- Redesign `/gateways` API [9364](https://github.com/emqx/emqx/pull/9364). + Use `PUT /gateways/{name}` instead of `POST /gateways`, gateway gets 'loaded' + automatically if needed. Use `PUT /gateways/{name}/enable/{true|false}` to + enable or disable gateway. No more `DELETE /gateways/{name}`. + ## Bug fixes - Return 404 for status of unknown authenticator in `/authenticator/{id}/status` [#9328](https://github.com/emqx/emqx/pull/9328). diff --git a/changes/v5.0.11-zh.md b/changes/v5.0.11-zh.md index a84b2b25c..4c74a7081 100644 --- a/changes/v5.0.11-zh.md +++ b/changes/v5.0.11-zh.md @@ -10,6 +10,9 @@ - 增加 `封禁` 功能 [#9367](https://github.com/emqx/emqx/pull/9367)。 现在客户端通过 `clientid` 被封禁时将会踢掉对应的会话。 +- 重新设计了 /gateways API [9364](https://github.com/emqx/emqx/pull/9364)。 + 使用 PUT /gateways/{name} 代替了 POST /gateways,现在网关将在需要时自动加载,然后删除了 DELETE /gateways/{name},之后可以使用 PUT /gateways/{name}/enable/{true|false} 来开启或禁用网关。 + ## 修复 - 通过 `/authenticator/{id}/status` 请求未知认证器的状态时,将会返回 404。