Merge pull request #7119 from JimMoen/refactor-node-api
refactor(api): api_nodes spec use dashboard_swagger
This commit is contained in:
commit
d35ff9303a
|
@ -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(
|
||||||
|
|
|
@ -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">>).
|
||||||
|
|
|
@ -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).
|
||||||
|
|
||||||
|
|
||||||
%%%==============================================================================================
|
%%%==============================================================================================
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue