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:
commit
75294a4f73
|
@ -194,15 +194,12 @@ jobs:
|
|||
run: |
|
||||
CID=$(docker run -d --rm -P $EMQX_IMAGE_TAG)
|
||||
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
|
||||
docker stop $CID
|
||||
- name: test two nodes cluster with proto_dist=inet_tls in docker
|
||||
run: |
|
||||
./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)
|
||||
# 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
|
||||
# cleanup
|
||||
./scripts/test/start-two-nodes-in-docker.sh -c
|
||||
|
|
|
@ -31,8 +31,9 @@
|
|||
|
||||
%% TODO: move to emqx_dashboard when we stop building api schema at build time
|
||||
-export([
|
||||
hotconf_schema_json/1,
|
||||
bridge_schema_json/1
|
||||
hotconf_schema_json/0,
|
||||
bridge_schema_json/0,
|
||||
hocon_schema_to_spec/2
|
||||
]).
|
||||
|
||||
%% for rpc
|
||||
|
@ -149,7 +150,6 @@ dump_schema(Dir, SchemaModule) ->
|
|||
lists:foreach(
|
||||
fun(Lang) ->
|
||||
ok = gen_config_md(Dir, SchemaModule, Lang),
|
||||
ok = gen_api_schema_json(Dir, Lang),
|
||||
ok = gen_schema_json(Dir, SchemaModule, Lang)
|
||||
end,
|
||||
["en", "zh"]
|
||||
|
@ -176,41 +176,15 @@ gen_schema_json(Dir, SchemaModule, Lang) ->
|
|||
IoData = emqx_utils_json:encode(JsonMap, [pretty, force_utf8]),
|
||||
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.
|
||||
hotconf_schema_json(Lang) ->
|
||||
hotconf_schema_json() ->
|
||||
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.
|
||||
bridge_schema_json(Lang) ->
|
||||
bridge_schema_json() ->
|
||||
SchemaInfo = #{title => <<"EMQX Data Bridge API Schema">>, version => <<"0.1.0">>},
|
||||
gen_api_schema_json_iodata(emqx_bridge_api, SchemaInfo, Lang).
|
||||
|
||||
schema_filename(Dir, Prefix, Lang) ->
|
||||
Filename = Prefix ++ Lang ++ ".json",
|
||||
filename:join([Dir, Filename]).
|
||||
gen_api_schema_json_iodata(emqx_bridge_api, SchemaInfo).
|
||||
|
||||
%% 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
|
||||
|
@ -270,50 +244,11 @@ gen_example(File, SchemaModule) ->
|
|||
Example = hocon_schema_example:gen(SchemaModule, Opts),
|
||||
file:write_file(File, Example).
|
||||
|
||||
%% TODO: move this to emqx_dashboard when we stop generating
|
||||
%% this JSON at build time.
|
||||
gen_api_schema_json_iodata(SchemaMod, SchemaInfo, Lang) ->
|
||||
{ApiSpec0, Components0} = emqx_dashboard_swagger:spec(
|
||||
gen_api_schema_json_iodata(SchemaMod, SchemaInfo) ->
|
||||
emqx_dashboard_swagger:gen_api_schema_json_iodata(
|
||||
SchemaMod,
|
||||
#{
|
||||
schema_converter => fun 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]
|
||||
SchemaInfo,
|
||||
fun ?MODULE:hocon_schema_to_spec/2
|
||||
).
|
||||
|
||||
-define(TO_REF(_N_, _F_), iolist_to_binary([to_bin(_N_), ".", to_bin(_F_)])).
|
||||
|
|
|
@ -45,18 +45,11 @@ schema("/schemas/:name") ->
|
|||
'operationId' => get_schema,
|
||||
get => #{
|
||||
parameters => [
|
||||
{name, hoconsc:mk(hoconsc:enum([hotconf, bridges]), #{in => path})},
|
||||
{lang,
|
||||
hoconsc:mk(typerefl:string(), #{
|
||||
in => query,
|
||||
default => <<"en">>,
|
||||
desc => <<"The language of the schema.">>
|
||||
})}
|
||||
{name, hoconsc:mk(hoconsc:enum([hotconf, bridges]), #{in => path})}
|
||||
],
|
||||
desc => <<
|
||||
"Get the schema JSON of the specified name. "
|
||||
"NOTE: you should never need to make use of this API "
|
||||
"unless you are building a multi-lang dashboaard."
|
||||
"NOTE: only intended for EMQX Dashboard."
|
||||
>>,
|
||||
tags => ?TAGS,
|
||||
security => [],
|
||||
|
@ -71,14 +64,13 @@ schema("/schemas/:name") ->
|
|||
%%--------------------------------------------------------------------
|
||||
|
||||
get_schema(get, #{
|
||||
bindings := #{name := Name},
|
||||
query_string := #{<<"lang">> := Lang}
|
||||
bindings := #{name := Name}
|
||||
}) ->
|
||||
{200, gen_schema(Name, iolist_to_binary(Lang))};
|
||||
{200, gen_schema(Name)};
|
||||
get_schema(get, _) ->
|
||||
{400, ?BAD_REQUEST, <<"unknown">>}.
|
||||
|
||||
gen_schema(hotconf, Lang) ->
|
||||
emqx_conf:hotconf_schema_json(Lang);
|
||||
gen_schema(bridges, Lang) ->
|
||||
emqx_conf:bridge_schema_json(Lang).
|
||||
gen_schema(hotconf) ->
|
||||
emqx_conf:hotconf_schema_json();
|
||||
gen_schema(bridges) ->
|
||||
emqx_conf:bridge_schema_json().
|
||||
|
|
|
@ -26,7 +26,11 @@
|
|||
-export([error_codes/1, error_codes/2]).
|
||||
-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).
|
||||
-export([
|
||||
|
@ -72,6 +76,8 @@
|
|||
])
|
||||
).
|
||||
|
||||
-define(SPECIAL_LANG_MSGID, <<"$msgid">>).
|
||||
|
||||
-define(MAX_ROW_LIMIT, 1000).
|
||||
-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
|
||||
%%------------------------------------------------------------------------------
|
||||
|
@ -482,6 +532,14 @@ maybe_add_summary_from_label(Spec, Hocon, Options) ->
|
|||
|
||||
get_i18n(Tag, ?DESC(Namespace, Id), Default, 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
|
||||
undefined ->
|
||||
Default;
|
||||
|
@ -489,6 +547,14 @@ get_i18n(Tag, ?DESC(Namespace, Id), Default, Options) ->
|
|||
Text
|
||||
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.
|
||||
%% At runtime, it's still the global config which controls the language.
|
||||
get_lang(#{i18n_lang := Lang}) -> Lang;
|
||||
|
|
|
@ -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.
|
|
@ -45,6 +45,17 @@ schema("/status") ->
|
|||
#{
|
||||
'operationId' => get_status,
|
||||
get => #{
|
||||
parameters => [
|
||||
{format,
|
||||
hoconsc:mk(
|
||||
string(),
|
||||
#{
|
||||
in => query,
|
||||
default => <<"text">>,
|
||||
desc => ?DESC(get_status_api_format)
|
||||
}
|
||||
)}
|
||||
],
|
||||
description => ?DESC(get_status_api),
|
||||
tags => ?TAGS,
|
||||
security => [],
|
||||
|
@ -70,7 +81,16 @@ path() ->
|
|||
"/status".
|
||||
|
||||
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),
|
||||
{ok, Req, State}.
|
||||
|
||||
|
@ -78,29 +98,52 @@ init(Req0, State) ->
|
|||
%% API Handler funcs
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
get_status(get, _Params) ->
|
||||
running_status().
|
||||
get_status(get, Params) ->
|
||||
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
|
||||
true ->
|
||||
BrokerStatus = broker_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 =
|
||||
case AppStatus of
|
||||
running -> 200;
|
||||
not_running -> 503
|
||||
end,
|
||||
ContentType =
|
||||
case Format of
|
||||
<<"json">> -> <<"applicatin/json">>;
|
||||
_ -> <<"text/plain">>
|
||||
end,
|
||||
Headers = #{
|
||||
<<"content-type">> => <<"text/plain">>,
|
||||
<<"content-type">> => ContentType,
|
||||
<<"retry-after">> => <<"15">>
|
||||
},
|
||||
{StatusCode, Headers, list_to_binary(Body)};
|
||||
{StatusCode, Headers, iolist_to_binary(Body)};
|
||||
false ->
|
||||
{503, #{<<"retry-after">> => <<"15">>}, <<>>}
|
||||
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() ->
|
||||
case emqx:is_running() of
|
||||
true ->
|
||||
|
|
|
@ -38,7 +38,10 @@ all() ->
|
|||
get_status_tests() ->
|
||||
[
|
||||
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() ->
|
||||
|
@ -87,8 +90,10 @@ do_request(Opts) ->
|
|||
headers := Headers,
|
||||
body := Body0
|
||||
} = Opts,
|
||||
QS = maps:get(qs, Opts, ""),
|
||||
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
|
||||
%% receives a 503 with `retry-after' header, and there's no option
|
||||
%% to stop that behavior...
|
||||
|
@ -165,3 +170,73 @@ t_status_not_ok(Config) ->
|
|||
Headers
|
||||
),
|
||||
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
7
build
|
@ -92,7 +92,7 @@ log() {
|
|||
}
|
||||
|
||||
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)"
|
||||
if [ -d "_build/default/lib/" ]; then
|
||||
libs_dir2="$("$FIND" "_build/default/lib/" -maxdepth 2 -name ebin -type d)"
|
||||
|
@ -113,14 +113,11 @@ make_docs() {
|
|||
;;
|
||||
esac
|
||||
docdir="_build/docgen/$PROFILE"
|
||||
dashboard_www_static='apps/emqx_dashboard/priv/www/static/'
|
||||
mkdir -p "$docdir" "$dashboard_www_static"
|
||||
mkdir -p "$docdir"
|
||||
# shellcheck disable=SC2086
|
||||
erl -noshell -pa $libs_dir1 $libs_dir2 $libs_dir3 -eval \
|
||||
"ok = emqx_conf:dump_schema('$docdir', $SCHEMA_MODULE), \
|
||||
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() {
|
||||
|
|
|
@ -1,21 +1,42 @@
|
|||
emqx_mgmt_api_status {
|
||||
|
||||
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.
|
||||
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:
|
||||
"""Service health check"""
|
||||
|
||||
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"""
|
||||
|
||||
get_status_response503.desc:
|
||||
"""Node emqx@127.0.0.1 is stopped
|
||||
emqx is not_running"""
|
||||
"""When EMQX application is temporary not running or being restarted, it may return '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."""
|
||||
|
||||
}
|
||||
|
|
|
@ -1,22 +1,34 @@
|
|||
emqx_mgmt_api_status {
|
||||
|
||||
get_status_api.desc:
|
||||
"""作为节点的健康检查。 返回一个纯文本的响应,描述节点的状态。
|
||||
"""节点的健康检查。 返回节点状态的描述信息。
|
||||
|
||||
如果 EMQX 应用程序已经启动并运行,返回状态代码 200,否则返回 503。
|
||||
|
||||
这个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_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"""
|
||||
|
||||
get_status_response503.desc:
|
||||
"""Node emqx@127.0.0.1 is stopped
|
||||
emqx is not_running"""
|
||||
"""如果 EMQX 应用暂时没有启动,或正在重启,则可能返回 'emqx is not_running'"""
|
||||
|
||||
get_status_api_format.desc:
|
||||
"""指定返回的内容格式。使用 'text'(默认)则返回自由格式的字符串; 'json' 则返回 JSON 格式。"""
|
||||
|
||||
}
|
||||
|
|
|
@ -2,42 +2,89 @@
|
|||
|
||||
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
|
||||
URL="http://$IP:$PORT/status"
|
||||
BASE_URL="http://$HOST:$PORT"
|
||||
|
||||
## Check if EMQX is responding
|
||||
ATTEMPTS=10
|
||||
while ! curl "$URL" >/dev/null 2>&1; do
|
||||
if [ $ATTEMPTS -eq 0 ]; then
|
||||
echo "emqx is not responding on $URL"
|
||||
exit 1
|
||||
wait_for_emqx() {
|
||||
local attempts=10
|
||||
local url="$BASE_URL"/status
|
||||
while ! curl "$url" >/dev/null 2>&1; do
|
||||
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
|
||||
sleep 5
|
||||
ATTEMPTS=$((ATTEMPTS-1))
|
||||
done
|
||||
}
|
||||
|
||||
## Check if the API docs are available
|
||||
API_DOCS_URL="http://$IP:$PORT/api-docs/index.html"
|
||||
API_DOCS_STATUS="$(curl -s -o /dev/null -w "%{http_code}" "$API_DOCS_URL")"
|
||||
if [ "$API_DOCS_STATUS" != "200" ]; then
|
||||
echo "emqx is not responding on $API_DOCS_URL"
|
||||
exit 1
|
||||
fi
|
||||
check_api_docs() {
|
||||
local url="$BASE_URL/api-docs/index.html"
|
||||
local status
|
||||
status="$(curl -s -o /dev/null -w "%{http_code}" "$url")"
|
||||
if [ "$status" != "200" ]; then
|
||||
echo "emqx is not responding on $API_DOCS_URL"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
## Check if the swagger.json contains hidden fields
|
||||
## fail if it does
|
||||
SWAGGER_JSON_URL="http://$IP:$PORT/api-docs/swagger.json"
|
||||
## assert swagger.json is valid json
|
||||
JSON="$(curl -s "$SWAGGER_JSON_URL")"
|
||||
echo "$JSON" | jq . >/dev/null
|
||||
|
||||
if [ "${EMQX_SMOKE_TEST_CHECK_HIDDEN_FIELDS:-yes}" = 'yes' ]; then
|
||||
check_swagger_json() {
|
||||
local url="$BASE_URL/api-docs/swagger.json"
|
||||
## assert swagger.json is valid json
|
||||
JSON="$(curl -s "$url")"
|
||||
echo "$JSON" | jq . >/dev/null
|
||||
## assert swagger.json does not contain trie_compaction (which is a hidden field)
|
||||
if echo "$JSON" | grep -q trie_compaction; then
|
||||
echo "swagger.json contains hidden fields"
|
||||
exit 1
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue