Merge pull request #10513 from zmstone/0424-EMQX-9689-stop-providing-desc-and-label-in-schemas-api

0424 emqx 9689 stop providing desc and label in schemas api (part 1)
This commit is contained in:
Zaiming (Stone) Shi 2023-04-26 19:04:33 +02:00 committed by GitHub
commit 75294a4f73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 382 additions and 145 deletions

View File

@ -194,15 +194,12 @@ jobs:
run: | run: |
CID=$(docker run -d --rm -P $EMQX_IMAGE_TAG) CID=$(docker run -d --rm -P $EMQX_IMAGE_TAG)
HTTP_PORT=$(docker inspect --format='{{(index (index .NetworkSettings.Ports "18083/tcp") 0).HostPort}}' $CID) HTTP_PORT=$(docker inspect --format='{{(index (index .NetworkSettings.Ports "18083/tcp") 0).HostPort}}' $CID)
export EMQX_SMOKE_TEST_CHECK_HIDDEN_FIELDS='yes'
./scripts/test/emqx-smoke-test.sh localhost $HTTP_PORT ./scripts/test/emqx-smoke-test.sh localhost $HTTP_PORT
docker stop $CID docker stop $CID
- name: test two nodes cluster with proto_dist=inet_tls in docker - name: test two nodes cluster with proto_dist=inet_tls in docker
run: | run: |
./scripts/test/start-two-nodes-in-docker.sh -P $EMQX_IMAGE_TAG $EMQX_IMAGE_OLD_VERSION_TAG ./scripts/test/start-two-nodes-in-docker.sh -P $EMQX_IMAGE_TAG $EMQX_IMAGE_OLD_VERSION_TAG
HTTP_PORT=$(docker inspect --format='{{(index (index .NetworkSettings.Ports "18083/tcp") 0).HostPort}}' haproxy) HTTP_PORT=$(docker inspect --format='{{(index (index .NetworkSettings.Ports "18083/tcp") 0).HostPort}}' haproxy)
# versions before 5.0.22 have hidden fields included in the API spec
export EMQX_SMOKE_TEST_CHECK_HIDDEN_FIELDS='no'
./scripts/test/emqx-smoke-test.sh localhost $HTTP_PORT ./scripts/test/emqx-smoke-test.sh localhost $HTTP_PORT
# cleanup # cleanup
./scripts/test/start-two-nodes-in-docker.sh -c ./scripts/test/start-two-nodes-in-docker.sh -c

View File

