fix(monitor): fix return value on badrpc

If the rpc in `emqx_dashboard_monitor_api:get_collect/1` fails, an
empty map is return.  But the current function expects a 4-tuple,
which results in a 500 error returned on such occasions.

```
curl -su admin:public localhost:8888/api/v5/monitor/current | jq .
{
  "code": "INTERNAL_ERROR",
  "message": "error, function_clause, [{emqx_dashboard_monitor_api,format_current_metrics,[[#{},{0,0,0,0},{0,0,0,0}],{0,0,0,0}],[{file,\"emqx_dashboard_monitor_api.erl\"},{line,179}]},{emqx_dashboard_monitor_api,current_counters,2,[{file,\"emqx_dashboard_monitor_api.erl\"},{line,167}]},{minirest_handler,apply_callback,3,[{file,\"minirest_handler.erl\"},{line,112}]},{minirest_handler,init,2,[{file,\"minirest_handler.erl\"},{line,38}]},{cowboy_handler,execute,2,[{file,\"cowboy_handler.erl\"},{line,41}]},{cowboy_stream_h,execute,3,[{file,\"cowboy_stream_h.erl\"},{line,318}]},{cowboy_stream_h,request_process,3,[{file,\"cowboy_stream_h.erl\"},{line,302}]},{proc_lib,init_p_do_apply,3,[{file,\"proc_lib.erl\"},{line,226}]}]"
}
```
This commit is contained in:
Thales Macedo Garitezi 2021-12-14 17:56:32 -03:00
parent 0300403fc0
commit 95de2d3467
No known key found for this signature in database
GPG Key ID: DD279F8152A9B6DD
3 changed files with 126 additions and 3 deletions

View File

@ -248,7 +248,7 @@ init_load(SchemaMod) ->
init_load(SchemaMod, ConfFiles).
%% @doc Initial load of the given config files.
%% NOTE: The order of the files is significant, configs from files orderd
%% NOTE: The order of the files is significant, configs from files ordered
%% in the rear of the list overrides prior values.
-spec init_load(module(), [string()] | binary() | hocon:config()) -> ok.
init_load(SchemaMod, Conf) when is_list(Conf) orelse is_binary(Conf) ->

View File

@ -29,6 +29,8 @@
, sent
, dropped]).
-define(EMPTY_COLLECTION, {0, 0, 0, 0}).
api_spec() ->
{[ monitor_api()
, monitor_nodes_api()
@ -175,7 +177,7 @@ current_counters(get, _Params) ->
{200, Response}.
format_current_metrics(Collects) ->
format_current_metrics(Collects, {0,0,0,0}).
format_current_metrics(Collects, ?EMPTY_COLLECTION).
format_current_metrics([], Acc) ->
Acc;
format_current_metrics([{Received, Sent, Sub, Conn} | Collects],
@ -217,7 +219,7 @@ get_collect(Node) when Node =:= node() ->
emqx_dashboard_collection:get_collect();
get_collect(Node) ->
case rpc:call(Node, emqx_dashboard_collection, get_collect, []) of
{badrpc, _Reason} -> #{};
{badrpc, _Reason} -> ?EMPTY_COLLECTION;
Res -> Res
end.

View File

@ -0,0 +1,121 @@
%%--------------------------------------------------------------------
%% 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_dashboard_monitor_api_SUITE).
-compile(nowarn_export_all).
-compile(export_all).
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx.hrl").
-include("emqx_dashboard.hrl").
all() ->
emqx_common_test_helpers:all(?MODULE).
init_per_testcase(t_badrpc_collect, Config) ->
Cluster = cluster_specs(2),
Apps = [emqx_modules, emqx_dashboard],
Nodes = [N1, N2] = lists:map(fun(Spec) -> start_slave(Spec, Apps) end, Cluster),
%% form the cluster
ok = rpc:call(N2, mria, join, [N1]),
%% Wait until all nodes are healthy:
[rpc:call(Node, mria_rlog, wait_for_shards, [[?DASHBOARD_SHARD], 5000])
|| Node <- Nodes],
[ {nodes, Nodes}
, {apps, Apps}
| Config];
init_per_testcase(_, Config) ->
Config.
end_per_testcase(t_badrpc_collect, Config) ->
Apps = ?config(apps, Config),
Nodes = ?config(nodes, Config),
lists:foreach(fun(Node) -> stop_slave(Node, Apps) end, Nodes),
ok;
end_per_testcase(_, _Config) ->
ok.
t_badrpc_collect(Config) ->
[N1, N2] = ?config(nodes, Config),
%% simulate badrpc on one node
ok = rpc:call(N2, meck, new, [emqx_dashboard_collection, [no_history, no_link]]),
%% we don't mock the `emqx_dashboard_collection:get_collect/0' to
%% provoke the `badrpc' error.
?assertMatch(
{200, #{nodes := 2}},
rpc:call(N1, emqx_dashboard_monitor_api, current_counters, [get, #{}])),
ok = rpc:call(N2, meck, unload, [emqx_dashboard_collection]),
ok.
%%------------------------------------------------------------------------------
%% Internal functions
%%------------------------------------------------------------------------------
cluster_specs(NumNodes) ->
BaseGenRpcPort = 9000,
Specs0 = [#{ name => node_name(N)
, num => N
}
|| N <- lists:seq(1, NumNodes)],
GenRpcPorts = maps:from_list([{node_id(Name), {tcp, BaseGenRpcPort + N}}
|| #{name := Name, num := N} <- Specs0]),
[ Spec#{env => [ {gen_rpc, tcp_server_port, BaseGenRpcPort + N}
, {gen_rpc, client_config_per_node, {internal, GenRpcPorts}}
]}
|| Spec = #{num := N} <- Specs0].
node_name(N) ->
list_to_atom("n" ++ integer_to_list(N)).
node_id(Name) ->
list_to_atom(lists:concat([Name, "@", host()])).
start_slave(Spec = #{ name := Name}, Apps) ->
CommonBeamOpts = "+S 1:1 ", % We want VMs to only occupy a single core
{ok, Node} = slave:start_link(host(), Name, CommonBeamOpts ++ ebin_path()),
setup_node(Node, Spec, Apps),
Node.
stop_slave(Node, Apps) ->
ok = rpc:call(Node, emqx_common_test_helpers, start_apps, [Apps]),
slave:stop(Node).
host() ->
[_, Host] = string:tokens(atom_to_list(node()), "@"), Host.
ebin_path() ->
string:join(["-pa" | lists:filter(fun is_lib/1, code:get_path())], " ").
is_lib(Path) ->
string:prefix(Path, code:lib_dir()) =:= nomatch.
setenv(Node, Env) ->
[rpc:call(Node, application, set_env, [App, Key, Val]) || {App, Key, Val} <- Env].
setup_node(Node, _Spec = #{env := Env}, Apps) ->
%% load these before starting ekka and such
[rpc:call(Node, application, load, [App]) || App <- [gen_rpc, emqx_conf, emqx]],
setenv(Node, Env),
EnvHandler =
fun(emqx) ->
application:set_env(emqx, boot_modules, [router, broker]);
(_) ->
ok
end,
ok = rpc:call(Node, emqx_common_test_helpers, start_apps, [Apps, EnvHandler]),
ok.