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: |
|
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
|
||||||
|
|
|
@ -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_)])).
|
||||||
|
|
|
@ -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().
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
'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 ->
|
||||||
|
|
|
@ -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
7
build
|
@ -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() {
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 格式。"""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
if [ $attempts -eq 0 ]; then
|
||||||
|
echo "emqx is not responding on $url"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
sleep 5
|
sleep 5
|
||||||
ATTEMPTS=$((ATTEMPTS-1))
|
attempts=$((attempts-1))
|
||||||
done
|
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
|
||||||
|
}
|
||||||
|
|
||||||
## 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
|
||||||
|
status="$(curl -s -o /dev/null -w "%{http_code}" "$url")"
|
||||||
|
if [ "$status" != "200" ]; then
|
||||||
echo "emqx is not responding on $API_DOCS_URL"
|
echo "emqx is not responding on $API_DOCS_URL"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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() {
|
||||||
|
local url="$BASE_URL/api-docs/swagger.json"
|
||||||
## assert swagger.json is valid json
|
## assert swagger.json is valid json
|
||||||
JSON="$(curl -s "$SWAGGER_JSON_URL")"
|
JSON="$(curl -s "$url")"
|
||||||
echo "$JSON" | jq . >/dev/null
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
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