Quellcode durchsuchen

Merge pull request #7332 from EMQ-YangM/add_authn_authz_status_api

feat: add authn, authz status api
Xinyu Liu vor 4 Jahren
Ursprung
Commit
40f1bfb3ab

+ 77 - 13
apps/emqx_authn/src/emqx_authn_api.erl

@@ -49,8 +49,10 @@
 
 -export([ authenticators/2
         , authenticator/2
+        , authenticator_status/2
         , listener_authenticators/2
         , listener_authenticator/2
+        , listener_authenticator_status/2
         , authenticator_move/2
         , listener_authenticator_move/2
         , authenticator_import_users/2
@@ -88,6 +90,7 @@ api_spec() ->
 
 paths() -> [ "/authentication"
            , "/authentication/:id"
+           , "/authentication/:id/status"
            , "/authentication/:id/move"
            , "/authentication/:id/import_users"
            , "/authentication/:id/users"
@@ -95,6 +98,7 @@ paths() -> [ "/authentication"
 
            , "/listeners/:listener_id/authentication"
            , "/listeners/:listener_id/authentication/:id"
+           , "/listeners/:listener_id/authentication/:id/status"
            , "/listeners/:listener_id/authentication/:id/move"
            , "/listeners/:listener_id/authentication/:id/import_users"
            , "/listeners/:listener_id/authentication/:id/users"
@@ -213,6 +217,22 @@ schema("/authentication/:id") ->
         }
     };
 
+schema("/authentication/:id/status") ->
+    #{
+        'operationId' => authenticator_status,
+        get => #{
+            tags => ?API_TAGS_GLOBAL,
+            description => <<"Get authenticator status from global authentication chain">>,
+            parameters => [param_auth_id()],
+            responses => #{
+                200 => emqx_dashboard_swagger:schema_with_examples(
+                    hoconsc:ref(emqx_authn_schema, "metrics_status_fields"),
+                    status_metrics_example()),
+                400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+            }
+        }
+    };
+
 schema("/listeners/:listener_id/authentication") ->
     #{
         'operationId' => listener_authenticators,
@@ -285,6 +305,21 @@ schema("/listeners/:listener_id/authentication/:id") ->
         }
     };
 
+schema("/listeners/:listener_id/authentication/:id/status") ->
+    #{
+        'operationId' => listener_authenticator_status,
+        get => #{
+            tags => ?API_TAGS_SINGLE,
+            description => <<"Get authenticator status from listener authentication chain">>,
+            parameters => [param_listener_id(), param_auth_id()],
+            responses => #{
+                200 => emqx_dashboard_swagger:schema_with_examples(
+                    hoconsc:ref(emqx_authn_schema, "metrics_status_fields"),
+                    status_metrics_example()),
+                400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+            }
+        }
+    };
 
 schema("/authentication/:id/move") ->
     #{
@@ -560,6 +595,9 @@ authenticator(put, #{bindings := #{id := AuthenticatorID}, body := Config}) ->
 authenticator(delete, #{bindings := #{id := AuthenticatorID}}) ->
     delete_authenticator([authentication], ?GLOBAL, AuthenticatorID).
 
+authenticator_status(get, #{bindings := #{id := AuthenticatorID}}) ->
+    lookup_from_all_nodes(?GLOBAL, AuthenticatorID).
+
 listener_authenticators(post, #{bindings := #{listener_id := ListenerID}, body := Config}) ->
     with_listener(ListenerID,
                   fun(Type, Name, ChainName) ->
@@ -599,6 +637,13 @@ listener_authenticator(delete,
                                              AuthenticatorID)
                   end).
 
+listener_authenticator_status(get,
+                      #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}}) ->
+    with_listener(ListenerID,
+                  fun(_, _, ChainName) ->
+                          lookup_from_all_nodes(ChainName, AuthenticatorID)
+                  end).
+
 authenticator_move(post,
                    #{bindings := #{id := AuthenticatorID},
                      body := #{<<"position">> := Position}}) ->
@@ -797,16 +842,16 @@ lookup_from_all_nodes(ChainName, AuthenticatorID) ->
              MKMap = fun (Name) -> fun ({Key, Val}) -> #{ node => Key, Name => Val } end end,
              HelpFun = fun (M, Name) -> lists:map(MKMap(Name), maps:to_list(M)) end,
              case AggregateStatus of
-                 empty_metrics_and_status -> {ok, #{}};
-                 _ -> {ok, #{node_status => HelpFun(StatusMap, status),
-                             node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics),
-                             status => AggregateStatus,
-                             metrics => restructure_map(AggregateMetrics)
-                            }
+                 empty_metrics_and_status -> serialize_error(unsupported_operation);
+                 _ -> {200, #{node_status => HelpFun(StatusMap, status),
+                              node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics),
+                              status => AggregateStatus,
+                              metrics => restructure_map(AggregateMetrics)
+                             }
                       }
              end;
         {error, ErrL} ->
-            {error, error_msg('INTERNAL_ERROR', ErrL)}
+            serialize_error(ErrL)
     end.
 
 aggregate_status([]) -> empty_metrics_and_status;
@@ -865,12 +910,6 @@ restructure_map(#{counters := #{failed := Failed, matched := Match, success := S
 restructure_map(Error) ->
      Error.
 
-error_msg(Code, Msg) ->
-              #{code => Code, message => bin(io_lib:format("~p", [Msg]))}.
-
-bin(S) when is_list(S) ->
-    list_to_binary(S).
-
 is_ok(ResL) ->
     case lists:filter(fun({ok, _}) -> false; (_) -> true end, ResL) of
         [] -> {ok, [Res || {ok, Res} <- ResL]};
@@ -1169,6 +1208,31 @@ authenticator_examples() ->
         }
     }.
 
+status_metrics_example() ->
+    #{ metrics => #{  matched => 0,
+                      success => 0,
+                      failed => 0,
+                      rate => 0.0,
+                      rate_last5m => 0.0,
+                      rate_max => 0.0
+                   },
+       node_metrics => [ #{node => node(),
+                           metrics => #{  matched => 0,
+                                          success => 0,
+                                          failed => 0,
+                                          rate => 0.0,
+                                          rate_last5m => 0.0,
+                                          rate_max => 0.0
+                                       }
+                           }
+                       ],
+       status => connected,
+       node_status => [ #{node => node(),
+                          status => connected
+                         }
+                      ]
+     }.
+
 request_user_create_examples() ->
     #{
         regular_user => #{

+ 39 - 1
apps/emqx_authn/src/emqx_authn_schema.erl

@@ -16,7 +16,9 @@
 
 -module(emqx_authn_schema).
 
+-elvis([{elvis_style, invalid_dynamic_call, disable}]).
 -include_lib("typerefl/include/types.hrl").
