chore(rule_engine): sync the code from rule-engine/dev/v4.3.0

This commit is contained in:
Shawn 2020-12-09 17:58:26 +08:00
parent bc8ddb7213
commit 573a4b2df8
4 changed files with 115 additions and 11 deletions

View File

@ -45,7 +45,7 @@ init([]) ->
shutdown => 5000, shutdown => 5000,
type => worker, type => worker,
modules => [emqx_rule_metrics]}, modules => [emqx_rule_metrics]},
{ok, {{one_for_all, 10, 100}, [Registry, Metrics]}}. {ok, {{one_for_one, 10, 10}, [Registry, Metrics]}}.
start_locker() -> start_locker() ->
Locker = #{id => emqx_rule_locker, Locker = #{id => emqx_rule_locker,

View File

@ -360,6 +360,12 @@ printable_maps(Headers) ->
fun (K, V0, AccIn) when K =:= peerhost; K =:= peername; K =:= sockname -> fun (K, V0, AccIn) when K =:= peerhost; K =:= peername; K =:= sockname ->
AccIn#{K => ntoa(V0)}; AccIn#{K => ntoa(V0)};
('User-Property', V0, AccIn) when is_list(V0) -> ('User-Property', V0, AccIn) when is_list(V0) ->
AccIn#{'User-Property' => maps:from_list(V0)}; AccIn#{
'User-Property' => maps:from_list(V0),
'User-Property-Pairs' => [#{
key => Key,
value => Value
} || {Key, Value} <- V0]
};
(K, V0, AccIn) -> AccIn#{K => V0} (K, V0, AccIn) -> AccIn#{K => V0}
end, #{}, Headers). end, #{}, Headers).

View File

