fix(kafka): fix result handling when sending message with invalid header
Fixes https://emqx.atlassian.net/browse/EMQX-10846
This commit is contained in:
parent
3aa15556a9
commit
60e6217496
|
@ -213,7 +213,7 @@ on_stop(InstanceId, _State) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
on_query(
|
on_query(
|
||||||
_InstId,
|
InstId,
|
||||||
{send_message, Message},
|
{send_message, Message},
|
||||||
#{
|
#{
|
||||||
message_template := Template,
|
message_template := Template,
|
||||||
|
@ -229,19 +229,34 @@ on_query(
|
||||||
ext_headers_tokens => KafkaExtHeadersTokens,
|
ext_headers_tokens => KafkaExtHeadersTokens,
|
||||||
headers_val_encode_mode => KafkaHeadersValEncodeMode
|
headers_val_encode_mode => KafkaHeadersValEncodeMode
|
||||||
},
|
},
|
||||||
KafkaMessage = render_message(Template, KafkaHeaders, Message),
|
|
||||||
?tp(
|
|
||||||
emqx_bridge_kafka_impl_producer_sync_query,
|
|
||||||
#{headers_config => KafkaHeaders, instance_id => _InstId}
|
|
||||||
),
|
|
||||||
try
|
try
|
||||||
{_Partition, _Offset} = wolff:send_sync(Producers, [KafkaMessage], SyncTimeout),
|
KafkaMessage = render_message(Template, KafkaHeaders, Message),
|
||||||
ok
|
?tp(
|
||||||
|
emqx_bridge_kafka_impl_producer_sync_query,
|
||||||
|
#{headers_config => KafkaHeaders, instance_id => InstId}
|
||||||
|
),
|
||||||
|
do_send_msg(sync, KafkaMessage, Producers, SyncTimeout)
|
||||||
catch
|
catch
|
||||||
error:{producer_down, _} = Reason ->
|
throw:{bad_kafka_header, _} = Error ->
|
||||||
{error, Reason};
|
?tp(
|
||||||
error:timeout ->
|
emqx_bridge_kafka_impl_producer_sync_query_failed,
|
||||||
{error, timeout}
|
#{
|
||||||
|
headers_config => KafkaHeaders,
|
||||||
|
instance_id => InstId,
|
||||||
|
reason => Error
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{error, {unrecoverable_error, Error}};
|
||||||
|
throw:{bad_kafka_headers, _} = Error ->
|
||||||
|
?tp(
|
||||||
|
emqx_bridge_kafka_impl_producer_sync_query_failed,
|
||||||
|
#{
|
||||||
|
headers_config => KafkaHeaders,
|
||||||
|
instance_id => InstId,
|
||||||
|
reason => Error
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{error, {unrecoverable_error, Error}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc The callback API for rule-engine (or bridge without rules)
|
%% @doc The callback API for rule-engine (or bridge without rules)
|
||||||
|
@ -251,7 +266,7 @@ on_query(
|
||||||
%% E.g. the output of rule-engine process chain
|
%% E.g. the output of rule-engine process chain
|
||||||
%% or the direct mapping from an MQTT message.
|
%% or the direct mapping from an MQTT message.
|
||||||
on_query_async(
|
on_query_async(
|
||||||
_InstId,
|
InstId,
|
||||||
{send_message, Message},
|
{send_message, Message},
|
||||||
AsyncReplyFn,
|
AsyncReplyFn,
|
||||||
#{
|
#{
|
||||||
|
@ -267,21 +282,35 @@ on_query_async(
|
||||||
ext_headers_tokens => KafkaExtHeadersTokens,
|
ext_headers_tokens => KafkaExtHeadersTokens,
|
||||||
headers_val_encode_mode => KafkaHeadersValEncodeMode
|
headers_val_encode_mode => KafkaHeadersValEncodeMode
|
||||||
},
|
},
|
||||||
KafkaMessage = render_message(Template, KafkaHeaders, Message),
|
try
|
||||||
?tp(
|
KafkaMessage = render_message(Template, KafkaHeaders, Message),
|
||||||
emqx_bridge_kafka_impl_producer_async_query,
|
?tp(
|
||||||
#{headers_config => KafkaHeaders, instance_id => _InstId}
|
emqx_bridge_kafka_impl_producer_async_query,
|
||||||
),
|
#{headers_config => KafkaHeaders, instance_id => InstId}
|
||||||
%% * Must be a batch because wolff:send and wolff:send_sync are batch APIs
|
),
|
||||||
%% * Must be a single element batch because wolff books calls, but not batch sizes
|
do_send_msg(async, KafkaMessage, Producers, AsyncReplyFn)
|
||||||
%% for counters and gauges.
|
catch
|
||||||
Batch = [KafkaMessage],
|
throw:{bad_kafka_header, _} = Error ->
|
||||||
%% The retuned information is discarded here.
|
?tp(
|
||||||
%% If the producer process is down when sending, this function would
|
emqx_bridge_kafka_impl_producer_async_query_failed,
|
||||||
%% raise an error exception which is to be caught by the caller of this callback
|
#{
|
||||||
{_Partition, Pid} = wolff:send(Producers, Batch, {fun ?MODULE:on_kafka_ack/3, [AsyncReplyFn]}),
|
headers_config => KafkaHeaders,
|
||||||
%% this Pid is so far never used because Kafka producer is by-passing the buffer worker
|
instance_id => InstId,
|
||||||
{ok, Pid}.
|
reason => Error
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{error, {unrecoverable_error, Error}};
|
||||||
|
throw:{bad_kafka_headers, _} = Error ->
|
||||||
|
?tp(
|
||||||
|
emqx_bridge_kafka_impl_producer_async_query_failed,
|
||||||
|
#{
|
||||||
|
headers_config => KafkaHeaders,
|
||||||
|
instance_id => InstId,
|
||||||
|
reason => Error
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{error, {unrecoverable_error, Error}}
|
||||||
|
end.
|
||||||
|
|
||||||
compile_message_template(T) ->
|
compile_message_template(T) ->
|
||||||
KeyTemplate = maps:get(key, T, <<"${.clientid}">>),
|
KeyTemplate = maps:get(key, T, <<"${.clientid}">>),
|
||||||
|
@ -337,6 +366,28 @@ render_timestamp(Template, Message) ->
|
||||||
erlang:system_time(millisecond)
|
erlang:system_time(millisecond)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
do_send_msg(sync, KafkaMessage, Producers, SyncTimeout) ->
|
||||||
|
try
|
||||||
|
{_Partition, _Offset} = wolff:send_sync(Producers, [KafkaMessage], SyncTimeout),
|
||||||
|
ok
|
||||||
|
catch
|
||||||
|
error:{producer_down, _} = Reason ->
|
||||||
|
{error, Reason};
|
||||||
|
error:timeout ->
|
||||||
|
{error, timeout}
|
||||||
|
end;
|
||||||
|
do_send_msg(async, KafkaMessage, Producers, AsyncReplyFn) ->
|
||||||
|
%% * Must be a batch because wolff:send and wolff:send_sync are batch APIs
|
||||||
|
%% * Must be a single element batch because wolff books calls, but not batch sizes
|
||||||
|
%% for counters and gauges.
|
||||||
|
Batch = [KafkaMessage],
|
||||||
|
%% The retuned information is discarded here.
|
||||||
|
%% If the producer process is down when sending, this function would
|
||||||
|
%% raise an error exception which is to be caught by the caller of this callback
|
||||||
|
{_Partition, Pid} = wolff:send(Producers, Batch, {fun ?MODULE:on_kafka_ack/3, [AsyncReplyFn]}),
|
||||||
|
%% this Pid is so far never used because Kafka producer is by-passing the buffer worker
|
||||||
|
{ok, Pid}.
|
||||||
|
|
||||||
%% Wolff producer never gives up retrying
|
%% Wolff producer never gives up retrying
|
||||||
%% so there can only be 'ok' results.
|
%% so there can only be 'ok' results.
|
||||||
on_kafka_ack(_Partition, Offset, {ReplyFn, Args}) when is_integer(Offset) ->
|
on_kafka_ack(_Partition, Offset, {ReplyFn, Args}) when is_integer(Offset) ->
|
||||||
|
|
|
@ -677,6 +677,90 @@ t_wrong_headers(_Config) ->
|
||||||
),
|
),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_wrong_headers_from_message(Config) ->
|
||||||
|
HostsString = kafka_hosts_string_sasl(),
|
||||||
|
AuthSettings = valid_sasl_plain_settings(),
|
||||||
|
Hash = erlang:phash2([HostsString, ?FUNCTION_NAME]),
|
||||||
|
Type = ?BRIDGE_TYPE,
|
||||||
|
Name = "kafka_bridge_name_" ++ erlang:integer_to_list(Hash),
|
||||||
|
ResourceId = emqx_bridge_resource:resource_id(Type, Name),
|
||||||
|
BridgeId = emqx_bridge_resource:bridge_id(Type, Name),
|
||||||
|
KafkaTopic = "test-topic-one-partition",
|
||||||
|
Conf = config_with_headers(#{
|
||||||
|
"authentication" => AuthSettings,
|
||||||
|
"kafka_hosts_string" => HostsString,
|
||||||
|
"kafka_topic" => KafkaTopic,
|
||||||
|
"instance_id" => ResourceId,
|
||||||
|
"kafka_headers" => <<"${payload}">>,
|
||||||
|
"producer" => #{
|
||||||
|
"kafka" => #{
|
||||||
|
"buffer" => #{
|
||||||
|
"memory_overload_protection" => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ssl" => #{}
|
||||||
|
}),
|
||||||
|
{ok, #{config := ConfigAtom1}} = emqx_bridge:create(
|
||||||
|
Type, erlang:list_to_atom(Name), Conf
|
||||||
|
),
|
||||||
|
ConfigAtom = ConfigAtom1#{bridge_name => Name},
|
||||||
|
{ok, State} = ?PRODUCER:on_start(ResourceId, ConfigAtom),
|
||||||
|
Time1 = erlang:unique_integer(),
|
||||||
|
Payload1 = <<"wrong_header">>,
|
||||||
|
Msg1 = #{
|
||||||
|
clientid => integer_to_binary(Time1),
|
||||||
|
payload => Payload1,
|
||||||
|
timestamp => Time1
|
||||||
|
},
|
||||||
|
?assertError(
|
||||||
|
{badmatch, {error, {unrecoverable_error, {bad_kafka_headers, Payload1}}}},
|
||||||
|
send(Config, ResourceId, Msg1, State)
|
||||||
|
),
|
||||||
|
Time2 = erlang:unique_integer(),
|
||||||
|
Payload2 = <<"[{\"foo\":\"bar\"}, {\"foo2\":\"bar2\"}]">>,
|
||||||
|
Msg2 = #{
|
||||||
|
clientid => integer_to_binary(Time2),
|
||||||
|
payload => Payload2,
|
||||||
|
timestamp => Time2
|
||||||
|
},
|
||||||
|
?assertError(
|
||||||
|
{badmatch, {error, {unrecoverable_error, {bad_kafka_header, [{<<"foo">>, <<"bar">>}]}}}},
|
||||||
|
send(Config, ResourceId, Msg2, State)
|
||||||
|
),
|
||||||
|
Time3 = erlang:unique_integer(),
|
||||||
|
Payload3 = <<"[{\"key\":\"foo\"}, {\"value\":\"bar\"}]">>,
|
||||||
|
Msg3 = #{
|
||||||
|
clientid => integer_to_binary(Time3),
|
||||||
|
payload => Payload3,
|
||||||
|
timestamp => Time3
|
||||||
|
},
|
||||||
|
?assertError(
|
||||||
|
{badmatch, {error, {unrecoverable_error, {bad_kafka_header, [{<<"key">>, <<"foo">>}]}}}},
|
||||||
|
send(Config, ResourceId, Msg3, State)
|
||||||
|
),
|
||||||
|
Time4 = erlang:unique_integer(),
|
||||||
|
Payload4 = <<"[{\"key\":\"foo\", \"value\":\"bar\"}]">>,
|
||||||
|
Msg4 = #{
|
||||||
|
clientid => integer_to_binary(Time4),
|
||||||
|
payload => Payload4,
|
||||||
|
timestamp => Time4
|
||||||
|
},
|
||||||
|
?assertError(
|
||||||
|
{badmatch,
|
||||||
|
{error,
|
||||||
|
{unrecoverable_error,
|
||||||
|
{bad_kafka_header, [{<<"key">>, <<"foo">>}, {<<"value">>, <<"bar">>}]}}}},
|
||||||
|
send(Config, ResourceId, Msg4, State)
|
||||||
|
),
|
||||||
|
%% TODO: refactor those into init/end per testcase
|
||||||
|
ok = ?PRODUCER:on_stop(ResourceId, State),
|
||||||
|
?assertEqual([], supervisor:which_children(wolff_client_sup)),
|
||||||
|
?assertEqual([], supervisor:which_children(wolff_producers_sup)),
|
||||||
|
ok = emqx_bridge_resource:remove(BridgeId),
|
||||||
|
delete_all_bridges(),
|
||||||
|
ok.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helper functions
|
%% Helper functions
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix message error handling on Kafka bridge when headers translate to an invalid value.
|
Loading…
Reference in New Issue