+-import(hoconsc, [mk/2, ref/2]).
 
 -export([ common_fields/0
         , roots/0
@@ -29,7 +31,6 @@
 
 roots() -> [].
 
-fields(_) -> [].
 
 common_fields() ->
     [ {enable, fun enable/1}
@@ -59,3 +60,40 @@ mechanism(Name) ->
 backend(Name) ->
     hoconsc:mk(hoconsc:enum([Name]),
                #{required => true}).
+
+fields("metrics_status_fields") ->
+    [ {"metrics", mk(ref(?MODULE, "metrics"), #{desc => "The metrics of the resource"})}
+    , {"node_metrics", mk(hoconsc:array(ref(?MODULE, "node_metrics")),
+                          #{ desc => "The metrics of the resource for each node"
+                           })}
+    , {"status", mk(status(), #{desc => "The status of the resource"})}
+    , {"node_status", mk(hoconsc:array(ref(?MODULE, "node_status")),
+                         #{ desc => "The status of the resource for each node"
+                          })}
+    ];
+
+fields("metrics") ->
+    [ {"matched", mk(integer(), #{desc => "Count of this resource is queried"})}
+    , {"success", mk(integer(), #{desc => "Count of query success"})}
+    , {"failed", mk(integer(), #{desc => "Count of query failed"})}
+    , {"rate", mk(float(), #{desc => "The rate of matched, times/second"})}
+    , {"rate_max", mk(float(), #{desc => "The max rate of matched, times/second"})}
+    , {"rate_last5m", mk(float(),
+           #{desc => "The average rate of matched in the last 5 minutes, times/second"})}
+    ];
+
+fields("node_metrics") ->
+    [ node_name()
+    , {"metrics", mk(ref(?MODULE, "metrics"), #{})}
+    ];
+
+fields("node_status") ->
+    [ node_name()
+    , {"status", mk(status(), #{})}
+    ].
+
+status() ->
+    hoconsc:enum([connected, disconnected, connecting]).
+
+node_name() ->
+    {"node", mk(binary(), #{desc => "The node name", example => "emqx@127.0.0.1"})}.

+ 28 - 24
apps/emqx_authn/test/emqx_authn_api_SUITE.erl

@@ -167,30 +167,34 @@ test_authenticator(PathPrefix) ->
     {ok, 200, _} = request(
                      get,
                      uri(PathPrefix ++ [?CONF_NS, "password_based:http"])),
-    %% {ok, RList} = emqx_json:safe_decode(Res),
-    %% Snd = fun ({_, Val}) -> Val end,
-    %% LookupVal = fun LookupV(List, RestJson) ->
-    %%         case List of
-    %%             [Name] -> Snd(lists:keyfind(Name, 1, RestJson));
-    %%             [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson)))
-    %%         end
-    %%     end,
-    %% LookFun = fun (List) -> LookupVal(List, RList) end,
-    %% MetricsList = [{<<"failed">>, 0},
-    %%                {<<"matched">>, 0},
-    %%                {<<"rate">>, 0.0},
-    %%                {<<"rate_last5m">>, 0.0},
-    %%                {<<"rate_max">>, 0.0},
-    %%                {<<"success">>, 0}],
-    %% EqualFun = fun ({M, V}) ->
-    %%                ?assertEqual(V, LookFun([<<"metrics">>,
-    %%                                         M]
-    %%                                       )
-    %%                            ) end,
-    %% lists:map(EqualFun, MetricsList),
-    %% ?assertEqual(<<"connected">>,
-    %%              LookFun([<<"status">>
-    %%                      ])),
+
+    {ok, 200, Res} = request(
+                     get,
+                     uri(PathPrefix ++ [?CONF_NS, "password_based:http", "status"])),
+    {ok, RList} = emqx_json:safe_decode(Res),
+    Snd = fun ({_, Val}) -> Val end,
+    LookupVal = fun LookupV(List, RestJson) ->
+            case List of
+                [Name] -> Snd(lists:keyfind(Name, 1, RestJson));
+                [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson)))
+            end
+        end,
+    LookFun = fun (List) -> LookupVal(List, RList) end,
+    MetricsList = [{<<"failed">>, 0},
+                   {<<"matched">>, 0},
+                   {<<"rate">>, 0.0},
+                   {<<"rate_last5m">>, 0.0},
+                   {<<"rate_max">>, 0.0},
+                   {<<"success">>, 0}],
+    EqualFun = fun ({M, V}) ->
+                   ?assertEqual(V, LookFun([<<"metrics">>,
+                                            M]
+                                          )
+                               ) end,
+    lists:map(EqualFun, MetricsList),
+    ?assertEqual(<<"connected">>,
+                 LookFun([<<"status">>
+                         ])),
     {ok, 404, _} = request(
                      get,
                      uri(PathPrefix ++ [?CONF_NS, "password_based:redis"])),

+ 60 - 6
apps/emqx_authz/src/emqx_authz_api_sources.erl

@@ -54,6 +54,7 @@
 
 -export([ get_raw_sources/0
         , get_raw_source/1
+        , source_status/2
         , lookup_from_local_node/1
         , lookup_from_all_nodes/1
         ]).
@@ -74,6 +75,7 @@ api_spec() ->
 paths() ->
     [ "/authorization/sources"
     , "/authorization/sources/:type"
+    , "/authorization/sources/:type/status"
     , "/authorization/sources/:type/move"].
 
 %%--------------------------------------------------------------------
@@ -148,6 +150,19 @@ schema("/authorization/sources/:type") ->
                    }
             }
      };
+schema("/authorization/sources/:type/status") ->
+    #{ 'operationId' => source_status
+     , get =>
+           #{ description => <<"Get a authorization source">>
+            , parameters => parameters_field()
+            , responses =>
+                  #{ 200 => emqx_dashboard_swagger:schema_with_examples(
+                              hoconsc:ref(emqx_authn_schema, "metrics_status_fields"),
+                              status_metrics_example())
+                   , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad request">>)
+                   }
+            }
+     };
 schema("/authorization/sources/:type/move") ->
     #{ 'operationId' => move_source
      , post =>
@@ -250,6 +265,21 @@ source(put, #{bindings := #{type := Type}, body := Body}) when is_map(Body) ->
 source(delete, #{bindings := #{type := Type}}) ->
     update_config({?CMD_DELETE, Type}, #{}).
 
+source_status(get, #{bindings := #{type := Type}}) ->
+    BinType = atom_to_binary(Type, utf8),
+    case get_raw_source(BinType) of
+        [] -> {400, #{code => <<"BAD_REQUEST">>,
+                      message => <<"Not found", BinType/binary>>}};
+        [#{<<"type">> := <<"file">>}] ->
+            {400, #{code => <<"BAD_REQUEST">>,
+                    message => <<"Not Support Status">>}};
+        [_] ->
+            case emqx_authz:lookup(Type) of
+                #{annotations := #{id := ResourceId }} -> lookup_from_all_nodes(ResourceId);
+                _ -> {400, #{code => <<"BAD_REQUEST">>, message => <<"Resource Disable">>}}
+            end
+    end.
+
 move_source(Method, #{bindings := #{type := Type} = Bindings } = Req)
   when is_atom(Type) ->
     move_source(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}});
@@ -301,8 +331,9 @@ lookup_from_all_nodes(ResourceId) ->
          MKMap = fun (Name) -> fun ({Key, Val}) -> #{ node => Key, Name => Val } end end,
          HelpFun = fun (M, Name) -> lists:map(MKMap(Name), maps:to_list(M)) end,
          case AggregateStatus of
-             empty_metrics_and_status -> {ok, #{}};
-             _ -> {ok, #{node_status => HelpFun(StatusMap, status),
+             empty_metrics_and_status -> {400, #{code => <<"BAD_REQUEST">>,
+                                               message => <<"Resource Not Support Status">>}};
+             _ -> {200, #{node_status => HelpFun(StatusMap, status),
                          node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics),
                          status => AggregateStatus,
                          metrics => restructure_map(AggregateMetrics)
@@ -310,7 +341,8 @@ lookup_from_all_nodes(ResourceId) ->
                   }
          end;
         {error, ErrL} ->
-            {error, error_msg('INTERNAL_ERROR', ErrL)}
+            {400, #{code => <<"BAD_REQUEST">>,
+                    message => bin_t(io_lib:format("~p", [ErrL]))}}
     end.
 
 aggregate_status([]) -> empty_metrics_and_status;
@@ -369,9 +401,6 @@ restructure_map(#{counters := #{failed := Failed, matched := Match, success := S
 restructure_map(Error) ->
      Error.
 
-error_msg(Code, Msg) ->
-              #{code => Code, message => bin_t(io_lib:format("~p", [Msg]))}.
-
 bin_t(S) when is_list(S) ->
     list_to_binary(S).
 
@@ -507,3 +536,28 @@ authz_sources_types(Type) ->
     emqx_authz_api_schema:authz_sources_types(Type).
 
 bin(Term) -> erlang:iolist_to_binary(io_lib:format("~p", [Term])).
+
+status_metrics_example() ->
+    #{ metrics => #{  matched => 0,
+                      success => 0,
+                      failed => 0,
+                      rate => 0.0,
+                      rate_last5m => 0.0,
+                      rate_max => 0.0
+                   },
+       node_metrics => [ #{node => node(),
+                           metrics => #{  matched => 0,
+                                          success => 0,
+                                          failed => 0,
+                                          rate => 0.0,
+                                          rate_last5m => 0.0,
+                                          rate_max => 0.0
+                                       }
+                          }
+                       ],
+       status => connected,
+       node_status => [ #{node => node(),
+                          status => connected
+                         }
+                      ]
+     }.

+ 30 - 28
apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl

@@ -176,32 +176,32 @@ t_api(_) ->
                            [?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5, ?SOURCE6]),
     {ok, 204, _} = request(post, uri(["authorization", "sources"]), ?SOURCE1),
 
-    %% Snd = fun ({_, Val}) -> Val end,
-    %% LookupVal = fun LookupV(List, RestJson) ->
-    %%                     case List of
-    %%                         [Name] -> Snd(lists:keyfind(Name, 1, RestJson));
-    %%                         [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson)))
-    %%                     end
-    %%             end,
-    %% EqualFun = fun (RList) ->
-    %%                fun ({M, V}) ->
-    %%                    ?assertEqual(V,
-    %%                                 LookupVal([<<"metrics">>, M],
-    %%                                          RList)
-    %%                                )
-    %%                end
-    %%            end,
-    %% AssertFun =
-    %%     fun (ResultJson) ->
-    %%         {ok, RList} = emqx_json:safe_decode(ResultJson),
-    %%         MetricsList = [{<<"failed">>, 0},
-    %%                        {<<"matched">>, 0},
-    %%                        {<<"rate">>, 0.0},
-    %%                        {<<"rate_last5m">>, 0.0},
-    %%                        {<<"rate_max">>, 0.0},
-    %%                        {<<"success">>, 0}],
-    %%         lists:map(EqualFun(RList), MetricsList)
-    %%     end,
+    Snd = fun ({_, Val}) -> Val end,
+    LookupVal = fun LookupV(List, RestJson) ->
+                        case List of
+                            [Name] -> Snd(lists:keyfind(Name, 1, RestJson));
+                            [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson)))
+                        end
+                end,
+    EqualFun = fun (RList) ->
+                   fun ({M, V}) ->
+                       ?assertEqual(V,
+                                    LookupVal([<<"metrics">>, M],
+                                             RList)
+                                   )
+                   end
+               end,
+    AssertFun =
+        fun (ResultJson) ->
+            {ok, RList} = emqx_json:safe_decode(ResultJson),
+            MetricsList = [{<<"failed">>, 0},
+                           {<<"matched">>, 0},
+                           {<<"rate">>, 0.0},
+                           {<<"rate_last5m">>, 0.0},
+                           {<<"rate_max">>, 0.0},
+                           {<<"success">>, 0}],
+            lists:map(EqualFun(RList), MetricsList)
+        end,
 
     {ok, 200, Result2} = request(get, uri(["authorization", "sources"]), []),
     Sources = get_sources(Result2),
@@ -238,7 +238,8 @@ t_api(_) ->
                                          <<"verify">> => <<"verify_none">>
                                         }}),
     {ok, 200, Result4} = request(get, uri(["authorization", "sources", "mongodb"]), []),
-    %% AssertFun(Result4),
+    {ok, 200, Status4} = request(get, uri(["authorization", "sources", "mongodb", "status"]), []),
+    AssertFun(Status4),
     ?assertMatch(#{<<"type">> := <<"mongodb">>,
                    <<"ssl">> := #{<<"enable">> := <<"true">>,
                                   <<"cacertfile">> := ?MATCH_CERT,
@@ -261,7 +262,8 @@ t_api(_) ->
                                          <<"verify">> => <<"verify_none">>
                                         }}),
     {ok, 200, Result5} = request(get, uri(["authorization", "sources", "mongodb"]), []),
-    %% AssertFun(Result5),
+    {ok, 200, Status5} = request(get, uri(["authorization", "sources", "mongodb", "status"]), []),
+    AssertFun(Status5),
     ?assertMatch(#{<<"type">> := <<"mongodb">>,
                    <<"ssl">> := #{<<"enable">> := <<"true">>,
                                   <<"cacertfile">> := ?MATCH_CERT,