@ -234,20 +234,26 @@ take_action(#action_instance{id = Id, name = ActName, fallbacks = Fallbacks} = A
= emqx_rule_registry:get_action_instance_params(Id), = emqx_rule_registry:get_action_instance_params(Id),
emqx_rule_metrics:inc_actions_taken(Id), emqx_rule_metrics:inc_actions_taken(Id),
apply_action_func(Selected, Envs, Apply, ActName) apply_action_func(Selected, Envs, Apply, ActName)
of
{badact, Reason} ->
handle_action_failure(OnFailed, Id, Fallbacks, Selected, Envs, Reason);
Result -> Result
catch catch
error:{badfun, _Func}:_ST -> error:{badfun, _Func}:_ST ->
%?LOG(warning, "Action ~p maybe outdated, refresh it and try again." %?LOG(warning, "Action ~p maybe outdated, refresh it and try again."
% "Func: ~p~nST:~0p", [Id, Func, ST]), % "Func: ~p~nST:~0p", [Id, Func, ST]),
trans_action_on(Id, fun() -> trans_action_on(Id, fun() ->
emqx_rule_engine:refresh_actions([ActInst]) emqx_rule_engine:refresh_actions([ActInst])
end, 5000), end, 5000),
emqx_rule_metrics:inc_actions_retry(Id), emqx_rule_metrics:inc_actions_retry(Id),
take_action(ActInst, Selected, Envs, OnFailed, RetryN-1); take_action(ActInst, Selected, Envs, OnFailed, RetryN-1);
Error:Reason:Stack -> Error:Reason:Stack ->
emqx_rule_metrics:inc_actions_exception(Id),
handle_action_failure(OnFailed, Id, Fallbacks, Selected, Envs, {Error, Reason, Stack}) handle_action_failure(OnFailed, Id, Fallbacks, Selected, Envs, {Error, Reason, Stack})
end; end;
take_action(#action_instance{id = Id, fallbacks = Fallbacks}, Selected, Envs, OnFailed, _RetryN) -> take_action(#action_instance{id = Id, fallbacks = Fallbacks}, Selected, Envs, OnFailed, _RetryN) ->
emqx_rule_metrics:inc_actions_error(Id),
handle_action_failure(OnFailed, Id, Fallbacks, Selected, Envs, {max_try_reached, ?ActionMaxRetry}). handle_action_failure(OnFailed, Id, Fallbacks, Selected, Envs, {max_try_reached, ?ActionMaxRetry}).
apply_action_func(Data, Envs, #{mod := Mod, bindings := Bindings}, Name) -> apply_action_func(Data, Envs, #{mod := Mod, bindings := Bindings}, Name) ->
@ -284,12 +290,10 @@ wait_action_on(Id, RetryN) ->
end. end.
handle_action_failure(continue, Id, Fallbacks, Selected, Envs, Reason) -> handle_action_failure(continue, Id, Fallbacks, Selected, Envs, Reason) ->
emqx_rule_metrics:inc_actions_exception(Id),
?LOG(error, "Take action ~p failed, continue next action, reason: ~0p", [Id, Reason]), ?LOG(error, "Take action ~p failed, continue next action, reason: ~0p", [Id, Reason]),
take_actions(Fallbacks, Selected, Envs, continue), take_actions(Fallbacks, Selected, Envs, continue),
failed; failed;
handle_action_failure(stop, Id, Fallbacks, Selected, Envs, Reason) -> handle_action_failure(stop, Id, Fallbacks, Selected, Envs, Reason) ->
emqx_rule_metrics:inc_actions_exception(Id),
?LOG(error, "Take action ~p failed, skip all actions, reason: ~0p", [Id, Reason]), ?LOG(error, "Take action ~p failed, skip all actions, reason: ~0p", [Id, Reason]),
take_actions(Fallbacks, Selected, Envs, continue), take_actions(Fallbacks, Selected, Envs, continue),
error({take_action_failed, {Id, Reason}}). error({take_action_failed, {Id, Reason}}).
@ -409,11 +413,13 @@ add_metadata(Input, Metadata) when is_map(Input), is_map(Metadata) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Internal Functions %% Internal Functions
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
may_decode_payload(Payload) -> may_decode_payload(Payload) when is_binary(Payload) ->
case get_cached_payload() of case get_cached_payload() of
undefined -> ensure_decoded(Payload); undefined -> safe_decode_and_cache(Payload);
DecodedP -> DecodedP DecodedP -> DecodedP
end. end;
may_decode_payload(Payload) ->
Payload.
get_cached_payload() -> get_cached_payload() ->
erlang:get(rule_payload). erlang:get(rule_payload).
@ -422,9 +428,7 @@ cache_payload(DecodedP) ->
erlang:put(rule_payload, DecodedP), erlang:put(rule_payload, DecodedP),
DecodedP. DecodedP.
ensure_decoded(Json) when is_map(Json); is_list(Json) -> safe_decode_and_cache(MaybeJson) ->
Json;
ensure_decoded(MaybeJson) ->
try cache_payload(emqx_json:decode(MaybeJson, [return_maps])) try cache_payload(emqx_json:decode(MaybeJson, [return_maps]))
catch _:_ -> #{} catch _:_ -> #{}
end. end.

View File

@ -37,6 +37,7 @@ all() ->
, {group, runtime} , {group, runtime}
, {group, events} , {group, events}
, {group, multi_actions} , {group, multi_actions}
, {group, bugs}
]. ].
suite() -> suite() ->
@ -123,11 +124,15 @@ groups() ->
{events, [], {events, [],
[t_events [t_events
]}, ]},
{bugs, [],
[t_sqlparse_payload_as
]},
{multi_actions, [], {multi_actions, [],
[t_sqlselect_multi_actoins_1, [t_sqlselect_multi_actoins_1,
t_sqlselect_multi_actoins_1_1, t_sqlselect_multi_actoins_1_1,
t_sqlselect_multi_actoins_2, t_sqlselect_multi_actoins_2,
t_sqlselect_multi_actoins_3, t_sqlselect_multi_actoins_3,
t_sqlselect_multi_actoins_3_1,
t_sqlselect_multi_actoins_4 t_sqlselect_multi_actoins_4
]} ]}
]. ].
@ -200,6 +205,7 @@ init_per_testcase(Test, Config)
;Test =:= t_sqlselect_multi_actoins_1_1 ;Test =:= t_sqlselect_multi_actoins_1_1
;Test =:= t_sqlselect_multi_actoins_2 ;Test =:= t_sqlselect_multi_actoins_2
;Test =:= t_sqlselect_multi_actoins_3 ;Test =:= t_sqlselect_multi_actoins_3
;Test =:= t_sqlselect_multi_actoins_3_1
;Test =:= t_sqlselect_multi_actoins_4 ;Test =:= t_sqlselect_multi_actoins_4
-> ->
ok = emqx_rule_engine:load_providers(), ok = emqx_rule_engine:load_providers(),
@ -209,6 +215,12 @@ init_per_testcase(Test, Config)
types=[], params_spec = #{}, types=[], params_spec = #{},
title = #{en => <<"Crash Action">>}, title = #{en => <<"Crash Action">>},
description = #{en => <<"This action will always fail!">>}}), description = #{en => <<"This action will always fail!">>}}),
ok = emqx_rule_registry:add_action(
#action{name = 'failure_action', app = ?APP,
module = ?MODULE, on_create = failure_action,
types=[], params_spec = #{},
title = #{en => <<"Crash Action">>},
description = #{en => <<"This action will always fail!">>}}),
ok = emqx_rule_registry:add_action( ok = emqx_rule_registry:add_action(
#action{name = 'plus_by_one', app = ?APP, #action{name = 'plus_by_one', app = ?APP,
module = ?MODULE, on_create = plus_by_one_action, module = ?MODULE, on_create = plus_by_one_action,
@ -1288,6 +1300,44 @@ t_sqlselect_multi_actoins_3(Config) ->
emqx_rule_registry:remove_rule(Rule). emqx_rule_registry:remove_rule(Rule).
t_sqlselect_multi_actoins_3_1(Config) ->
%% We create 2 actions in the same rule (on_action_failed = continue):
%% The first will fail (with a 'badact' return) and we need to make sure the
%% fallback actions can be executed, and the next actoins
%% will be run without influence
{ok, Rule} = emqx_rule_engine:create_rule(
#{rawsql => ?config(connsql, Config),
on_action_failed => continue,
actions => [
#{name => 'failure_action', args => #{}, fallbacks =>[
#{name => 'plus_by_one', args => #{}, fallbacks =>[]},
#{name => 'plus_by_one', args => #{}, fallbacks =>[]}
]},
#{name => 'republish',
args => #{<<"target_topic">> => <<"t2">>,
<<"target_qos">> => -1,
<<"payload_tmpl">> => <<"clientid=${clientid}">>
},
fallbacks => []}
]
}),
(?config(conn_event, Config))(),
timer:sleep(100),
%% verfiy the fallback actions has been run
?assertEqual(2, ets:lookup_element(plus_by_one_action, num, 2)),
%% verfiy the next actions can be run
receive {publish, #{topic := T, payload := Payload}} ->
?assertEqual(<<"t2">>, T),
?assertEqual(<<"clientid=c_emqx1">>, Payload)
after 1000 ->
ct:fail(wait_for_t2)
end,
emqx_rule_registry:remove_rule(Rule).
t_sqlselect_multi_actoins_4(Config) -> t_sqlselect_multi_actoins_4(Config) ->
%% We create 2 actions in the same rule (on_action_failed = continue): %% We create 2 actions in the same rule (on_action_failed = continue):
%% The first will fail and we need to make sure the %% The first will fail and we need to make sure the
@ -1920,6 +1970,44 @@ t_sqlparse_new_map(_Config) ->
<<"c">> := [#{}] <<"c">> := [#{}]
}, Res00). }, Res00).
t_sqlparse_payload_as(_Config) ->
%% https://github.com/emqx/emqx/issues/3866
Sql00 = "SELECT "
" payload, map_get('engineWorkTime', payload.params, -1) as payload.params.engineWorkTime, "
" map_get('hydOilTem', payload.params, -1) as payload.params.hydOilTem "
"FROM \"t/#\" ",
Payload1 = <<"{ \"msgId\": 1002, \"params\": { \"convertTemp\": 20, \"engineSpeed\": 42, \"hydOilTem\": 30 } }">>,
{ok, Res01} = emqx_rule_sqltester:test(
#{<<"rawsql">> => Sql00,
<<"ctx">> => #{<<"payload">> => Payload1,
<<"topic">> => <<"t/a">>}}),
?assertMatch(#{
<<"payload">> := #{
<<"params">> := #{
<<"convertTemp">> := 20,
<<"engineSpeed">> := 42,
<<"engineWorkTime">> := -1,
<<"hydOilTem">> := 30
}
}
}, Res01),
Payload2 = <<"{ \"msgId\": 1002, \"params\": { \"convertTemp\": 20, \"engineSpeed\": 42 } }">>,
{ok, Res02} = emqx_rule_sqltester:test(
#{<<"rawsql">> => Sql00,
<<"ctx">> => #{<<"payload">> => Payload2,
<<"topic">> => <<"t/a">>}}),
?assertMatch(#{
<<"payload">> := #{
<<"params">> := #{
<<"convertTemp">> := 20,
<<"engineSpeed">> := 42,
<<"engineWorkTime">> := -1,
<<"hydOilTem">> := -1
}
}
}, Res02).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Internal helpers %% Internal helpers
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -2006,6 +2094,12 @@ mfa_action(Id, _Params) ->
mfa_action_do(_Data, _Envs, K) -> mfa_action_do(_Data, _Envs, K) ->
persistent_term:put(K, 1). persistent_term:put(K, 1).
failure_action(_Id, _Params) ->
fun(Data, _Envs) ->
ct:pal("applying crash action, Data: ~p", [Data]),
{badact, intentional_failure}
end.
crash_action(_Id, _Params) -> crash_action(_Id, _Params) ->
fun(Data, _Envs) -> fun(Data, _Envs) ->
ct:pal("applying crash action, Data: ~p", [Data]), ct:pal("applying crash action, Data: ~p", [Data]),