Merge pull request #7119 from JimMoen/refactor-node-api

refactor(api): api_nodes spec use dashboard_swagger
This commit is contained in:
zhongwencool 2022-02-24 14:16:59 +08:00 committed by GitHub
commit d35ff9303a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 205 additions and 126 deletions

View File

@ -40,12 +40,10 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
api_spec() -> api_spec() ->
% {[metrics_api()], [metrics_schema()]}.
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
paths() -> paths() ->
[ "/metrics" ["/metrics"].
].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% http handlers %% http handlers
@ -66,16 +64,15 @@ metrics(get, #{query_string := Qs}) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
schema("/metrics") -> schema("/metrics") ->
#{'operationId' => metrics, #{ 'operationId' => metrics
get => , get =>
#{ description => <<"EMQX metrics">> #{ description => <<"EMQX metrics">>
, parameters => , parameters =>
[{aggregate, [{ aggregate
mk(boolean(), , mk( boolean()
#{ in => query , #{ in => query
, nullable => true , nullable => true
, desc => <<"">> , desc => <<"Whether to aggregate all nodes Metrics">>})
})
}] }]
, responses => , responses =>
#{ 200 => hoconsc:union( #{ 200 => hoconsc:union(

View File

@ -17,121 +17,177 @@
-behaviour(minirest_api). -behaviour(minirest_api).
-import(emqx_mgmt_util, [ schema/2 -include_lib("emqx/include/emqx.hrl").
, object_schema/2 -include_lib("typerefl/include/types.hrl").
, object_array_schema/2
, error_schema/2 -import(hoconsc, [mk/2, ref/1, ref/2, enum/1, array/1]).
, properties/1
-define(NODE_METRICS_MODULE, emqx_mgmt_api_metrics).
-define(NODE_STATS_MODULE, emqx_mgmt_api_stats).
-define(SOURCE_ERROR, 'SOURCE_ERROR').
%% Swagger specs from hocon schema
-export([ api_spec/0
, schema/1
, paths/0
, fields/1
]). ]).
-export([api_spec/0]). %% API callbacks
-export([ nodes/2 -export([ nodes/2
, node/2 , node/2
, node_metrics/2 , node_metrics/2
, node_stats/2]). , node_stats/2
-include_lib("emqx/include/emqx.hrl").
api_spec() ->
{apis(), []}.
apis() ->
[ nodes_api()
, node_api()
, node_metrics_api()
, node_stats_api()].
properties() ->
properties([
{node, string, <<"Node name">>},
{connections, integer, <<"Number of clients currently connected to this node">>},
{load1, string, <<"CPU average load in 1 minute">>},
{load5, string, <<"CPU average load in 5 minute">>},
{load15, string, <<"CPU average load in 15 minute">>},
{max_fds, integer, <<"File descriptors limit">>},
{memory_total, string, <<"Allocated memory">>},
{memory_used, string, <<"Used memory">>},
{node_status, string, <<"Node status">>},
{otp_release, string, <<"Erlang/OTP version">>},
{process_available, integer, <<"Erlang processes limit">>},
{process_used, integer, <<"Running Erlang processes">>},
{uptime, integer, <<"System uptime, milliseconds">>},
{version, string, <<"Release version">>},
{sys_path, string, <<"Path to system files">>},
{log_path, string, <<"Path to log files">>},
{role, string, <<"Node role">>}
]). ]).
parameters() -> %%--------------------------------------------------------------------
[#{ %% API spec funcs
name => node_name, %%--------------------------------------------------------------------
in => path,
description => <<"node name">>, api_spec() ->
schema => #{type => string}, emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
required => true,
example => node() paths() ->
}]. [ "/nodes"
nodes_api() -> , "/nodes/:node"
Metadata = #{ , "/nodes/:node/metrics"
get => #{ , "/nodes/:node/stats"
description => <<"List EMQX nodes">>, ].
responses => #{
<<"200">> => object_array_schema(properties(), <<"List EMQX Nodes">>) schema("/nodes") ->
#{ 'operationId' => nodes
, get =>
#{ description => <<"List EMQX nodes">>
, responses =>
#{200 => mk( array(ref(node_info))
, #{desc => <<"List all EMQX nodes">>})}
}
};
schema("/nodes/:node") ->
#{ 'operationId' => node
, get =>
#{ description => <<"Get node info">>
, parameters => [ref(node_name)]
, responses =>
#{ 200 => mk( ref(node_info)
, #{desc => <<"Get node info successfully">>})
, 400 => node_error()
} }
} }
}, };
{"/nodes", Metadata, nodes}. schema("/nodes/:node/metrics") ->
#{ 'operationId' => node_metrics
, get =>
#{ description => <<"Get node metrics">>
, parameters => [ref(node_name)]
, responses =>
#{ 200 => mk( ref(?NODE_METRICS_MODULE, node_metrics)
, #{desc => <<"Get node metrics successfully">>})
, 400 => node_error()
}
}
};
schema("/nodes/:node/stats") ->
#{ 'operationId' => node_stats
, get =>
#{ description => <<"Get node stats">>
, parameters => [ref(node_name)]
, responses =>
#{ 200 => mk( ref(?NODE_STATS_MODULE, node_stats_data)
, #{desc => <<"Get node stats successfully">>})
, 400 => node_error()
}
}
}.
node_api() -> %%--------------------------------------------------------------------
Metadata = #{ %% Fields
get => #{
description => <<"Get node info">>,
parameters => parameters(),
responses => #{
<<"400">> => error_schema(<<"Node error">>, ['SOURCE_ERROR']),
<<"200">> => object_schema(properties(), <<"Get EMQX Nodes info by name">>)}}},
{"/nodes/:node_name", Metadata, node}.
node_metrics_api() -> fields(node_name) ->
Metadata = #{ [ { node
get => #{ , mk(atom()
description => <<"Get node metrics">>, , #{ in => path
parameters => parameters(), , description => <<"Node name">>
responses => #{ , required => true
<<"400">> => error_schema(<<"Node error">>, ['SOURCE_ERROR']), , example => <<"emqx@127.0.0.1">>
%% TODO: Node Metrics Schema })
<<"200">> => schema(metrics, <<"Get EMQX Node Metrics">>)}}}, }
{"/nodes/:node_name/metrics", Metadata, node_metrics}. ];
fields(node_info) ->
[ { node
, mk( atom()
, #{desc => <<"Node name">>, example => <<"emqx@127.0.0.1">>})}
, { connections
, mk( non_neg_integer()
, #{desc => <<"Number of clients currently connected to this node">>, example => 0})}
, { load1
, mk( string()
, #{desc => <<"CPU average load in 1 minute">>, example => "2.66"})}
, { load5
, mk( string()
, #{desc => <<"CPU average load in 5 minute">>, example => "2.66"})}
, { load15
, mk( string()
, #{desc => <<"CPU average load in 15 minute">>, example => "2.66"})}
, { max_fds
, mk( non_neg_integer()
, #{desc => <<"File descriptors limit">>, example => 1024})}
, { memory_total
, mk( emqx_schema:bytesize()
, #{desc => <<"Allocated memory">>, example => "512.00M"})}
, { memory_used
, mk( emqx_schema:bytesize()
, #{desc => <<"Used memory">>, example => "256.00M"})}
, { node_status
, mk( enum(["Running", "Stopped"])
, #{desc => <<"Node status">>, example => "Running"})}
, { otp_release
, mk( string()
, #{ desc => <<"Erlang/OTP version">>, example => "24.2/12.2"})}
, { process_available
, mk( non_neg_integer()
, #{desc => <<"Erlang processes limit">>, example => 2097152})}
, { process_used
, mk( non_neg_integer()
, #{desc => <<"Running Erlang processes">>, example => 1024})}
, { uptime
, mk( non_neg_integer()
, #{desc => <<"System uptime, milliseconds">>, example => 5120000})}
, { version
, mk( string()
, #{desc => <<"Release version">>, example => "5.0.0-beat.3-00000000"})}
, { sys_path
, mk( string()
, #{desc => <<"Path to system files">>, example => "path/to/emqx"})}
, { log_path
, mk( string()
, #{desc => <<"Path to log files">>, example => "path/to/log | not found"})}
, { role
, mk( enum(["core", "replicant"])
, #{desc => <<"Node role">>, example => "core"})}
].
node_stats_api() -> %%--------------------------------------------------------------------
Metadata = #{ %% API Handler funcs
get => #{ %%--------------------------------------------------------------------
description => <<"Get node stats">>,
parameters => parameters(),
responses => #{
<<"400">> => error_schema(<<"Node error">>, ['SOURCE_ERROR']),
%% TODO: Node Stats Schema
<<"200">> => schema(stat, <<"Get EMQX Node Stats">>)}}},
{"/nodes/:node_name/stats", Metadata, node_stats}.
%%%==============================================================================================
%% parameters trans
nodes(get, _Params) -> nodes(get, _Params) ->
list(#{}). list_nodes(#{}).
node(get, #{bindings := #{node_name := NodeName}}) -> node(get, #{bindings := #{node := NodeName}}) ->
get_node(binary_to_atom(NodeName, utf8)). get_node(NodeName).
node_metrics(get, #{bindings := #{node_name := NodeName}}) -> node_metrics(get, #{bindings := #{node := NodeName}}) ->
get_metrics(binary_to_atom(NodeName, utf8)). get_metrics(NodeName).
node_stats(get, #{bindings := #{node_name := NodeName}}) -> node_stats(get, #{bindings := #{node := NodeName}}) ->
get_stats(binary_to_atom(NodeName, utf8)). get_stats(NodeName).
%%%============================================================================================== %%--------------------------------------------------------------------
%% api apply %% api apply
list(#{}) ->
list_nodes(#{}) ->
NodesInfo = [format(Node, NodeInfo) || {Node, NodeInfo} <- emqx_mgmt:list_nodes()], NodesInfo = [format(Node, NodeInfo) || {Node, NodeInfo} <- emqx_mgmt:list_nodes()],
{200, NodesInfo}. {200, NodesInfo}.
@ -159,7 +215,7 @@ get_stats(Node) ->
{200, Stats} {200, Stats}
end. end.
%%============================================================================================================ %%--------------------------------------------------------------------
%% internal function %% internal function
format(_Node, Info = #{memory_total := Total, memory_used := Used}) -> format(_Node, Info = #{memory_total := Total, memory_used := Used}) ->
@ -188,3 +244,6 @@ get_log_path([_LoggerConfig | LoggerConfigs]) ->
get_log_path(LoggerConfigs); get_log_path(LoggerConfigs);
get_log_path([]) -> get_log_path([]) ->
undefined. undefined.
node_error() ->
emqx_dashboard_swagger:error_codes([?SOURCE_ERROR], <<"Node error">>).

View File

@ -46,7 +46,7 @@ schema("/stats") ->
, tags => [<<"stats">>] , tags => [<<"stats">>]
, parameters => [ref(aggregate)] , parameters => [ref(aggregate)]
, responses => , responses =>
#{ 200 => mk( hoconsc:union([ ref(?MODULE, base_data) #{ 200 => mk( hoconsc:union([ ref(?MODULE, node_stats_data)
, array(ref(?MODULE, aggergate_data)) , array(ref(?MODULE, aggergate_data))
]) ])
, #{ desc => <<"List stats ok">> }) , #{ desc => <<"List stats ok">> })
@ -62,7 +62,7 @@ fields(aggregate) ->
, nullable => true , nullable => true
, example => false})} , example => false})}
]; ];
fields(base_data) -> fields(node_stats_data) ->
[ { 'channels.count' [ { 'channels.count'
, mk( integer(), #{ desc => <<"sessions.count">> , mk( integer(), #{ desc => <<"sessions.count">>
, example => 0})} , example => 0})}
@ -140,7 +140,7 @@ fields(aggergate_data) ->
[ { node [ { node
, mk( string(), #{ desc => <<"Node name">> , mk( string(), #{ desc => <<"Node name">>
, example => <<"emqx@127.0.0.1">>})} , example => <<"emqx@127.0.0.1">>})}
] ++ fields(base_data). ] ++ fields(node_stats_data).
%%%============================================================================================== %%%==============================================================================================

View File

@ -31,13 +31,36 @@ end_per_suite(_) ->
emqx_mgmt_api_test_util:end_suite(). emqx_mgmt_api_test_util:end_suite().
t_metrics_api(_) -> t_metrics_api(_) ->
MetricsPath = emqx_mgmt_api_test_util:api_path(["metrics?aggregate=true"]), {ok, MetricsResponse} = request_helper("metrics?aggregate=true"),
SystemMetrics = emqx_mgmt:get_metrics(), MetricsFromAPI = emqx_json:decode(MetricsResponse, [return_maps]),
{ok, MetricsResponse} = emqx_mgmt_api_test_util:request_api(get, MetricsPath), AggregateMetrics = emqx_mgmt:get_metrics(),
Metrics = emqx_json:decode(MetricsResponse, [return_maps]), match_helper(AggregateMetrics, MetricsFromAPI).
?assertEqual(erlang:length(maps:keys(SystemMetrics)), erlang:length(maps:keys(Metrics))),
t_single_node_metrics_api(_) ->
{ok, MetricsResponse} = request_helper("metrics"),
[MetricsFromAPI] = emqx_json:decode(MetricsResponse, [return_maps]),
LocalNodeMetrics = maps:from_list(
emqx_mgmt:get_metrics(node()) ++ [{node, to_bin(node())}]),
match_helper(LocalNodeMetrics, MetricsFromAPI).
match_helper(SystemMetrics, MetricsFromAPI) ->
length_equal(SystemMetrics, MetricsFromAPI),
Fun = Fun =
fun(Key) -> fun (Key, {SysMetrics, APIMetrics}) ->
?assertEqual(maps:get(Key, SystemMetrics), maps:get(atom_to_binary(Key, utf8), Metrics)) Value = maps:get(Key, SysMetrics),
?assertEqual(Value, maps:get(to_bin(Key), APIMetrics)),
{Value, {SysMetrics, APIMetrics}}
end, end,
lists:foreach(Fun, maps:keys(SystemMetrics)). lists:mapfoldl(Fun, {SystemMetrics, MetricsFromAPI}, maps:keys(SystemMetrics)).
length_equal(SystemMetrics, MetricsFromAPI) ->
?assertEqual(erlang:length(maps:keys(SystemMetrics)), erlang:length(maps:keys(MetricsFromAPI))).
request_helper(Path) ->
MetricsPath = emqx_mgmt_api_test_util:api_path([Path]),
emqx_mgmt_api_test_util:request_api(get, MetricsPath).
to_bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
to_bin(L) when is_list(L) -> list_to_binary(L);
to_bin(I) when is_integer(I) -> integer_to_binary(I);
to_bin(B) when is_binary(B) -> B.