diff --git a/apps/emqx_management/src/emqx_mgmt_api_routes.erl b/apps/emqx_management/src/emqx_mgmt_api_routes.erl index 00cb7bb90..d40a26ecd 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_routes.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_routes.erl @@ -18,30 +18,108 @@ -include_lib("emqx/include/emqx.hrl"). --rest_api(#{name => list_routes, - method => 'GET', - path => "/routes/", - func => list, - descr => "List routes"}). +%% API +-behavior(minirest_api). --rest_api(#{name => lookup_routes, - method => 'GET', - path => "/routes/:bin:topic", - func => lookup, - descr => "Lookup routes to a topic"}). +-export([api_spec/0]). --export([ list/2 - , lookup/2 - ]). +-export([ routes/2 + , route/2]). -list(Bindings, Params) when map_size(Bindings) == 0 -> - emqx_mgmt:return({ok, emqx_mgmt_api:paginate(emqx_route, Params, fun format/1)}). +-define(TOPIC_NOT_FOUND, 'TOPIC_NOT_FOUND'). -lookup(#{topic := Topic}, _Params) -> - Topic1 = emqx_mgmt_util:urldecode(Topic), - emqx_mgmt:return({ok, [format(R) || R <- emqx_mgmt:lookup_routes(Topic1)]}). +api_spec() -> + { + [routes_api(), route_api()], + [route_schema()] + }. + +route_schema() -> + #{ + route => #{ + type => object, + properties => #{ + topic => #{ + type => string}, + node => #{ + type => string, + example => node()}}}}. + +routes_api() -> + Metadata = #{ + get => #{ + description => "EMQ X routes", + parameters => [ + #{ + name => page, + in => query, + description => <<"Page">>, + schema => #{type => integer}, + default => 1 + }, + #{ + name => limit, + in => query, + description => <<"Page size">>, + schema => #{type => integer}, + default => emqx_mgmt:max_row_limit() + }], + responses => #{ + <<"200">> => + emqx_mgmt_util:response_array_schema("List route info", <<"route">>)}}}, + {"/routes", Metadata, routes}. + +route_api() -> + Metadata = #{ + get => #{ + description => "EMQ X routes", + parameters => [#{ + name => topic, + in => path, + required => true, + description => <<"topic">>, + schema => #{type => string} + }], + responses => #{ + <<"200">> => + emqx_mgmt_util:response_schema(<<"Route info">>, <<"route">>), + <<"404">> => + emqx_mgmt_util:not_found_schema(<<"Topic not found">>, [?TOPIC_NOT_FOUND]) + }}}, + {"/routes/:topic", Metadata, route}. + +%%%============================================================================================== +%% parameters trans +routes(get, Request) -> + Params = cowboy_req:parse_qs(Request), + list(Params). + +route(get, Request) -> + Topic = cowboy_req:binding(topic, Request), + lookup(#{topic => Topic}). + +%%%============================================================================================== +%% api apply +list(Params) -> + Data = emqx_mgmt_api:paginate(emqx_route, Params, fun format/1), + Response = emqx_json:encode(Data), + {200, Response}. + +lookup(#{topic := Topic}) -> + case emqx_mgmt:lookup_routes(Topic) of + [] -> + NotFound = #{code => ?TOPIC_NOT_FOUND, reason => <<"Topic not found">>}, + Response = emqx_json:encode(NotFound), + {404, Response}; + [Route] -> + Data = format(Route), + Response = emqx_json:encode(Data), + {200, Response} + end. + +%%%============================================================================================== +%% internal format(#route{topic = Topic, dest = {_, Node}}) -> #{topic => Topic, node => Node}; format(#route{topic = Topic, dest = Node}) -> #{topic => Topic, node => Node}. - diff --git a/apps/emqx_management/test/emqx_mgmt_routes_api_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_routes_api_SUITE.erl new file mode 100644 index 000000000..1756f6ff5 --- /dev/null +++ b/apps/emqx_management/test/emqx_mgmt_routes_api_SUITE.erl @@ -0,0 +1,65 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_mgmt_routes_api_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> + emqx_ct:all(?MODULE). + +init_per_suite(Config) -> + ekka_mnesia:start(), + emqx_mgmt_auth:mnesia(boot), + emqx_ct_helpers:start_apps([emqx_management], fun set_special_configs/1), + Config. + +end_per_suite(_) -> + emqx_ct_helpers:stop_apps([emqx_management]). + +set_special_configs(emqx_management) -> + emqx_config:put([emqx_management], #{listeners => [#{protocol => http, port => 8081}], + applications =>[#{id => "admin", secret => "public"}]}), + ok; +set_special_configs(_App) -> + ok. + +t_nodes_api(_) -> + Topic = <<"test_topic">>, + {ok, Client} = emqtt:start_link(#{username => <<"routes_username">>, clientid => <<"routes_cid">>}), + {ok, _} = emqtt:connect(Client), + {ok, _, _} = emqtt:subscribe(Client, Topic), + + Path = emqx_mgmt_api_test_util:api_path(["routes"]), + {ok, Response} = emqx_mgmt_api_test_util:request_api(get, Path), + RoutesData = emqx_json:decode(Response, [return_maps]), + Meta = maps:get(<<"meta">>, RoutesData), + ?assertEqual(1, maps:get(<<"page">>, Meta)), + ?assertEqual(emqx_mgmt:max_row_limit(), maps:get(<<"limit">>, Meta)), + ?assertEqual(1, maps:get(<<"count">>, Meta)), + Data = maps:get(<<"data">>, RoutesData), + Route = erlang:hd(Data), + ?assertEqual(Topic, maps:get(<<"topic">>, Route)), + ?assertEqual(atom_to_binary(node(), utf8), maps:get(<<"node">>, Route)), + + %% get routes/:topic + RoutePath = emqx_mgmt_api_test_util:api_path(["routes", Topic]), + {ok, RouteResponse} = emqx_mgmt_api_test_util:request_api(get, RoutePath), + RouteData = emqx_json:decode(RouteResponse, [return_maps]), + ?assertEqual(Topic, maps:get(<<"topic">>, RouteData)), + ?assertEqual(atom_to_binary(node(), utf8), maps:get(<<"node">>, RouteData)). \ No newline at end of file