@ -31,8 +31,9 @@
%% TODO: move to emqx_dashboard when we stop building api schema at build time %% TODO: move to emqx_dashboard when we stop building api schema at build time
-export([ -export([
hotconf_schema_json/1, hotconf_schema_json/0,
bridge_schema_json/1 bridge_schema_json/0,
hocon_schema_to_spec/2
]). ]).
%% for rpc %% for rpc
@ -149,7 +150,6 @@ dump_schema(Dir, SchemaModule) ->
lists:foreach( lists:foreach(
fun(Lang) -> fun(Lang) ->
ok = gen_config_md(Dir, SchemaModule, Lang), ok = gen_config_md(Dir, SchemaModule, Lang),
ok = gen_api_schema_json(Dir, Lang),
ok = gen_schema_json(Dir, SchemaModule, Lang) ok = gen_schema_json(Dir, SchemaModule, Lang)
end, end,
["en", "zh"] ["en", "zh"]
@ -176,41 +176,15 @@ gen_schema_json(Dir, SchemaModule, Lang) ->
IoData = emqx_utils_json:encode(JsonMap, [pretty, force_utf8]), IoData = emqx_utils_json:encode(JsonMap, [pretty, force_utf8]),
ok = file:write_file(SchemaJsonFile, IoData). ok = file:write_file(SchemaJsonFile, IoData).
%% TODO: delete this function when we stop generating this JSON at build time.
gen_api_schema_json(Dir, Lang) ->
gen_api_schema_json_hotconf(Dir, Lang),
gen_api_schema_json_bridge(Dir, Lang).
%% TODO: delete this function when we stop generating this JSON at build time.
gen_api_schema_json_hotconf(Dir, Lang) ->
File = schema_filename(Dir, "hot-config-schema-", Lang),
IoData = hotconf_schema_json(Lang),
ok = write_api_schema_json_file(File, IoData).
%% TODO: delete this function when we stop generating this JSON at build time.
gen_api_schema_json_bridge(Dir, Lang) ->
File = schema_filename(Dir, "bridge-api-", Lang),
IoData = bridge_schema_json(Lang),
ok = write_api_schema_json_file(File, IoData).
%% TODO: delete this function when we stop generating this JSON at build time.
write_api_schema_json_file(File, IoData) ->
io:format(user, "===< Generating: ~s~n", [File]),
file:write_file(File, IoData).
%% TODO: move this function to emqx_dashboard when we stop generating this JSON at build time. %% TODO: move this function to emqx_dashboard when we stop generating this JSON at build time.
hotconf_schema_json(Lang) -> hotconf_schema_json() ->
SchemaInfo = #{title => <<"EMQX Hot Conf API Schema">>, version => <<"0.1.0">>}, SchemaInfo = #{title => <<"EMQX Hot Conf API Schema">>, version => <<"0.1.0">>},
gen_api_schema_json_iodata(emqx_mgmt_api_configs, SchemaInfo, Lang). gen_api_schema_json_iodata(emqx_mgmt_api_configs, SchemaInfo).
%% TODO: move this function to emqx_dashboard when we stop generating this JSON at build time. %% TODO: move this function to emqx_dashboard when we stop generating this JSON at build time.
bridge_schema_json(Lang) -> bridge_schema_json() ->
SchemaInfo = #{title => <<"EMQX Data Bridge API Schema">>, version => <<"0.1.0">>}, SchemaInfo = #{title => <<"EMQX Data Bridge API Schema">>, version => <<"0.1.0">>},
gen_api_schema_json_iodata(emqx_bridge_api, SchemaInfo, Lang). gen_api_schema_json_iodata(emqx_bridge_api, SchemaInfo).
schema_filename(Dir, Prefix, Lang) ->
Filename = Prefix ++ Lang ++ ".json",
filename:join([Dir, Filename]).
%% TODO: remove it and also remove hocon_md.erl and friends. %% TODO: remove it and also remove hocon_md.erl and friends.
%% markdown generation from schema is a failure and we are moving to an interactive %% markdown generation from schema is a failure and we are moving to an interactive
@ -270,50 +244,11 @@ gen_example(File, SchemaModule) ->
Example = hocon_schema_example:gen(SchemaModule, Opts), Example = hocon_schema_example:gen(SchemaModule, Opts),
file:write_file(File, Example). file:write_file(File, Example).
%% TODO: move this to emqx_dashboard when we stop generating gen_api_schema_json_iodata(SchemaMod, SchemaInfo) ->
%% this JSON at build time. emqx_dashboard_swagger:gen_api_schema_json_iodata(
gen_api_schema_json_iodata(SchemaMod, SchemaInfo, Lang) ->
{ApiSpec0, Components0} = emqx_dashboard_swagger:spec(
SchemaMod, SchemaMod,
#{ SchemaInfo,
schema_converter => fun hocon_schema_to_spec/2, fun ?MODULE:hocon_schema_to_spec/2
i18n_lang => Lang
}
),
ApiSpec = lists:foldl(
fun({Path, Spec, _, _}, Acc) ->
NewSpec = maps:fold(
fun(Method, #{responses := Responses}, SubAcc) ->
case Responses of
#{
<<"200">> :=
#{
<<"content">> := #{
<<"application/json">> := #{<<"schema">> := Schema}
}
}
} ->
SubAcc#{Method => Schema};
_ ->
SubAcc
end
end,
#{},
Spec
),
Acc#{list_to_atom(Path) => NewSpec}
end,
#{},
ApiSpec0
),
Components = lists:foldl(fun(M, Acc) -> maps:merge(M, Acc) end, #{}, Components0),
emqx_utils_json:encode(
#{
info => SchemaInfo,
paths => ApiSpec,
components => #{schemas => Components}
},
[pretty, force_utf8]
). ).
-define(TO_REF(_N_, _F_), iolist_to_binary([to_bin(_N_), ".", to_bin(_F_)])). -define(TO_REF(_N_, _F_), iolist_to_binary([to_bin(_N_), ".", to_bin(_F_)])).

