Merge pull request #11862 from kjellwinblad/kjell/shared_con/del_rules/EMQX-11293

fix(bridge_v2 API): optional cascading delete operation
This commit is contained in:
Zaiming (Stone) Shi 2023-11-02 10:28:43 +01:00 committed by GitHub
commit 34ec7375ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 135 additions and 10 deletions

View File

@ -38,7 +38,11 @@
list/0,
lookup/2,
create/3,
remove/2
remove/2,
%% The following is the remove function that is called by the HTTP API
%% It also checks for rule action dependencies and optionally removes
%% them
check_deps_and_remove/3
]).
%% Operations
@ -231,6 +235,25 @@ remove(BridgeType, BridgeName) ->
{error, Reason} -> {error, Reason}
end.
check_deps_and_remove(BridgeType, BridgeName, AlsoDeleteActions) ->
AlsoDelete =
case AlsoDeleteActions of
true -> [rule_actions];
false -> []
end,
case
emqx_bridge_lib:maybe_withdraw_rule_action(
BridgeType,
BridgeName,
AlsoDelete
)
of
ok ->
remove(BridgeType, BridgeName);
{error, Reason} ->
{error, Reason}
end.
%%--------------------------------------------------------------------
%% Helpers for CRUD API
%%--------------------------------------------------------------------

View File

@ -123,6 +123,18 @@ param_path_id() ->
}
)}.
param_qs_delete_cascade() ->
{also_delete_dep_actions,
mk(
boolean(),
#{
in => query,
required => false,
default => false,
desc => ?DESC("desc_qs_also_delete_dep_actions")
}
)}.
param_path_operation_cluster() ->
{operation,
mk(
@ -231,7 +243,7 @@ schema("/bridges_v2/:id") ->
tags => [<<"bridges_v2">>],
summary => <<"Delete bridge">>,
description => ?DESC("desc_api5"),
parameters => [param_path_id()],
parameters => [param_path_id(), param_qs_delete_cascade()],
responses => #{
204 => <<"Bridge deleted">>,
400 => error_schema(
@ -365,19 +377,35 @@ schema("/bridges_v2_probe") ->
?BRIDGE_NOT_FOUND(BridgeType, BridgeName)
end
);
'/bridges_v2/:id'(delete, #{bindings := #{id := Id}}) ->
'/bridges_v2/:id'(delete, #{bindings := #{id := Id}, query_string := Qs}) ->
?TRY_PARSE_ID(
Id,
case emqx_bridge_v2:lookup(BridgeType, BridgeName) of
{ok, _} ->
case emqx_bridge_v2:remove(BridgeType, BridgeName) of
AlsoDeleteActions =
case maps:get(<<"also_delete_dep_actions">>, Qs, <<"false">>) of
<<"true">> -> true;
true -> true;
_ -> false
end,
case
emqx_bridge_v2:check_deps_and_remove(BridgeType, BridgeName, AlsoDeleteActions)
of
ok ->
?NO_CONTENT;
{error, {active_channels, Channels}} ->
?BAD_REQUEST(
{<<"Cannot delete bridge while there are active channels defined for this bridge">>,
Channels}
);
{error, #{
reason := rules_depending_on_this_bridge,
rule_ids := RuleIds
}} ->
RuleIdLists = [binary_to_list(iolist_to_binary(X)) || X <- RuleIds],
RulesStr = string:join(RuleIdLists, ", "),
Msg = io_lib:format(
"Cannot delete bridge while active rules are depending on it: ~s\n"
"Append ?also_delete_dep_actions=true to the request URL to delete "
"rule actions that depend on this bridge as well.",
[RulesStr]
),
?BAD_REQUEST(iolist_to_binary(Msg));
{error, timeout} ->
?SERVICE_UNAVAILABLE(<<"request timeout">>);
{error, Reason} ->

View File

@ -147,7 +147,8 @@
emqx,
emqx_auth,
emqx_management,
{emqx_bridge, "bridges_v2 {}"}
{emqx_bridge, "bridges_v2 {}"},
{emqx_rule_engine, "rule_engine { rules {} }"}
]).
-define(APPSPEC_DASHBOARD,
@ -667,6 +668,73 @@ t_bridges_probe(Config) ->
),
ok.
t_cascade_delete_actions(Config) ->
%% assert we there's no bridges at first
{ok, 200, []} = request_json(get, uri([?ROOT]), Config),
%% then we add a a bridge, using POST
%% POST /bridges_v2/ will create a bridge
BridgeID = emqx_bridge_resource:bridge_id(?BRIDGE_TYPE, ?BRIDGE_NAME),
{ok, 201, _} = request(
post,
uri([?ROOT]),
?KAFKA_BRIDGE(?BRIDGE_NAME),
Config
),
{ok, 201, #{<<"id">> := RuleId}} = request_json(
post,
uri(["rules"]),
#{
<<"name">> => <<"t_http_crud_apis">>,
<<"enable">> => true,
<<"actions">> => [BridgeID],
<<"sql">> => <<"SELECT * from \"t\"">>
},
Config
),
%% delete the bridge will also delete the actions from the rules
{ok, 204, _} = request(
delete,
uri([?ROOT, BridgeID]) ++ "?also_delete_dep_actions=true",
Config
),
{ok, 200, []} = request_json(get, uri([?ROOT]), Config),
?assertMatch(
{ok, 200, #{<<"actions">> := []}},
request_json(get, uri(["rules", RuleId]), Config)
),
{ok, 204, <<>>} = request(delete, uri(["rules", RuleId]), Config),
{ok, 201, _} = request(
post,
uri([?ROOT]),
?KAFKA_BRIDGE(?BRIDGE_NAME),
Config
),
{ok, 201, _} = request(
post,
uri(["rules"]),
#{
<<"name">> => <<"t_http_crud_apis">>,
<<"enable">> => true,
<<"actions">> => [BridgeID],
<<"sql">> => <<"SELECT * from \"t\"">>
},
Config
),
{ok, 400, _} = request(
delete,
uri([?ROOT, BridgeID]),
Config
),
{ok, 200, [_]} = request_json(get, uri([?ROOT]), Config),
%% Cleanup
{ok, 204, _} = request(
delete,
uri([?ROOT, BridgeID]) ++ "?also_delete_dep_actions=true",
Config
),
{ok, 200, []} = request_json(get, uri([?ROOT]), Config).
%%% helpers
listen_on_random_port() ->
SockOpts = [binary, {active, false}, {packet, raw}, {reuseaddr, true}, {backlog, 1000}],

View File

@ -79,6 +79,12 @@ desc_param_path_id.desc:
desc_param_path_id.label:
"""Bridge ID"""
desc_qs_also_delete_dep_actions.desc:
"""Whether to cascade delete dependent actions."""
desc_qs_also_delete_dep_actions.label:
"""Cascade delete dependent actions?"""
desc_param_path_node.desc:
"""The node name, e.g. 'emqx@127.0.0.1'."""