Merge pull request #11864 from thalesmg/test-enable-rule-check-deps-r53-20231101
fix(rule_engine): don't enable a rule that references non-existent bridge
This commit is contained in:
commit
953d483c24
|
@ -448,6 +448,21 @@ bridge_node_operation_http_api_v2(Name, Node0, Op0) ->
|
||||||
ct:pal("bridge node op ~p (http v2) result:\n ~p", [{Node, Op}, Res]),
|
ct:pal("bridge node op ~p (http v2) result:\n ~p", [{Node, Op}, Res]),
|
||||||
Res.
|
Res.
|
||||||
|
|
||||||
|
is_rule_enabled(RuleId) ->
|
||||||
|
{ok, #{enable := Enable}} = emqx_rule_engine:get_rule(RuleId),
|
||||||
|
Enable.
|
||||||
|
|
||||||
|
update_rule_http(RuleId, Params) ->
|
||||||
|
Path = emqx_mgmt_api_test_util:api_path(["rules", RuleId]),
|
||||||
|
ct:pal("update rule ~p:\n ~p", [RuleId, Params]),
|
||||||
|
Res = request(put, Path, Params),
|
||||||
|
ct:pal("update rule ~p result:\n ~p", [RuleId, Res]),
|
||||||
|
Res.
|
||||||
|
|
||||||
|
enable_rule_http(RuleId) ->
|
||||||
|
Params = #{<<"enable">> => true},
|
||||||
|
update_rule_http(RuleId, Params).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Test cases
|
%% Test cases
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
@ -703,3 +718,91 @@ t_scenario_1(_Config) ->
|
||||||
%% ?assertMatch({error, {{_, 404, _}, _, _}}, bridge_operation_http_api_v2(NameA, restart)),
|
%% ?assertMatch({error, {{_, 404, _}, _, _}}, bridge_operation_http_api_v2(NameA, restart)),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_scenario_2(Config) ->
|
||||||
|
%% ===================================================================================
|
||||||
|
%% Pre-conditions
|
||||||
|
%% ===================================================================================
|
||||||
|
?assertMatch({ok, {{_, 200, _}, _, []}}, list_bridges_http_api_v1()),
|
||||||
|
?assertMatch({ok, {{_, 200, _}, _, []}}, list_bridges_http_api_v2()),
|
||||||
|
%% created in the test case init
|
||||||
|
?assertMatch({ok, {{_, 200, _}, _, [#{}]}}, list_connectors_http()),
|
||||||
|
{ok, {{_, 200, _}, _, [#{<<"name">> := _PreexistentConnectorName}]}} = list_connectors_http(),
|
||||||
|
|
||||||
|
%% ===================================================================================
|
||||||
|
%% Try to create a rule referencing a non-existent bridge. It succeeds, but it's
|
||||||
|
%% implicitly disabled. Trying to update it later without creating the bridge should
|
||||||
|
%% keep it disabled.
|
||||||
|
%% ===================================================================================
|
||||||
|
BridgeName = <<"scenario2">>,
|
||||||
|
RuleTopic = <<"t/scenario2">>,
|
||||||
|
{ok, #{<<"id">> := RuleId0}} =
|
||||||
|
emqx_bridge_v2_testlib:create_rule_and_action_http(
|
||||||
|
bridge_type(),
|
||||||
|
RuleTopic,
|
||||||
|
[
|
||||||
|
{bridge_name, BridgeName}
|
||||||
|
| Config
|
||||||
|
],
|
||||||
|
#{overrides => #{enable => true}}
|
||||||
|
),
|
||||||
|
?assertNot(is_rule_enabled(RuleId0)),
|
||||||
|
?assertMatch({ok, {{_, 200, _}, _, _}}, enable_rule_http(RuleId0)),
|
||||||
|
?assertNot(is_rule_enabled(RuleId0)),
|
||||||
|
|
||||||
|
%% ===================================================================================
|
||||||
|
%% Now we create the bridge, and attempt to create a new enabled rule. It should
|
||||||
|
%% start enabled. Also, updating the previous rule to enable it should work now.
|
||||||
|
%% ===================================================================================
|
||||||
|
?assertMatch(
|
||||||
|
{ok, {{_, 201, _}, _, #{}}},
|
||||||
|
create_bridge_http_api_v1(#{name => BridgeName})
|
||||||
|
),
|
||||||
|
{ok, #{<<"id">> := RuleId1}} =
|
||||||
|
emqx_bridge_v2_testlib:create_rule_and_action_http(
|
||||||
|
bridge_type(),
|
||||||
|
RuleTopic,
|
||||||
|
[
|
||||||
|
{bridge_name, BridgeName}
|
||||||
|
| Config
|
||||||
|
],
|
||||||
|
#{overrides => #{enable => true}}
|
||||||
|
),
|
||||||
|
?assertNot(is_rule_enabled(RuleId0)),
|
||||||
|
?assert(is_rule_enabled(RuleId1)),
|
||||||
|
?assertMatch({ok, {{_, 200, _}, _, _}}, enable_rule_http(RuleId0)),
|
||||||
|
?assert(is_rule_enabled(RuleId0)),
|
||||||
|
|
||||||
|
%% ===================================================================================
|
||||||
|
%% Creating a rule with mixed existent/non-existent bridges should deny enabling it.
|
||||||
|
%% ===================================================================================
|
||||||
|
NonExistentBridgeName = <<"scenario2_not_created">>,
|
||||||
|
{ok, #{<<"id">> := RuleId2}} =
|
||||||
|
emqx_bridge_v2_testlib:create_rule_and_action_http(
|
||||||
|
bridge_type(),
|
||||||
|
RuleTopic,
|
||||||
|
[
|
||||||
|
{bridge_name, BridgeName}
|
||||||
|
| Config
|
||||||
|
],
|
||||||
|
#{
|
||||||
|
overrides => #{
|
||||||
|
enable => true,
|
||||||
|
actions => [
|
||||||
|
emqx_bridge_resource:bridge_id(
|
||||||
|
bridge_type(),
|
||||||
|
BridgeName
|
||||||
|
),
|
||||||
|
emqx_bridge_resource:bridge_id(
|
||||||
|
bridge_type(),
|
||||||
|
NonExistentBridgeName
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
?assertNot(is_rule_enabled(RuleId2)),
|
||||||
|
?assertMatch({ok, {{_, 200, _}, _, _}}, enable_rule_http(RuleId2)),
|
||||||
|
?assertNot(is_rule_enabled(RuleId2)),
|
||||||
|
|
||||||
|
ok.
|
||||||
|
|
|
@ -260,11 +260,13 @@ create_rule_and_action_http(BridgeType, RuleTopic, Config, Opts) ->
|
||||||
BridgeName = ?config(bridge_name, Config),
|
BridgeName = ?config(bridge_name, Config),
|
||||||
BridgeId = emqx_bridge_resource:bridge_id(BridgeType, BridgeName),
|
BridgeId = emqx_bridge_resource:bridge_id(BridgeType, BridgeName),
|
||||||
SQL = maps:get(sql, Opts, <<"SELECT * FROM \"", RuleTopic/binary, "\"">>),
|
SQL = maps:get(sql, Opts, <<"SELECT * FROM \"", RuleTopic/binary, "\"">>),
|
||||||
Params = #{
|
Params0 = #{
|
||||||
enable => true,
|
enable => true,
|
||||||
sql => SQL,
|
sql => SQL,
|
||||||
actions => [BridgeId]
|
actions => [BridgeId]
|
||||||
},
|
},
|
||||||
|
Overrides = maps:get(overrides, Opts, #{}),
|
||||||
|
Params = emqx_utils_maps:deep_merge(Params0, Overrides),
|
||||||
Path = emqx_mgmt_api_test_util:api_path(["rules"]),
|
Path = emqx_mgmt_api_test_util:api_path(["rules"]),
|
||||||
AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
|
AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
|
||||||
ct:pal("rule action params: ~p", [Params]),
|
ct:pal("rule action params: ~p", [Params]),
|
||||||
|
|
|
@ -460,12 +460,11 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
with_parsed_rule(Params = #{id := RuleId, sql := Sql, actions := Actions}, CreatedAt, Fun) ->
|
with_parsed_rule(Params = #{id := RuleId, sql := Sql, actions := Actions}, CreatedAt, Fun) ->
|
||||||
case emqx_rule_sqlparser:parse(Sql) of
|
case emqx_rule_sqlparser:parse(Sql) of
|
||||||
{ok, Select} ->
|
{ok, Select} ->
|
||||||
Rule = #{
|
Rule0 = #{
|
||||||
id => RuleId,
|
id => RuleId,
|
||||||
name => maps:get(name, Params, <<"">>),
|
name => maps:get(name, Params, <<"">>),
|
||||||
created_at => CreatedAt,
|
created_at => CreatedAt,
|
||||||
updated_at => now_ms(),
|
updated_at => now_ms(),
|
||||||
enable => maps:get(enable, Params, true),
|
|
||||||
sql => Sql,
|
sql => Sql,
|
||||||
actions => parse_actions(Actions),
|
actions => parse_actions(Actions),
|
||||||
description => maps:get(description, Params, ""),
|
description => maps:get(description, Params, ""),
|
||||||
|
@ -478,6 +477,21 @@ with_parsed_rule(Params = #{id := RuleId, sql := Sql, actions := Actions}, Creat
|
||||||
conditions => emqx_rule_sqlparser:select_where(Select)
|
conditions => emqx_rule_sqlparser:select_where(Select)
|
||||||
%% -- calculated fields end
|
%% -- calculated fields end
|
||||||
},
|
},
|
||||||
|
InputEnable = maps:get(enable, Params, true),
|
||||||
|
Enable =
|
||||||
|
case validate_bridge_existence_in_actions(Rule0) of
|
||||||
|
ok ->
|
||||||
|
InputEnable;
|
||||||
|
{error, NonExistentBridgeIDs} ->
|
||||||
|
?SLOG(error, #{
|
||||||
|
msg => "action_references_nonexistent_bridges",
|
||||||
|
rule_id => RuleId,
|
||||||
|
nonexistent_bridge_ids => NonExistentBridgeIDs,
|
||||||
|
hint => "this rule will be disabled"
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
end,
|
||||||
|
Rule = Rule0#{enable => Enable},
|
||||||
ok = Fun(Rule),
|
ok = Fun(Rule),
|
||||||
{ok, Rule};
|
{ok, Rule};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
|
@ -593,3 +607,42 @@ extra_functions_module() ->
|
||||||
set_extra_functions_module(Mod) ->
|
set_extra_functions_module(Mod) ->
|
||||||
persistent_term:put({?MODULE, extra_functions}, Mod),
|
persistent_term:put({?MODULE, extra_functions}, Mod),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
%% Checks whether the referenced bridges in actions all exist. If there are non-existent
|
||||||
|
%% ones, the rule shouldn't be allowed to be enabled.
|
||||||
|
%% The actions here are already parsed.
|
||||||
|
validate_bridge_existence_in_actions(#{actions := Actions, from := Froms} = _Rule) ->
|
||||||
|
BridgeIDs0 =
|
||||||
|
lists:map(
|
||||||
|
fun(BridgeID) ->
|
||||||
|
emqx_bridge_resource:parse_bridge_id(BridgeID, #{atom_name => false})
|
||||||
|
end,
|
||||||
|
get_referenced_hookpoints(Froms)
|
||||||
|
),
|
||||||
|
BridgeIDs1 =
|
||||||
|
lists:filtermap(
|
||||||
|
fun
|
||||||
|
({bridge_v2, Type, Name}) -> {true, {Type, Name}};
|
||||||
|
({bridge, Type, Name, _ResId}) -> {true, {Type, Name}};
|
||||||
|
(_) -> false
|
||||||
|
end,
|
||||||
|
Actions
|
||||||
|
),
|
||||||
|
NonExistentBridgeIDs =
|
||||||
|
lists:filter(
|
||||||
|
fun({Type, Name}) ->
|
||||||
|
try
|
||||||
|
case emqx_bridge:lookup(Type, Name) of
|
||||||
|
{ok, _} -> false;
|
||||||
|
{error, _} -> true
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
_:_ -> true
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
BridgeIDs0 ++ BridgeIDs1
|
||||||
|
),
|
||||||
|
case NonExistentBridgeIDs of
|
||||||
|
[] -> ok;
|
||||||
|
_ -> {error, #{nonexistent_bridge_ids => NonExistentBridgeIDs}}
|
||||||
|
end.
|
||||||
|
|
|
@ -251,6 +251,10 @@ init_per_testcase(t_events, Config) ->
|
||||||
),
|
),
|
||||||
?assertMatch(#{id := <<"rule:t_events">>}, Rule),
|
?assertMatch(#{id := <<"rule:t_events">>}, Rule),
|
||||||
[{hook_points_rules, Rule} | Config];
|
[{hook_points_rules, Rule} | Config];
|
||||||
|
init_per_testcase(t_get_basic_usage_info_1, Config) ->
|
||||||
|
meck:new(emqx_bridge, [passthrough, no_link, no_history]),
|
||||||
|
meck:expect(emqx_bridge, lookup, fun(_Type, _Name) -> {ok, #{mocked => true}} end),
|
||||||
|
Config;
|
||||||
init_per_testcase(_TestCase, Config) ->
|
init_per_testcase(_TestCase, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
|
@ -259,6 +263,10 @@ end_per_testcase(t_events, Config) ->
|
||||||
ok = delete_rule(?config(hook_points_rules, Config)),
|
ok = delete_rule(?config(hook_points_rules, Config)),
|
||||||
emqx_common_test_helpers:call_janitor(),
|
emqx_common_test_helpers:call_janitor(),
|
||||||
ok;
|
ok;
|
||||||
|
end_per_testcase(t_get_basic_usage_info_1, _Config) ->
|
||||||
|
meck:unload(),
|
||||||
|
emqx_common_test_helpers:call_janitor(),
|
||||||
|
ok;
|
||||||
end_per_testcase(_TestCase, _Config) ->
|
end_per_testcase(_TestCase, _Config) ->
|
||||||
emqx_common_test_helpers:call_janitor(),
|
emqx_common_test_helpers:call_janitor(),
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -781,8 +781,8 @@ setup_fake_rule_engine_data() ->
|
||||||
[
|
[
|
||||||
#{function => <<"erlang:hibernate">>, args => #{}},
|
#{function => <<"erlang:hibernate">>, args => #{}},
|
||||||
#{function => console},
|
#{function => console},
|
||||||
<<"webhook:my_webhook">>,
|
<<"webhook:basic_usage_info_webhook">>,
|
||||||
<<"webhook:my_webhook">>
|
<<"webhook:basic_usage_info_webhook_disabled">>
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -793,8 +793,8 @@ setup_fake_rule_engine_data() ->
|
||||||
sql => <<"select 1 from topic">>,
|
sql => <<"select 1 from topic">>,
|
||||||
actions =>
|
actions =>
|
||||||
[
|
[
|
||||||
<<"mqtt:my_mqtt_bridge">>,
|
<<"mqtt:basic_usage_info_mqtt">>,
|
||||||
<<"webhook:my_webhook">>
|
<<"webhook:basic_usage_info_webhook">>
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -802,7 +802,7 @@ setup_fake_rule_engine_data() ->
|
||||||
emqx_rule_engine:create_rule(
|
emqx_rule_engine:create_rule(
|
||||||
#{
|
#{
|
||||||
id => <<"rule:t_get_basic_usage_info:3">>,
|
id => <<"rule:t_get_basic_usage_info:3">>,
|
||||||
sql => <<"select 1 from \"$bridges/mqtt:mqtt_in\"">>,
|
sql => <<"select 1 from \"$bridges/mqtt:basic_usage_info_mqtt\"">>,
|
||||||
actions =>
|
actions =>
|
||||||
[
|
[
|
||||||
#{function => console}
|
#{function => console}
|
||||||
|
|
Loading…
Reference in New Issue