View File

@ -45,18 +45,11 @@ schema("/schemas/:name") ->
'operationId' => get_schema, 'operationId' => get_schema,
get => #{ get => #{
parameters => [ parameters => [
{name, hoconsc:mk(hoconsc:enum([hotconf, bridges]), #{in => path})}, {name, hoconsc:mk(hoconsc:enum([hotconf, bridges]), #{in => path})}
{lang,
hoconsc:mk(typerefl:string(), #{
in => query,
default => <<"en">>,
desc => <<"The language of the schema.">>
})}
], ],
desc => << desc => <<
"Get the schema JSON of the specified name. " "Get the schema JSON of the specified name. "
"NOTE: you should never need to make use of this API " "NOTE: only intended for EMQX Dashboard."
"unless you are building a multi-lang dashboaard."
>>, >>,
tags => ?TAGS, tags => ?TAGS,
security => [], security => [],
@ -71,14 +64,13 @@ schema("/schemas/:name") ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
get_schema(get, #{ get_schema(get, #{
bindings := #{name := Name}, bindings := #{name := Name}
query_string := #{<<"lang">> := Lang}
}) -> }) ->
{200, gen_schema(Name, iolist_to_binary(Lang))}; {200, gen_schema(Name)};
get_schema(get, _) -> get_schema(get, _) ->
{400, ?BAD_REQUEST, <<"unknown">>}. {400, ?BAD_REQUEST, <<"unknown">>}.
gen_schema(hotconf, Lang) -> gen_schema(hotconf) ->
emqx_conf:hotconf_schema_json(Lang); emqx_conf:hotconf_schema_json();
gen_schema(bridges, Lang) -> gen_schema(bridges) ->
emqx_conf:bridge_schema_json(Lang). emqx_conf:bridge_schema_json().

View File

