fix(emqx_connector): don't crash in API on delete with active channels
This commit is contained in:
parent
b42d4e5ab0
commit
1f1d9e58c6
|
@ -372,7 +372,7 @@ schema("/connectors_probe") ->
|
||||||
case emqx_connector:remove(ConnectorType, ConnectorName) of
|
case emqx_connector:remove(ConnectorType, ConnectorName) of
|
||||||
ok ->
|
ok ->
|
||||||
?NO_CONTENT;
|
?NO_CONTENT;
|
||||||
{error, {active_channels, Channels}} ->
|
{error, {post_config_update, _HandlerMod, {active_channels, Channels}}} ->
|
||||||
?BAD_REQUEST(
|
?BAD_REQUEST(
|
||||||
{<<"Cannot delete connector while there are active channels defined for this connector">>,
|
{<<"Cannot delete connector while there are active channels defined for this connector">>,
|
||||||
Channels}
|
Channels}
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
-include_lib("snabbkaffe/include/test_macros.hrl").
|
-include_lib("snabbkaffe/include/test_macros.hrl").
|
||||||
|
|
||||||
-define(CONNECTOR_NAME, (atom_to_binary(?FUNCTION_NAME))).
|
-define(CONNECTOR_NAME, (atom_to_binary(?FUNCTION_NAME))).
|
||||||
-define(CONNECTOR(NAME, TYPE), #{
|
-define(RESOURCE(NAME, TYPE), #{
|
||||||
%<<"ssl">> => #{<<"enable">> => false},
|
%<<"ssl">> => #{<<"enable">> => false},
|
||||||
<<"type">> => TYPE,
|
<<"type">> => TYPE,
|
||||||
<<"name">> => NAME
|
<<"name">> => NAME
|
||||||
|
@ -52,12 +52,57 @@
|
||||||
-define(KAFKA_CONNECTOR_BASE, ?KAFKA_CONNECTOR_BASE(?KAFKA_BOOTSTRAP_HOST)).
|
-define(KAFKA_CONNECTOR_BASE, ?KAFKA_CONNECTOR_BASE(?KAFKA_BOOTSTRAP_HOST)).
|
||||||
-define(KAFKA_CONNECTOR(Name, BootstrapHosts),
|
-define(KAFKA_CONNECTOR(Name, BootstrapHosts),
|
||||||
maps:merge(
|
maps:merge(
|
||||||
?CONNECTOR(Name, ?CONNECTOR_TYPE),
|
?RESOURCE(Name, ?CONNECTOR_TYPE),
|
||||||
?KAFKA_CONNECTOR_BASE(BootstrapHosts)
|
?KAFKA_CONNECTOR_BASE(BootstrapHosts)
|
||||||
)
|
)
|
||||||
).
|
).
|
||||||
-define(KAFKA_CONNECTOR(Name), ?KAFKA_CONNECTOR(Name, ?KAFKA_BOOTSTRAP_HOST)).
|
-define(KAFKA_CONNECTOR(Name), ?KAFKA_CONNECTOR(Name, ?KAFKA_BOOTSTRAP_HOST)).
|
||||||
|
|
||||||
|
-define(BRIDGE_NAME, (atom_to_binary(?FUNCTION_NAME))).
|
||||||
|
-define(BRIDGE_TYPE_STR, "kafka_producer").
|
||||||
|
-define(BRIDGE_TYPE, <<?BRIDGE_TYPE_STR>>).
|
||||||
|
-define(KAFKA_BRIDGE(Name, Connector), ?RESOURCE(Name, ?BRIDGE_TYPE)#{
|
||||||
|
<<"enable">> => true,
|
||||||
|
<<"connector">> => Connector,
|
||||||
|
<<"kafka">> => #{
|
||||||
|
<<"buffer">> => #{
|
||||||
|
<<"memory_overload_protection">> => true,
|
||||||
|
<<"mode">> => <<"hybrid">>,
|
||||||
|
<<"per_partition_limit">> => <<"2GB">>,
|
||||||
|
<<"segment_bytes">> => <<"100MB">>
|
||||||
|
},
|
||||||
|
<<"compression">> => <<"no_compression">>,
|
||||||
|
<<"kafka_ext_headers">> => [
|
||||||
|
#{
|
||||||
|
<<"kafka_ext_header_key">> => <<"clientid">>,
|
||||||
|
<<"kafka_ext_header_value">> => <<"${clientid}">>
|
||||||
|
},
|
||||||
|
#{
|
||||||
|
<<"kafka_ext_header_key">> => <<"topic">>,
|
||||||
|
<<"kafka_ext_header_value">> => <<"${topic}">>
|
||||||
|
}
|
||||||
|
],
|
||||||
|
<<"kafka_header_value_encode_mode">> => <<"none">>,
|
||||||
|
<<"kafka_headers">> => <<"${pub_props}">>,
|
||||||
|
<<"max_batch_bytes">> => <<"896KB">>,
|
||||||
|
<<"max_inflight">> => 10,
|
||||||
|
<<"message">> => #{
|
||||||
|
<<"key">> => <<"${.clientid}">>,
|
||||||
|
<<"timestamp">> => <<"${.timestamp}">>,
|
||||||
|
<<"value">> => <<"${.}">>
|
||||||
|
},
|
||||||
|
<<"partition_count_refresh_interval">> => <<"60s">>,
|
||||||
|
<<"partition_strategy">> => <<"random">>,
|
||||||
|
<<"required_acks">> => <<"all_isr">>,
|
||||||
|
<<"topic">> => <<"kafka-topic">>
|
||||||
|
},
|
||||||
|
<<"local_topic">> => <<"mqtt/local/topic">>,
|
||||||
|
<<"resource_opts">> => #{
|
||||||
|
<<"health_check_interval">> => <<"32s">>
|
||||||
|
}
|
||||||
|
}).
|
||||||
|
-define(KAFKA_BRIDGE(Name), ?KAFKA_BRIDGE(Name, ?CONNECTOR_NAME)).
|
||||||
|
|
||||||
%% -define(CONNECTOR_TYPE_MQTT, <<"mqtt">>).
|
%% -define(CONNECTOR_TYPE_MQTT, <<"mqtt">>).
|
||||||
%% -define(MQTT_CONNECTOR(SERVER, NAME), ?CONNECTOR(NAME, ?CONNECTOR_TYPE_MQTT)#{
|
%% -define(MQTT_CONNECTOR(SERVER, NAME), ?CONNECTOR(NAME, ?CONNECTOR_TYPE_MQTT)#{
|
||||||
%% <<"server">> => SERVER,
|
%% <<"server">> => SERVER,
|
||||||
|
@ -105,7 +150,8 @@
|
||||||
emqx,
|
emqx,
|
||||||
emqx_auth,
|
emqx_auth,
|
||||||
emqx_management,
|
emqx_management,
|
||||||
{emqx_connector, "connectors {}"}
|
{emqx_connector, "connectors {}"},
|
||||||
|
{emqx_bridge, "actions {}"}
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(APPSPEC_DASHBOARD,
|
-define(APPSPEC_DASHBOARD,
|
||||||
|
@ -128,7 +174,8 @@ all() ->
|
||||||
groups() ->
|
groups() ->
|
||||||
AllTCs = emqx_common_test_helpers:all(?MODULE),
|
AllTCs = emqx_common_test_helpers:all(?MODULE),
|
||||||
SingleOnlyTests = [
|
SingleOnlyTests = [
|
||||||
t_connectors_probe
|
t_connectors_probe,
|
||||||
|
t_fail_delete_with_action
|
||||||
],
|
],
|
||||||
ClusterLaterJoinOnlyTCs = [
|
ClusterLaterJoinOnlyTCs = [
|
||||||
% t_cluster_later_join_metrics
|
% t_cluster_later_join_metrics
|
||||||
|
@ -187,29 +234,38 @@ end_per_group(_, Config) ->
|
||||||
emqx_cth_suite:stop(?config(group_apps, Config)),
|
emqx_cth_suite:stop(?config(group_apps, Config)),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
init_per_testcase(_TestCase, Config) ->
|
init_per_testcase(TestCase, Config) ->
|
||||||
case ?config(cluster_nodes, Config) of
|
case ?config(cluster_nodes, Config) of
|
||||||
undefined ->
|
undefined ->
|
||||||
init_mocks();
|
init_mocks(TestCase);
|
||||||
Nodes ->
|
Nodes ->
|
||||||
[erpc:call(Node, ?MODULE, init_mocks, []) || Node <- Nodes]
|
[erpc:call(Node, ?MODULE, init_mocks, [TestCase]) || Node <- Nodes]
|
||||||
end,
|
end,
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_testcase(_TestCase, Config) ->
|
end_per_testcase(TestCase, Config) ->
|
||||||
|
Node = ?config(node, Config),
|
||||||
|
ok = erpc:call(Node, ?MODULE, clear_resources, [TestCase]),
|
||||||
case ?config(cluster_nodes, Config) of
|
case ?config(cluster_nodes, Config) of
|
||||||
undefined ->
|
undefined ->
|
||||||
meck:unload();
|
meck:unload();
|
||||||
Nodes ->
|
Nodes ->
|
||||||
[erpc:call(Node, meck, unload, []) || Node <- Nodes]
|
[erpc:call(N, meck, unload, []) || N <- Nodes]
|
||||||
end,
|
end,
|
||||||
Node = ?config(node, Config),
|
|
||||||
ok = emqx_common_test_helpers:call_janitor(),
|
ok = emqx_common_test_helpers:call_janitor(),
|
||||||
ok = erpc:call(Node, fun clear_resources/0),
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
-define(CONNECTOR_IMPL, dummy_connector_impl).
|
-define(CONNECTOR_IMPL, dummy_connector_impl).
|
||||||
init_mocks() ->
|
init_mocks(t_fail_delete_with_action) ->
|
||||||
|
init_mocks(common),
|
||||||
|
meck:expect(?CONNECTOR_IMPL, on_add_channel, 4, {ok, connector_state}),
|
||||||
|
meck:expect(?CONNECTOR_IMPL, on_remove_channel, 3, {ok, connector_state}),
|
||||||
|
meck:expect(?CONNECTOR_IMPL, on_get_channel_status, 3, connected),
|
||||||
|
ok = meck:expect(?CONNECTOR_IMPL, on_get_channels, fun(ResId) ->
|
||||||
|
emqx_bridge_v2:get_channels_for_connector(ResId)
|
||||||
|
end),
|
||||||
|
ok;
|
||||||
|
init_mocks(_TestCase) ->
|
||||||
meck:new(emqx_connector_ee_schema, [passthrough, no_link]),
|
meck:new(emqx_connector_ee_schema, [passthrough, no_link]),
|
||||||
meck:expect(emqx_connector_ee_schema, resource_type, 1, ?CONNECTOR_IMPL),
|
meck:expect(emqx_connector_ee_schema, resource_type, 1, ?CONNECTOR_IMPL),
|
||||||
meck:new(?CONNECTOR_IMPL, [non_strict, no_link]),
|
meck:new(?CONNECTOR_IMPL, [non_strict, no_link]),
|
||||||
|
@ -235,7 +291,15 @@ init_mocks() ->
|
||||||
),
|
),
|
||||||
[?CONNECTOR_IMPL, emqx_connector_ee_schema].
|
[?CONNECTOR_IMPL, emqx_connector_ee_schema].
|
||||||
|
|
||||||
clear_resources() ->
|
clear_resources(t_fail_delete_with_action) ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(#{type := Type, name := Name}) ->
|
||||||
|
ok = emqx_bridge_v2:remove(Type, Name)
|
||||||
|
end,
|
||||||
|
emqx_bridge_v2:list()
|
||||||
|
),
|
||||||
|
clear_resources(common);
|
||||||
|
clear_resources(_) ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(#{type := Type, name := Name}) ->
|
fun(#{type := Type, name := Name}) ->
|
||||||
ok = emqx_connector:remove(Type, Name)
|
ok = emqx_connector:remove(Type, Name)
|
||||||
|
@ -646,7 +710,7 @@ t_connectors_probe(Config) ->
|
||||||
request_json(
|
request_json(
|
||||||
post,
|
post,
|
||||||
uri(["connectors_probe"]),
|
uri(["connectors_probe"]),
|
||||||
?CONNECTOR(<<"broken_connector">>, <<"unknown_type">>),
|
?RESOURCE(<<"broken_connector">>, <<"unknown_type">>),
|
||||||
Config
|
Config
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -674,6 +738,57 @@ t_create_with_bad_name(Config) ->
|
||||||
?assertMatch(#{<<"kind">> := <<"validation_error">>}, Msg),
|
?assertMatch(#{<<"kind">> := <<"validation_error">>}, Msg),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_fail_delete_with_action(Config) ->
|
||||||
|
Name = ?CONNECTOR_NAME,
|
||||||
|
?assertMatch(
|
||||||
|
{ok, 201, #{
|
||||||
|
<<"type">> := ?CONNECTOR_TYPE,
|
||||||
|
<<"name">> := Name,
|
||||||
|
<<"enable">> := true,
|
||||||
|
<<"status">> := <<"connected">>,
|
||||||
|
<<"node_status">> := [_ | _]
|
||||||
|
}},
|
||||||
|
request_json(
|
||||||
|
post,
|
||||||
|
uri(["connectors"]),
|
||||||
|
?KAFKA_CONNECTOR(Name),
|
||||||
|
Config
|
||||||
|
)
|
||||||
|
),
|
||||||
|
ConnectorID = emqx_connector_resource:connector_id(?CONNECTOR_TYPE, Name),
|
||||||
|
BridgeName = ?BRIDGE_NAME,
|
||||||
|
?assertMatch(
|
||||||
|
{ok, 201, #{
|
||||||
|
<<"type">> := ?BRIDGE_TYPE,
|
||||||
|
<<"name">> := BridgeName,
|
||||||
|
<<"enable">> := true,
|
||||||
|
<<"status">> := <<"connected">>,
|
||||||
|
<<"node_status">> := [_ | _],
|
||||||
|
<<"connector">> := Name,
|
||||||
|
<<"kafka">> := #{},
|
||||||
|
<<"local_topic">> := _,
|
||||||
|
<<"resource_opts">> := _
|
||||||
|
}},
|
||||||
|
request_json(
|
||||||
|
post,
|
||||||
|
uri(["actions"]),
|
||||||
|
?KAFKA_BRIDGE(?BRIDGE_NAME),
|
||||||
|
Config
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
%% delete the connector
|
||||||
|
?assertMatch(
|
||||||
|
{ok, 400, #{
|
||||||
|
<<"code">> := <<"BAD_REQUEST">>,
|
||||||
|
<<"message">> :=
|
||||||
|
<<"{<<\"Cannot delete connector while there are active channels",
|
||||||
|
" defined for this connector\">>,", _/binary>>
|
||||||
|
}},
|
||||||
|
request_json(delete, uri(["connectors", ConnectorID]), Config)
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
%%% helpers
|
%%% helpers
|
||||||
listen_on_random_port() ->
|
listen_on_random_port() ->
|
||||||
SockOpts = [binary, {active, false}, {packet, raw}, {reuseaddr, true}, {backlog, 1000}],
|
SockOpts = [binary, {active, false}, {packet, raw}, {reuseaddr, true}, {backlog, 1000}],
|
||||||
|
|
Loading…
Reference in New Issue