Просмотр исходного кода

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

refactor(api): api_nodes spec use dashboard_swagger
zhongwencool 4 лет назад
Родитель
Сommit
d35ff9303a

+ 17 - 20
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() ->

+ 160 - 101
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").
+
+-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).
 
--export([api_spec/0]).
+-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(), []}.
-
-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() ->
-    [#{
-        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">>)
+    emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
+
+paths() ->
+    [ "/nodes"
+    , "/nodes/:node"
+    , "/nodes/:node/metrics"
+    , "/nodes/:node/stats"
+    ].
+
+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()
+                   }
+            }
+     };
+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()
+                   }
             }
-        }
-    },
-    {"/nodes", Metadata, nodes}.
-
-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}.
-
-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}.
-
-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}.
-
-%%%==============================================================================================
-%% parameters trans
+     }.
+
+%%--------------------------------------------------------------------
+%% Fields
+
+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"})}
+    ].
+
+%%--------------------------------------------------------------------
+%% API Handler funcs
+%%--------------------------------------------------------------------
+
 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">>).

+ 3 - 3
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).
 
 
 %%%==============================================================================================

+ 31 - 8
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.