@ -26,7 +26,11 @@
-export([error_codes/1, error_codes/2]). -export([error_codes/1, error_codes/2]).
-export([file_schema/1]). -export([file_schema/1]).
-export([filter_check_request/2, filter_check_request_and_translate_body/2]). -export([
filter_check_request/2,
filter_check_request_and_translate_body/2,
gen_api_schema_json_iodata/3
]).
-ifdef(TEST). -ifdef(TEST).
-export([ -export([
@ -72,6 +76,8 @@
]) ])
). ).
-define(SPECIAL_LANG_MSGID, <<"$msgid">>).
-define(MAX_ROW_LIMIT, 1000). -define(MAX_ROW_LIMIT, 1000).
-define(DEFAULT_ROW, 100). -define(DEFAULT_ROW, 100).
@ -192,6 +198,50 @@ file_schema(FileName) ->
} }
}. }.
gen_api_schema_json_iodata(SchemaMod, SchemaInfo, Converter) ->
{ApiSpec0, Components0} = emqx_dashboard_swagger:spec(
SchemaMod,
#{
schema_converter => Converter,
i18n_lang => ?SPECIAL_LANG_MSGID
}
),
ApiSpec = lists:foldl(
fun({Path, Spec, _, _}, Acc) ->
NewSpec = maps:fold(
fun(Method, #{responses := Responses}, SubAcc) ->
case Responses of
#{
<<"200">> :=
#{
<<"content">> := #{
<<"application/json">> := #{<<"schema">> := Schema}
}
}
} ->
SubAcc#{Method => Schema};
_ ->
SubAcc
end
end,
#{},
Spec
),
Acc#{list_to_atom(Path) => NewSpec}
end,
#{},
ApiSpec0
),
Components = lists:foldl(fun(M, Acc) -> maps:merge(M, Acc) end, #{}, Components0),
emqx_utils_json:encode(
#{
info => SchemaInfo,
paths => ApiSpec,
components => #{schemas => Components}
},
[pretty, force_utf8]
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Private functions %% Private functions
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -482,6 +532,14 @@ maybe_add_summary_from_label(Spec, Hocon, Options) ->
get_i18n(Tag, ?DESC(Namespace, Id), Default, Options) -> get_i18n(Tag, ?DESC(Namespace, Id), Default, Options) ->
Lang = get_lang(Options), Lang = get_lang(Options),
case Lang of
?SPECIAL_LANG_MSGID ->
make_msgid(Namespace, Id, Tag);
_ ->
get_i18n_text(Lang, Namespace, Id, Tag, Default)
end.
get_i18n_text(Lang, Namespace, Id, Tag, Default) ->
case emqx_dashboard_desc_cache:lookup(Lang, Namespace, Id, Tag) of case emqx_dashboard_desc_cache:lookup(Lang, Namespace, Id, Tag) of
undefined -> undefined ->
Default; Default;
@ -489,6 +547,14 @@ get_i18n(Tag, ?DESC(Namespace, Id), Default, Options) ->
Text Text
end. end.
%% Format$msgid:Namespace.Id.Tag
%% e.g. $msgid:emqx_schema.key.desc
%% $msgid:emqx_schema.key.label
%% if needed, the consumer of this schema JSON can use this msgid to
%% resolve the text in the i18n database.
make_msgid(Namespace, Id, Tag) ->
iolist_to_binary(["$msgid:", to_bin(Namespace), ".", to_bin(Id), ".", Tag]).
%% So far i18n_lang in options is only used at build time. %% So far i18n_lang in options is only used at build time.
%% At runtime, it's still the global config which controls the language. %% At runtime, it's still the global config which controls the language.
get_lang(#{i18n_lang := Lang}) -> Lang; get_lang(#{i18n_lang := Lang}) -> Lang;

View File

@ -0,0 +1,52 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_dashboard_schema_api_SUITE).
-compile(nowarn_export_all).
-compile(export_all).
-include_lib("emqx/include/http_api.hrl").
-include_lib("eunit/include/eunit.hrl").
-define(SERVER, "http://127.0.0.1:18083/api/v5").
-import(emqx_mgmt_api_test_util, [request/2]).
all() ->
emqx_common_test_helpers:all(?MODULE).
init_per_suite(Config) ->
emqx_mgmt_api_test_util:init_suite([emqx_conf]),
Config.
end_per_suite(_Config) ->
emqx_mgmt_api_test_util:end_suite([emqx_conf]).
t_hotconf(_) ->
Url = ?SERVER ++ "/schemas/hotconf",
{ok, 200, Body} = request(get, Url),
%% assert it's a valid json
_ = emqx_utils_json:decode(Body),
ok.
t_bridges(_) ->
Url = ?SERVER ++ "/schemas/bridges",
{ok, 200, Body} = request(get, Url),
%% assert it's a valid json
_ = emqx_utils_json:decode(Body),
ok.

View File

@ -45,6 +45,17 @@ schema("/status") ->
#{ #{
'operationId' => get_status, 'operationId' => get_status,
get => #{ get => #{
parameters => [
{format,
hoconsc:mk(
string(),
#{
in => query,
default => <<"text">>,
desc => ?DESC(get_status_api_format)
}
)}
],
description => ?DESC(get_status_api), description => ?DESC(get_status_api),
tags => ?TAGS, tags => ?TAGS,
security => [], security => [],
@ -70,7 +81,16 @@ path() ->
"/status". "/status".
init(Req0, State) -> init(Req0, State) ->
{Code, Headers, Body} = running_status(), Format =
try
QS = cowboy_req:parse_qs(Req0),
{_, F} = lists:keyfind(<<"format">>, 1, QS),
F
catch
_:_ ->
<<"text">>
end,
{Code, Headers, Body} = running_status(Format),
Req = cowboy_req:reply(Code, Headers, Body, Req0), Req = cowboy_req:reply(Code, Headers, Body, Req0),
{ok, Req, State}. {ok, Req, State}.
@ -78,29 +98,52 @@ init(Req0, State) ->
%% API Handler funcs %% API Handler funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
get_status(get, _Params) -> get_status(get, Params) ->
running_status(). Format = maps:get(<<"format">>, maps:get(query_string, Params, #{}), <<"text">>),
running_status(iolist_to_binary(Format)).
running_status() -> running_status(Format) ->
case emqx_dashboard_listener:is_ready(timer:seconds(20)) of case emqx_dashboard_listener:is_ready(timer:seconds(20)) of
true -> true ->
BrokerStatus = broker_status(),
AppStatus = application_status(), AppStatus = application_status(),
Body = io_lib:format("Node ~ts is ~ts~nemqx is ~ts", [node(), BrokerStatus, AppStatus]), Body = do_get_status(AppStatus, Format),
StatusCode = StatusCode =
case AppStatus of case AppStatus of
running -> 200; running -> 200;
not_running -> 503 not_running -> 503
end, end,
ContentType =
case Format of
<<"json">> -> <<"applicatin/json">>;
_ -> <<"text/plain">>
end,
Headers = #{ Headers = #{
<<"content-type">> => <<"text/plain">>, <<"content-type">> => ContentType,
<<"retry-after">> => <<"15">> <<"retry-after">> => <<"15">>
}, },
{StatusCode, Headers, list_to_binary(Body)}; {StatusCode, Headers, iolist_to_binary(Body)};
false -> false ->
{503, #{<<"retry-after">> => <<"15">>}, <<>>} {503, #{<<"retry-after">> => <<"15">>}, <<>>}
end. end.
do_get_status(AppStatus, <<"json">>) ->
BrokerStatus = broker_status(),
emqx_utils_json:encode(#{
node_name => atom_to_binary(node(), utf8),
rel_vsn => vsn(),
broker_status => atom_to_binary(BrokerStatus),
app_status => atom_to_binary(AppStatus)
});
do_get_status(AppStatus, _) ->
BrokerStatus = broker_status(),
io_lib:format("Node ~ts is ~ts~nemqx is ~ts", [node(), BrokerStatus, AppStatus]).
vsn() ->
iolist_to_binary([
emqx_release:edition_vsn_prefix(),
emqx_release:version()
]).
broker_status() -> broker_status() ->
case emqx:is_running() of case emqx:is_running() of
true -> true ->

