diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index 2d214be66..c0fabe219 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/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 => #{ diff --git a/apps/emqx_authn/src/emqx_authn_schema.erl b/apps/emqx_authn/src/emqx_authn_schema.erl index 7a30e0b72..d27d3fed3 100644 --- a/apps/emqx_authn/src/emqx_authn_schema.erl +++ b/apps/emqx_authn/src/emqx_authn_schema.erl @@ -17,6 +17,7 @@ -module(emqx_authn_schema). -include_lib("typerefl/include/types.hrl"). +-import(hoconsc, [mk/2, ref/2]). -export([ common_fields/0 , roots/0 @@ -29,7 +30,6 @@ roots() -> []. -fields(_) -> []. common_fields() -> [ {enable, fun enable/1} @@ -59,3 +59,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"})}. diff --git a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl b/apps/emqx_authn/test/emqx_authn_api_SUITE.erl index dd58a220a..039eff808 100644 --- a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl +++ b/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"])), diff --git a/apps/emqx_authz/src/emqx_authz_api_sources.erl b/apps/emqx_authz/src/emqx_authz_api_sources.erl index 876cbc813..0b4b98bd6 100644 --- a/apps/emqx_authz/src/emqx_authz_api_sources.erl +++ b/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 + } + ] + }. diff --git a/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl b/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl index 5476110b0..835d8ab48 100644 --- a/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl +++ b/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,