From 806cf3719b9c3a29deb9dcd0526339f026e3c3e7 Mon Sep 17 00:00:00 2001 From: JimMoen Date: Wed, 23 Feb 2022 10:54:45 +0800 Subject: [PATCH 1/2] refactor(api): api_nodes spec use dashboard_swagger --- .../src/emqx_mgmt_api_metrics.erl | 37 ++- .../src/emqx_mgmt_api_nodes.erl | 249 +++++++++++------- .../src/emqx_mgmt_api_stats.erl | 6 +- 3 files changed, 174 insertions(+), 118 deletions(-) diff --git a/apps/emqx_management/src/emqx_mgmt_api_metrics.erl b/apps/emqx_management/src/emqx_mgmt_api_metrics.erl index 0ea9f2a01..5ebc01c21 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_metrics.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_metrics.erl @@ -40,12 +40,10 @@ %%-------------------------------------------------------------------- api_spec() -> -% {[metrics_api()], [metrics_schema()]}. emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). paths() -> - [ "/metrics" - ]. + ["/metrics"]. %%-------------------------------------------------------------------- %% http handlers @@ -66,23 +64,22 @@ metrics(get, #{query_string := Qs}) -> %%-------------------------------------------------------------------- schema("/metrics") -> - #{'operationId' => metrics, - get => - #{ description => <<"EMQX metrics">> - , parameters => - [{aggregate, - mk(boolean(), - #{ in => query - , nullable => true - , desc => <<"">> - }) - }] - , responses => - #{ 200 => hoconsc:union( - [ref(?MODULE, aggregated_metrics), - hoconsc:array(ref(?MODULE, node_metrics))]) - } - } + #{ 'operationId' => metrics + , get => + #{ description => <<"EMQX metrics">> + , parameters => + [{ aggregate + , mk( boolean() + , #{ in => query + , nullable => true + , desc => <<"Whether to aggregate all nodes Metrics">>}) + }] + , responses => + #{ 200 => hoconsc:union( + [ref(?MODULE, aggregated_metrics), + hoconsc:array(ref(?MODULE, node_metrics))]) + } + } }. roots() -> diff --git a/apps/emqx_management/src/emqx_mgmt_api_nodes.erl b/apps/emqx_management/src/emqx_mgmt_api_nodes.erl index cb8a6dbb7..79c40a2e6 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_nodes.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_nodes.erl @@ -17,121 +17,177 @@ -behaviour(minirest_api). --import(emqx_mgmt_util, [ schema/2 - , object_schema/2 - , object_array_schema/2 - , error_schema/2 - , properties/1 - ]). +-include_lib("emqx/include/emqx.hrl"). +-include_lib("typerefl/include/types.hrl"). --export([api_spec/0]). +-import(hoconsc, [mk/2, ref/1, ref/2, enum/1, array/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 + ]). + +%% API callbacks -export([ nodes/2 , node/2 , node_metrics/2 - , node_stats/2]). + , node_stats/2 + ]). --include_lib("emqx/include/emqx.hrl"). +%%-------------------------------------------------------------------- +%% API spec funcs +%%-------------------------------------------------------------------- api_spec() -> - {apis(), []}. + emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). -apis() -> - [ nodes_api() - , node_api() - , node_metrics_api() - , node_stats_api()]. +paths() -> + [ "/nodes" + , "/nodes/:node" + , "/nodes/:node/metrics" + , "/nodes/:node/stats" + ]. -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() -> - [#{ - name => node_name, - in => path, - description => <<"node name">>, - schema => #{type => string}, - required => true, - example => node() - }]. -nodes_api() -> - Metadata = #{ - get => #{ - 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">>})} } - } - }, - {"/nodes", Metadata, 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() + } + } + }; +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 = #{ - 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}. +%%-------------------------------------------------------------------- +%% Fields -node_metrics_api() -> - Metadata = #{ - get => #{ - description => <<"Get node metrics">>, - parameters => parameters(), - responses => #{ - <<"400">> => error_schema(<<"Node error">>, ['SOURCE_ERROR']), - %% TODO: Node Metrics Schema - <<"200">> => schema(metrics, <<"Get EMQX Node Metrics">>)}}}, - {"/nodes/:node_name/metrics", Metadata, node_metrics}. +fields(node_name) -> + [ { node + , mk(atom() + , #{ in => path + , description => <<"Node name">> + , required => true + , example => <<"emqx@127.0.0.1">> + }) + } + ]; +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 = #{ - 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}. +%%-------------------------------------------------------------------- +%% API Handler funcs +%%-------------------------------------------------------------------- -%%%============================================================================================== -%% parameters trans nodes(get, _Params) -> - list(#{}). + list_nodes(#{}). -node(get, #{bindings := #{node_name := NodeName}}) -> - get_node(binary_to_atom(NodeName, utf8)). +node(get, #{bindings := #{node := NodeName}}) -> + get_node(NodeName). -node_metrics(get, #{bindings := #{node_name := NodeName}}) -> - get_metrics(binary_to_atom(NodeName, utf8)). +node_metrics(get, #{bindings := #{node := NodeName}}) -> + get_metrics(NodeName). -node_stats(get, #{bindings := #{node_name := NodeName}}) -> - get_stats(binary_to_atom(NodeName, utf8)). +node_stats(get, #{bindings := #{node := NodeName}}) -> + get_stats(NodeName). -%%%============================================================================================== +%%-------------------------------------------------------------------- %% api apply -list(#{}) -> + +list_nodes(#{}) -> NodesInfo = [format(Node, NodeInfo) || {Node, NodeInfo} <- emqx_mgmt:list_nodes()], {200, NodesInfo}. @@ -159,7 +215,7 @@ get_stats(Node) -> {200, Stats} end. -%%============================================================================================================ +%%-------------------------------------------------------------------- %% internal function 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([]) -> undefined. + +node_error() -> + emqx_dashboard_swagger:error_codes([?SOURCE_ERROR], <<"Node error">>). diff --git a/apps/emqx_management/src/emqx_mgmt_api_stats.erl b/apps/emqx_management/src/emqx_mgmt_api_stats.erl index 478a7489c..d97a46623 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_stats.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_stats.erl @@ -46,7 +46,7 @@ schema("/stats") -> , tags => [<<"stats">>] , parameters => [ref(aggregate)] , responses => - #{ 200 => mk( hoconsc:union([ ref(?MODULE, base_data) + #{ 200 => mk( hoconsc:union([ ref(?MODULE, node_stats_data) , array(ref(?MODULE, aggergate_data)) ]) , #{ desc => <<"List stats ok">> }) @@ -62,7 +62,7 @@ fields(aggregate) -> , nullable => true , example => false})} ]; -fields(base_data) -> +fields(node_stats_data) -> [ { 'channels.count' , mk( integer(), #{ desc => <<"sessions.count">> , example => 0})} @@ -140,7 +140,7 @@ fields(aggergate_data) -> [ { node , mk( string(), #{ desc => <<"Node name">> , example => <<"emqx@127.0.0.1">>})} - ] ++ fields(base_data). + ] ++ fields(node_stats_data). %%%============================================================================================== From cf61349aefc6d84b7534c2d731fe0f1acdd86d9f Mon Sep 17 00:00:00 2001 From: JimMoen Date: Wed, 23 Feb 2022 18:34:27 +0800 Subject: [PATCH 2/2] test(api_metrics): test `node_metrics` without aggergate --- .../test/emqx_mgmt_api_metrics_SUITE.erl | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/apps/emqx_management/test/emqx_mgmt_api_metrics_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_metrics_SUITE.erl index bc2e6e5f5..5d6f9e80b 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_metrics_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_metrics_SUITE.erl @@ -31,13 +31,36 @@ end_per_suite(_) -> emqx_mgmt_api_test_util:end_suite(). t_metrics_api(_) -> - MetricsPath = emqx_mgmt_api_test_util:api_path(["metrics?aggregate=true"]), - SystemMetrics = emqx_mgmt:get_metrics(), - {ok, MetricsResponse} = emqx_mgmt_api_test_util:request_api(get, MetricsPath), - Metrics = emqx_json:decode(MetricsResponse, [return_maps]), - ?assertEqual(erlang:length(maps:keys(SystemMetrics)), erlang:length(maps:keys(Metrics))), + {ok, MetricsResponse} = request_helper("metrics?aggregate=true"), + MetricsFromAPI = emqx_json:decode(MetricsResponse, [return_maps]), + AggregateMetrics = emqx_mgmt:get_metrics(), + match_helper(AggregateMetrics, MetricsFromAPI). + +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(Key) -> - ?assertEqual(maps:get(Key, SystemMetrics), maps:get(atom_to_binary(Key, utf8), Metrics)) + fun (Key, {SysMetrics, APIMetrics}) -> + Value = maps:get(Key, SysMetrics), + ?assertEqual(Value, maps:get(to_bin(Key), APIMetrics)), + {Value, {SysMetrics, APIMetrics}} 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.