View File

@ -38,7 +38,10 @@ all() ->
get_status_tests() -> get_status_tests() ->
[ [
t_status_ok, t_status_ok,
t_status_not_ok t_status_not_ok,
t_status_text_format,
t_status_json_format,
t_status_bad_format_qs
]. ].
groups() -> groups() ->
@ -87,8 +90,10 @@ do_request(Opts) ->
headers := Headers, headers := Headers,
body := Body0 body := Body0
} = Opts, } = Opts,
QS = maps:get(qs, Opts, ""),
URL = ?HOST ++ filename:join(Path0), URL = ?HOST ++ filename:join(Path0),
{ok, #{host := Host, port := Port, path := Path}} = emqx_http_lib:uri_parse(URL), {ok, #{host := Host, port := Port, path := Path1}} = emqx_http_lib:uri_parse(URL),
Path = Path1 ++ QS,
%% we must not use `httpc' here, because it keeps retrying when it %% we must not use `httpc' here, because it keeps retrying when it
%% receives a 503 with `retry-after' header, and there's no option %% receives a 503 with `retry-after' header, and there's no option
%% to stop that behavior... %% to stop that behavior...
@ -165,3 +170,73 @@ t_status_not_ok(Config) ->
Headers Headers
), ),
ok. ok.
t_status_text_format(Config) ->
Path = ?config(get_status_path, Config),
#{
body := Resp,
status_code := StatusCode
} = do_request(#{
method => get,
path => Path,
qs => "?format=text",
headers => [],
body => no_body
}),
?assertEqual(200, StatusCode),
?assertMatch(
{match, _},
re:run(Resp, <<"emqx is running$">>)
),
ok.
t_status_json_format(Config) ->
Path = ?config(get_status_path, Config),
#{
body := Resp,
status_code := StatusCode
} = do_request(#{
method => get,
path => Path,
qs => "?format=json",
headers => [],
body => no_body
}),
?assertEqual(200, StatusCode),
?assertMatch(
#{<<"app_status">> := <<"running">>},
emqx_utils_json:decode(Resp)
),
ok.
t_status_bad_format_qs(Config) ->
lists:foreach(
fun(QS) ->
test_status_bad_format_qs(QS, Config)
end,
[
"?a=b",
"?format=",
"?format=x"
]
).
%% when query-sting is invalid, fallback to text format
test_status_bad_format_qs(QS, Config) ->
Path = ?config(get_status_path, Config),
#{
body := Resp,
status_code := StatusCode
} = do_request(#{
method => get,
path => Path,
qs => QS,
headers => [],
body => no_body
}),
?assertEqual(200, StatusCode),
?assertMatch(
{match, _},
re:run(Resp, <<"emqx is running$">>)
),
ok.

