fix(resource): take error from action/connector before attempting query
Fixes https://emqx.atlassian.net/browse/EMQX-11284 Fixes https://emqx.atlassian.net/browse/EMQX-11298
This commit is contained in:
parent
ac6ad79029
commit
7dcdbc9e51
|
@ -535,11 +535,13 @@ do_send_msg_with_enabled_config(
|
||||||
BridgeType, BridgeName, Message, QueryOpts0, Config
|
BridgeType, BridgeName, Message, QueryOpts0, Config
|
||||||
) ->
|
) ->
|
||||||
QueryMode = get_query_mode(BridgeType, Config),
|
QueryMode = get_query_mode(BridgeType, Config),
|
||||||
|
ConnectorName = maps:get(connector, Config),
|
||||||
|
ConnectorResId = emqx_connector_resource:resource_id(BridgeType, ConnectorName),
|
||||||
QueryOpts = maps:merge(
|
QueryOpts = maps:merge(
|
||||||
emqx_bridge:query_opts(Config),
|
emqx_bridge:query_opts(Config),
|
||||||
QueryOpts0#{
|
QueryOpts0#{
|
||||||
query_mode => QueryMode,
|
connector_resource_id => ConnectorResId,
|
||||||
query_mode_cache_override => false
|
query_mode => QueryMode
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
BridgeV2Id = id(BridgeType, BridgeName),
|
BridgeV2Id = id(BridgeType, BridgeName),
|
||||||
|
|
|
@ -557,11 +557,8 @@ check_topic_status(ClientId, WolffClientPid, KafkaTopic) ->
|
||||||
ok ->
|
ok ->
|
||||||
ok;
|
ok;
|
||||||
{error, unknown_topic_or_partition} ->
|
{error, unknown_topic_or_partition} ->
|
||||||
throw(#{
|
Msg = iolist_to_binary([<<"Unknown topic or partition: ">>, KafkaTopic]),
|
||||||
error => unknown_kafka_topic,
|
throw({unhealthy_target, Msg});
|
||||||
kafka_client_id => ClientId,
|
|
||||||
kafka_topic => KafkaTopic
|
|
||||||
});
|
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
throw(#{
|
throw(#{
|
||||||
error => failed_to_check_topic_status,
|
error => failed_to_check_topic_status,
|
||||||
|
|
|
@ -574,8 +574,14 @@ t_nonexistent_topic(_Config) ->
|
||||||
erlang:list_to_atom(Type), erlang:list_to_atom(Name), Conf
|
erlang:list_to_atom(Type), erlang:list_to_atom(Name), Conf
|
||||||
),
|
),
|
||||||
% TODO: make sure the user facing APIs for Bridge V1 also get this error
|
% TODO: make sure the user facing APIs for Bridge V1 also get this error
|
||||||
#{status := disconnected, error := #{error := unknown_kafka_topic}} = emqx_bridge_v2:health_check(
|
?assertMatch(
|
||||||
?BRIDGE_TYPE_V2, list_to_atom(Name)
|
#{
|
||||||
|
status := disconnected,
|
||||||
|
error := {unhealthy_target, <<"Unknown topic or partition: undefined-test-topic">>}
|
||||||
|
},
|
||||||
|
emqx_bridge_v2:health_check(
|
||||||
|
?BRIDGE_TYPE_V2, list_to_atom(Name)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
ok = emqx_bridge:remove(list_to_atom(Type), list_to_atom(Name)),
|
ok = emqx_bridge:remove(list_to_atom(Type), list_to_atom(Name)),
|
||||||
delete_all_bridges(),
|
delete_all_bridges(),
|
||||||
|
|
|
@ -140,6 +140,33 @@ t_local_topic(_) ->
|
||||||
ok = emqx_connector:remove(?TYPE, test_connector),
|
ok = emqx_connector:remove(?TYPE, test_connector),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_unknown_topic(_Config) ->
|
||||||
|
ConnectorName = <<"test_connector">>,
|
||||||
|
BridgeName = <<"test_bridge">>,
|
||||||
|
BridgeV2Config0 = bridge_v2_config(ConnectorName),
|
||||||
|
BridgeV2Config = emqx_utils_maps:deep_put(
|
||||||
|
[<<"kafka">>, <<"topic">>],
|
||||||
|
BridgeV2Config0,
|
||||||
|
<<"nonexistent">>
|
||||||
|
),
|
||||||
|
ConnectorConfig = connector_config(),
|
||||||
|
{ok, _} = emqx_connector:create(?TYPE, ConnectorName, ConnectorConfig),
|
||||||
|
{ok, _} = emqx_bridge_v2:create(?TYPE, BridgeName, BridgeV2Config),
|
||||||
|
Payload = <<"will be dropped">>,
|
||||||
|
emqx:publish(emqx_message:make(<<"kafka_t/local">>, Payload)),
|
||||||
|
BridgeV2Id = emqx_bridge_v2:id(?TYPE, BridgeName),
|
||||||
|
?retry(
|
||||||
|
_Sleep0 = 50,
|
||||||
|
_Attempts0 = 100,
|
||||||
|
begin
|
||||||
|
?assertEqual(1, emqx_resource_metrics:matched_get(BridgeV2Id)),
|
||||||
|
?assertEqual(1, emqx_resource_metrics:dropped_get(BridgeV2Id)),
|
||||||
|
?assertEqual(1, emqx_resource_metrics:dropped_resource_stopped_get(BridgeV2Id)),
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
check_send_message_with_bridge(BridgeName) ->
|
check_send_message_with_bridge(BridgeName) ->
|
||||||
%% ######################################
|
%% ######################################
|
||||||
%% Create Kafka message
|
%% Create Kafka message
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
-type resource_spec() :: map().
|
-type resource_spec() :: map().
|
||||||
-type resource_state() :: term().
|
-type resource_state() :: term().
|
||||||
-type resource_status() :: connected | disconnected | connecting | stopped.
|
-type resource_status() :: connected | disconnected | connecting | stopped.
|
||||||
-type channel_status() :: connected | connecting.
|
-type channel_status() :: connected | connecting | disconnected.
|
||||||
-type callback_mode() :: always_sync | async_if_possible.
|
-type callback_mode() :: always_sync | async_if_possible.
|
||||||
-type query_mode() ::
|
-type query_mode() ::
|
||||||
simple_sync
|
simple_sync
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
simple_query => boolean(),
|
simple_query => boolean(),
|
||||||
reply_to => reply_fun(),
|
reply_to => reply_fun(),
|
||||||
query_mode => query_mode(),
|
query_mode => query_mode(),
|
||||||
query_mode_cache_override => boolean()
|
connector_resource_id => resource_id()
|
||||||
}.
|
}.
|
||||||
-type resource_data() :: #{
|
-type resource_data() :: #{
|
||||||
id := resource_id(),
|
id := resource_id(),
|
||||||
|
|
|
@ -379,32 +379,32 @@ query(ResId, Request) ->
|
||||||
-spec query(resource_id(), Request :: term(), query_opts()) ->
|
-spec query(resource_id(), Request :: term(), query_opts()) ->
|
||||||
Result :: term().
|
Result :: term().
|
||||||
query(ResId, Request, Opts) ->
|
query(ResId, Request, Opts) ->
|
||||||
case get_query_mode_error(ResId, Opts) of
|
case emqx_resource_manager:get_query_mode_and_last_error(ResId, Opts) of
|
||||||
{error, _} = ErrorTuple ->
|
{error, _} = ErrorTuple ->
|
||||||
ErrorTuple;
|
ErrorTuple;
|
||||||
{_, unhealthy_target} ->
|
{ok, {_, unhealthy_target}} ->
|
||||||
emqx_resource_metrics:matched_inc(ResId),
|
emqx_resource_metrics:matched_inc(ResId),
|
||||||
emqx_resource_metrics:dropped_resource_stopped_inc(ResId),
|
emqx_resource_metrics:dropped_resource_stopped_inc(ResId),
|
||||||
?RESOURCE_ERROR(unhealthy_target, "unhealthy target");
|
?RESOURCE_ERROR(unhealthy_target, "unhealthy target");
|
||||||
{_, {unhealthy_target, _Message}} ->
|
{ok, {_, {unhealthy_target, Message}}} ->
|
||||||
emqx_resource_metrics:matched_inc(ResId),
|
emqx_resource_metrics:matched_inc(ResId),
|
||||||
emqx_resource_metrics:dropped_resource_stopped_inc(ResId),
|
emqx_resource_metrics:dropped_resource_stopped_inc(ResId),
|
||||||
?RESOURCE_ERROR(unhealthy_target, "unhealthy target");
|
?RESOURCE_ERROR(unhealthy_target, Message);
|
||||||
{simple_async, _} ->
|
{ok, {simple_async, _}} ->
|
||||||
%% TODO(5.1.1): pass Resource instead of ResId to simple APIs
|
%% TODO(5.1.1): pass Resource instead of ResId to simple APIs
|
||||||
%% so the buffer worker does not need to lookup the cache again
|
%% so the buffer worker does not need to lookup the cache again
|
||||||
emqx_resource_buffer_worker:simple_async_query(ResId, Request, Opts);
|
emqx_resource_buffer_worker:simple_async_query(ResId, Request, Opts);
|
||||||
{simple_sync, _} ->
|
{ok, {simple_sync, _}} ->
|
||||||
%% TODO(5.1.1): pass Resource instead of ResId to simple APIs
|
%% TODO(5.1.1): pass Resource instead of ResId to simple APIs
|
||||||
%% so the buffer worker does not need to lookup the cache again
|
%% so the buffer worker does not need to lookup the cache again
|
||||||
emqx_resource_buffer_worker:simple_sync_query(ResId, Request, Opts);
|
emqx_resource_buffer_worker:simple_sync_query(ResId, Request, Opts);
|
||||||
{simple_async_internal_buffer, _} ->
|
{ok, {simple_async_internal_buffer, _}} ->
|
||||||
%% This is for bridges/connectors that have internal buffering, such
|
%% This is for bridges/connectors that have internal buffering, such
|
||||||
%% as Kafka and Pulsar producers.
|
%% as Kafka and Pulsar producers.
|
||||||
%% TODO(5.1.1): pass Resource instead of ResId to simple APIs
|
%% TODO(5.1.1): pass Resource instead of ResId to simple APIs
|
||||||
%% so the buffer worker does not need to lookup the cache again
|
%% so the buffer worker does not need to lookup the cache again
|
||||||
emqx_resource_buffer_worker:simple_async_query(ResId, Request, Opts);
|
emqx_resource_buffer_worker:simple_async_query(ResId, Request, Opts);
|
||||||
{simple_sync_internal_buffer, _} ->
|
{ok, {simple_sync_internal_buffer, _}} ->
|
||||||
%% This is for bridges/connectors that have internal buffering, such
|
%% This is for bridges/connectors that have internal buffering, such
|
||||||
%% as Kafka and Pulsar producers.
|
%% as Kafka and Pulsar producers.
|
||||||
%% TODO(5.1.1): pass Resource instead of ResId to simple APIs
|
%% TODO(5.1.1): pass Resource instead of ResId to simple APIs
|
||||||
|
@ -412,30 +412,12 @@ query(ResId, Request, Opts) ->
|
||||||
emqx_resource_buffer_worker:simple_sync_internal_buffer_query(
|
emqx_resource_buffer_worker:simple_sync_internal_buffer_query(
|
||||||
ResId, Request, Opts
|
ResId, Request, Opts
|
||||||
);
|
);
|
||||||
{sync, _} ->
|
{ok, {sync, _}} ->
|
||||||
emqx_resource_buffer_worker:sync_query(ResId, Request, Opts);
|
emqx_resource_buffer_worker:sync_query(ResId, Request, Opts);
|
||||||
{async, _} ->
|
{ok, {async, _}} ->
|
||||||
emqx_resource_buffer_worker:async_query(ResId, Request, Opts)
|
emqx_resource_buffer_worker:async_query(ResId, Request, Opts)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_query_mode_error(ResId, Opts) ->
|
|
||||||
case maps:get(query_mode_cache_override, Opts, true) of
|
|
||||||
false ->
|
|
||||||
case Opts of
|
|
||||||
#{query_mode := QueryMode} ->
|
|
||||||
{QueryMode, ok};
|
|
||||||
_ ->
|
|
||||||
{async, unhealthy_target}
|
|
||||||
end;
|
|
||||||
true ->
|
|
||||||
case emqx_resource_manager:lookup_cached(ResId) of
|
|
||||||
{ok, _Group, #{query_mode := QM, error := Error}} ->
|
|
||||||
{QM, Error};
|
|
||||||
{error, not_found} ->
|
|
||||||
{error, not_found}
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec simple_sync_query(resource_id(), Request :: term()) -> Result :: term().
|
-spec simple_sync_query(resource_id(), Request :: term()) -> Result :: term().
|
||||||
simple_sync_query(ResId, Request) ->
|
simple_sync_query(ResId, Request) ->
|
||||||
emqx_resource_buffer_worker:simple_sync_query(ResId, Request).
|
emqx_resource_buffer_worker:simple_sync_query(ResId, Request).
|
||||||
|
|
|
@ -45,7 +45,8 @@
|
||||||
lookup_cached/1,
|
lookup_cached/1,
|
||||||
get_metrics/1,
|
get_metrics/1,
|
||||||
reset_metrics/1,
|
reset_metrics/1,
|
||||||
channel_status_is_channel_added/1
|
channel_status_is_channel_added/1,
|
||||||
|
get_query_mode_and_last_error/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
|
@ -75,6 +76,7 @@
|
||||||
extra
|
extra
|
||||||
}).
|
}).
|
||||||
-type data() :: #data{}.
|
-type data() :: #data{}.
|
||||||
|
-type channel_status_map() :: #{status := channel_status(), error := term()}.
|
||||||
|
|
||||||
-define(NAME(ResId), {n, l, {?MODULE, ResId}}).
|
-define(NAME(ResId), {n, l, {?MODULE, ResId}}).
|
||||||
-define(REF(ResId), {via, gproc, ?NAME(ResId)}).
|
-define(REF(ResId), {via, gproc, ?NAME(ResId)}).
|
||||||
|
@ -326,6 +328,46 @@ remove_channel(ResId, ChannelId) ->
|
||||||
get_channels(ResId) ->
|
get_channels(ResId) ->
|
||||||
safe_call(ResId, get_channels, ?T_OPERATION).
|
safe_call(ResId, get_channels, ?T_OPERATION).
|
||||||
|
|
||||||
|
-spec get_query_mode_and_last_error(resource_id(), query_opts()) ->
|
||||||
|
{ok, {query_mode(), LastError}} | {error, not_found}
|
||||||
|
when
|
||||||
|
LastError ::
|
||||||
|
unhealthy_target
|
||||||
|
| {unhealthy_target, binary()}
|
||||||
|
| channel_status_map()
|
||||||
|
| term().
|
||||||
|
get_query_mode_and_last_error(RequestResId, Opts = #{connector_resource_id := ResId}) ->
|
||||||
|
do_get_query_mode_error(ResId, RequestResId, Opts);
|
||||||
|
get_query_mode_and_last_error(RequestResId, Opts) ->
|
||||||
|
do_get_query_mode_error(RequestResId, RequestResId, Opts).
|
||||||
|
|
||||||
|
do_get_query_mode_error(ResId, RequestResId, Opts) ->
|
||||||
|
case emqx_resource_manager:lookup_cached(ResId) of
|
||||||
|
{ok, _Group, ResourceData} ->
|
||||||
|
QM = get_query_mode(ResourceData, Opts),
|
||||||
|
Error = get_error(RequestResId, ResourceData),
|
||||||
|
{ok, {QM, Error}};
|
||||||
|
{error, not_found} ->
|
||||||
|
{error, not_found}
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_query_mode(_ResourceData, #{query_mode := QM}) ->
|
||||||
|
QM;
|
||||||
|
get_query_mode(#{query_mode := QM}, _Opts) ->
|
||||||
|
QM.
|
||||||
|
|
||||||
|
get_error(ResId, #{added_channels := #{} = Channels} = ResourceData) when
|
||||||
|
is_map_key(ResId, Channels)
|
||||||
|
->
|
||||||
|
case maps:get(ResId, Channels) of
|
||||||
|
#{error := Error} ->
|
||||||
|
Error;
|
||||||
|
_ ->
|
||||||
|
maps:get(error, ResourceData, undefined)
|
||||||
|
end;
|
||||||
|
get_error(_ResId, #{error := Error}) ->
|
||||||
|
Error.
|
||||||
|
|
||||||
%% Server start/stop callbacks
|
%% Server start/stop callbacks
|
||||||
|
|
||||||
%% @doc Function called from the supervisor to actually start the server
|
%% @doc Function called from the supervisor to actually start the server
|
||||||
|
|
Loading…
Reference in New Issue