Просмотр исходного кода

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

 fix(bridge_v2 API): optional cascading delete operation
Zaiming (Stone) Shi 2 лет назад
Родитель
Сommit
34ec7375ba

+ 24 - 1
apps/emqx_bridge/src/emqx_bridge_v2.erl

@@ -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
 %%--------------------------------------------------------------------

+ 36 - 8
apps/emqx_bridge/src/emqx_bridge_v2_api.erl

@@ -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} ->

+ 69 - 1
apps/emqx_bridge/test/emqx_bridge_v2_api_SUITE.erl

@@ -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}],

+ 6 - 0
rel/i18n/emqx_bridge_v2_api.hocon

@@ -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'."""