7
build
View File

@ -92,7 +92,7 @@ log() {
} }
make_docs() { make_docs() {
local libs_dir1 libs_dir2 libs_dir3 docdir dashboard_www_static local libs_dir1 libs_dir2 libs_dir3 docdir
libs_dir1="$("$FIND" "_build/$PROFILE/lib/" -maxdepth 2 -name ebin -type d)" libs_dir1="$("$FIND" "_build/$PROFILE/lib/" -maxdepth 2 -name ebin -type d)"
if [ -d "_build/default/lib/" ]; then if [ -d "_build/default/lib/" ]; then
libs_dir2="$("$FIND" "_build/default/lib/" -maxdepth 2 -name ebin -type d)" libs_dir2="$("$FIND" "_build/default/lib/" -maxdepth 2 -name ebin -type d)"
@ -113,14 +113,11 @@ make_docs() {
;; ;;
esac esac
docdir="_build/docgen/$PROFILE" docdir="_build/docgen/$PROFILE"
dashboard_www_static='apps/emqx_dashboard/priv/www/static/' mkdir -p "$docdir"
mkdir -p "$docdir" "$dashboard_www_static"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
erl -noshell -pa $libs_dir1 $libs_dir2 $libs_dir3 -eval \ erl -noshell -pa $libs_dir1 $libs_dir2 $libs_dir3 -eval \
"ok = emqx_conf:dump_schema('$docdir', $SCHEMA_MODULE), \ "ok = emqx_conf:dump_schema('$docdir', $SCHEMA_MODULE), \
halt(0)." halt(0)."
cp "$docdir"/bridge-api-*.json "$dashboard_www_static"
cp "$docdir"/hot-config-schema-*.json "$dashboard_www_static"
} }
assert_no_compile_time_only_deps() { assert_no_compile_time_only_deps() {

View File

@ -1,21 +1,42 @@
emqx_mgmt_api_status { emqx_mgmt_api_status {
get_status_api.desc: get_status_api.desc:
"""Serves as a health check for the node. Returns a plain text response describing the status of the node. This endpoint requires no authentication. """Serves as a health check for the node.
Returns response to describe the status of the node and the application.
This endpoint requires no authentication.
Returns status code 200 if the EMQX application is up and running, 503 otherwise. Returns status code 200 if the EMQX application is up and running, 503 otherwise.
This API was introduced in v5.0.10. This API was introduced in v5.0.10.
The GET `/status` endpoint (without the `/api/...` prefix) is also an alias to this endpoint and works in the same way. This alias has been available since v5.0.0.""" The GET `/status` endpoint (without the `/api/...` prefix) is also an alias to this endpoint and works in the same way.
This alias has been available since v5.0.0.
Starting from v5.0.25 or e5.0.4, you can also use 'format' parameter to get JSON format information.
"""
get_status_api.label: get_status_api.label:
"""Service health check""" """Service health check"""
get_status_response200.desc: get_status_response200.desc:
"""Node emqx@127.0.0.1 is started """If 'format' parameter is 'json', then it returns a JSON like below:<br/>
{
"rel_vsn": "v5.0.23",
"node_name": "emqx@127.0.0.1",
"broker_status": "started",
"app_status": "running"
}
<br/>
Otherwise it returns free text strings as below:<br/>
Node emqx@127.0.0.1 is started
emqx is running""" emqx is running"""
get_status_response503.desc: get_status_response503.desc:
"""Node emqx@127.0.0.1 is stopped """When EMQX application is temporary not running or being restarted, it may return 'emqx is not_running'.
emqx is not_running""" If the 'format' parameter is provided 'json', the nthe 'app_status' field in the JSON object is 'not_running'.
"""
get_status_api_format.desc:
"""Specify the response format, 'text' (default) to return the HTTP body in free text,
or 'json' to return the HTTP body with a JSON object."""
} }

View File

@ -1,22 +1,34 @@
emqx_mgmt_api_status { emqx_mgmt_api_status {
get_status_api.desc: get_status_api.desc:
"""作为节点的健康检查。 返回一个纯文本的响应,描述节点状态。 """节点的健康检查。 返回节点状态的描述信息
如果 EMQX 应用程序已经启动并运行,返回状态代码 200否则返回 503。 如果 EMQX 应用程序已经启动并运行,返回状态代码 200否则返回 503。
这个API是在v5.0.10中引入的。 这个API是在v5.0.10中引入的。
GET `/status`端点(没有`/api/...`前缀)也是这个端点的一个别名,工作方式相同。 这个别名从v5.0.0开始就有了。""" GET `/status`端点(没有`/api/...`前缀)也是这个端点的一个别名,工作方式相同。 这个别名从v5.0.0开始就有了。
自 v5.0.25 和 e5.0.4 开始,可以通过指定 'format' 参数来得到 JSON 格式的信息。"""
get_status_api.label: get_status_api.label:
"""服务健康检查""" """服务健康检查"""
get_status_response200.desc: get_status_response200.desc:
"""Node emqx@127.0.0.1 is started """如果 'format' 参数为 'json'则返回如下JSON<br/>
{
"rel_vsn": "v5.0.23",
"node_name": "emqx@127.0.0.1",
"broker_status": "started",
"app_status": "running"
}
<br/>
否则返回2行自由格式的文本第一行描述节点的状态第二行描述 EMQX 应用运行状态。例如:<br/>
Node emqx@127.0.0.1 is started
emqx is running""" emqx is running"""
get_status_response503.desc: get_status_response503.desc:
"""Node emqx@127.0.0.1 is stopped """如果 EMQX 应用暂时没有启动,或正在重启,则可能返回 'emqx is not_running'"""
emqx is not_running"""
get_status_api_format.desc:
"""指定返回的内容格式。使用 'text'(默认)则返回自由格式的字符串; 'json' 则返回 JSON 格式。"""
} }

View File

@ -2,42 +2,89 @@
set -euo pipefail set -euo pipefail
[ $# -ne 2 ] && { echo "Usage: $0 ip port"; exit 1; } [ $# -ne 2 ] && { echo "Usage: $0 host port"; exit 1; }
IP=$1 HOST=$1
PORT=$2 PORT=$2
URL="http://$IP:$PORT/status" BASE_URL="http://$HOST:$PORT"
## Check if EMQX is responding ## Check if EMQX is responding
ATTEMPTS=10 wait_for_emqx() {
while ! curl "$URL" >/dev/null 2>&1; do local attempts=10
if [ $ATTEMPTS -eq 0 ]; then local url="$BASE_URL"/status
echo "emqx is not responding on $URL" while ! curl "$url" >/dev/null 2>&1; do
exit 1 if [ $attempts -eq 0 ]; then
echo "emqx is not responding on $url"
exit 1
fi
sleep 5
attempts=$((attempts-1))
done
}
## Get the JSON format status which is jq friendly and includes a version string
json_status() {
local url="${BASE_URL}/status?format=json"
local resp
resp="$(curl -s "$url")"
if (echo "$resp" | jq . >/dev/null 2>&1); then
echo "$resp"
else
echo 'NOT_JSON'
fi fi
sleep 5 }
ATTEMPTS=$((ATTEMPTS-1))
done
## Check if the API docs are available ## Check if the API docs are available
API_DOCS_URL="http://$IP:$PORT/api-docs/index.html" check_api_docs() {
API_DOCS_STATUS="$(curl -s -o /dev/null -w "%{http_code}" "$API_DOCS_URL")" local url="$BASE_URL/api-docs/index.html"
if [ "$API_DOCS_STATUS" != "200" ]; then local status
echo "emqx is not responding on $API_DOCS_URL" status="$(curl -s -o /dev/null -w "%{http_code}" "$url")"
exit 1 if [ "$status" != "200" ]; then
fi echo "emqx is not responding on $API_DOCS_URL"
exit 1
fi
}
## Check if the swagger.json contains hidden fields ## Check if the swagger.json contains hidden fields
## fail if it does ## fail if it does
SWAGGER_JSON_URL="http://$IP:$PORT/api-docs/swagger.json" check_swagger_json() {
## assert swagger.json is valid json local url="$BASE_URL/api-docs/swagger.json"
JSON="$(curl -s "$SWAGGER_JSON_URL")" ## assert swagger.json is valid json
echo "$JSON" | jq . >/dev/null JSON="$(curl -s "$url")"
echo "$JSON" | jq . >/dev/null
if [ "${EMQX_SMOKE_TEST_CHECK_HIDDEN_FIELDS:-yes}" = 'yes' ]; then
## assert swagger.json does not contain trie_compaction (which is a hidden field) ## assert swagger.json does not contain trie_compaction (which is a hidden field)
if echo "$JSON" | grep -q trie_compaction; then if echo "$JSON" | grep -q trie_compaction; then
echo "swagger.json contains hidden fields" echo "swagger.json contains hidden fields"
exit 1 exit 1
fi fi
fi }
check_schema_json() {
local name="$1"
local expected_title="$2"
local url="$BASE_URL/api/v5/schemas/$name"
local json
json="$(curl -s "$url" | jq .)"
title="$(echo "$json" | jq -r '.info.title')"
if [[ "$title" != "$expected_title" ]]; then
echo "unexpected value from GET $url"
echo "expected: $expected_title"
echo "got : $title"
exit 1
fi
}
main() {
wait_for_emqx
local JSON_STATUS
JSON_STATUS="$(json_status)"
check_api_docs
## The json status feature was added after hotconf and bridges schema API
if [ "$JSON_STATUS" != 'NOT_JSON' ]; then
check_swagger_json
check_schema_json hotconf "EMQX Hot Conf API Schema"
check_schema_json bridges "EMQX Data Bridge API Schema"
fi
}
main