test(pulsar): add pulsar action v2 testcase
This commit is contained in:
parent
e9e1daf962
commit
3814203fa2
|
@ -0,0 +1,451 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%--------------------------------------------------------------------
|
||||
-module(emqx_bridge_pulsar_v2_SUITE).
|
||||
|
||||
-compile(nowarn_export_all).
|
||||
-compile(export_all).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||
-include_lib("emqx/include/asserts.hrl").
|
||||
|
||||
-import(emqx_common_test_helpers, [on_exit/1]).
|
||||
|
||||
-define(TYPE, <<"pulsar">>).
|
||||
-define(APPS, [emqx_conf, emqx_resource, emqx_bridge, emqx_rule_engine, emqx_bridge_pulsar]).
|
||||
-define(RULE_TOPIC, "pulsar/rule").
|
||||
-define(RULE_TOPIC_BIN, <<?RULE_TOPIC>>).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% CT boilerplate
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
all() ->
|
||||
[
|
||||
{group, plain},
|
||||
{group, tls}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
AllTCs = emqx_common_test_helpers:all(?MODULE),
|
||||
[
|
||||
{plain, AllTCs},
|
||||
{tls, AllTCs}
|
||||
].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
%% Ensure enterprise bridge module is loaded
|
||||
_ = emqx_bridge_enterprise:module_info(),
|
||||
{ok, Cwd} = file:get_cwd(),
|
||||
PrivDir = ?config(priv_dir, Config),
|
||||
WorkDir = emqx_utils_fs:find_relpath(filename:join(PrivDir, "ebp"), Cwd),
|
||||
Apps = emqx_cth_suite:start(
|
||||
lists:flatten([
|
||||
?APPS,
|
||||
emqx_management,
|
||||
emqx_mgmt_api_test_util:emqx_dashboard()
|
||||
]),
|
||||
#{work_dir => WorkDir}
|
||||
),
|
||||
[{suite_apps, Apps} | Config].
|
||||
|
||||
end_per_suite(Config) ->
|
||||
ok = emqx_cth_suite:stop(?config(suite_apps, Config)).
|
||||
|
||||
init_per_group(plain = Type, Config) ->
|
||||
PulsarHost = os:getenv("PULSAR_PLAIN_HOST", "toxiproxy"),
|
||||
PulsarPort = list_to_integer(os:getenv("PULSAR_PLAIN_PORT", "6652")),
|
||||
ProxyName = "pulsar_plain",
|
||||
case emqx_common_test_helpers:is_tcp_server_available(PulsarHost, PulsarPort) of
|
||||
true ->
|
||||
Config1 = common_init_per_group(),
|
||||
NewConfig =
|
||||
[
|
||||
{proxy_name, ProxyName},
|
||||
{pulsar_host, PulsarHost},
|
||||
{pulsar_port, PulsarPort},
|
||||
{pulsar_type, Type},
|
||||
{use_tls, false}
|
||||
| Config1 ++ Config
|
||||
],
|
||||
create_connector(?MODULE, NewConfig),
|
||||
NewConfig;
|
||||
false ->
|
||||
maybe_skip_without_ci()
|
||||
end;
|
||||
init_per_group(tls = Type, Config) ->
|
||||
PulsarHost = os:getenv("PULSAR_TLS_HOST", "toxiproxy"),
|
||||
PulsarPort = list_to_integer(os:getenv("PULSAR_TLS_PORT", "6653")),
|
||||
ProxyName = "pulsar_tls",
|
||||
case emqx_common_test_helpers:is_tcp_server_available(PulsarHost, PulsarPort) of
|
||||
true ->
|
||||
Config1 = common_init_per_group(),
|
||||
NewConfig =
|
||||
[
|
||||
{proxy_name, ProxyName},
|
||||
{pulsar_host, PulsarHost},
|
||||
{pulsar_port, PulsarPort},
|
||||
{pulsar_type, Type},
|
||||
{use_tls, true}
|
||||
| Config1 ++ Config
|
||||
],
|
||||
create_connector(?MODULE, NewConfig),
|
||||
NewConfig;
|
||||
false ->
|
||||
maybe_skip_without_ci()
|
||||
end.
|
||||
|
||||
end_per_group(Group, Config) when
|
||||
Group =:= plain;
|
||||
Group =:= tls
|
||||
->
|
||||
common_end_per_group(Config),
|
||||
ok.
|
||||
|
||||
common_init_per_group() ->
|
||||
ProxyHost = os:getenv("PROXY_HOST", "toxiproxy"),
|
||||
ProxyPort = list_to_integer(os:getenv("PROXY_PORT", "8474")),
|
||||
emqx_common_test_helpers:reset_proxy(ProxyHost, ProxyPort),
|
||||
UniqueNum = integer_to_binary(erlang:unique_integer()),
|
||||
MQTTTopic = <<"mqtt/topic/", UniqueNum/binary>>,
|
||||
[
|
||||
{proxy_host, ProxyHost},
|
||||
{proxy_port, ProxyPort},
|
||||
{mqtt_topic, MQTTTopic}
|
||||
].
|
||||
|
||||
common_end_per_group(Config) ->
|
||||
ProxyHost = ?config(proxy_host, Config),
|
||||
ProxyPort = ?config(proxy_port, Config),
|
||||
emqx_common_test_helpers:reset_proxy(ProxyHost, ProxyPort),
|
||||
emqx_bridge_v2_testlib:delete_all_bridges_and_connectors(),
|
||||
ok.
|
||||
|
||||
init_per_testcase(TestCase, Config) ->
|
||||
common_init_per_testcase(TestCase, Config).
|
||||
|
||||
end_per_testcase(_Testcase, Config) ->
|
||||
case proplists:get_bool(skip_does_not_apply, Config) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
ok = emqx_config:delete_override_conf_files(),
|
||||
ProxyHost = ?config(proxy_host, Config),
|
||||
ProxyPort = ?config(proxy_port, Config),
|
||||
emqx_common_test_helpers:reset_proxy(ProxyHost, ProxyPort),
|
||||
emqx_bridge_v2_testlib:delete_all_bridges(),
|
||||
stop_consumer(Config),
|
||||
%% in CI, apparently this needs more time since the
|
||||
%% machines struggle with all the containers running...
|
||||
emqx_common_test_helpers:call_janitor(60_000),
|
||||
ok = snabbkaffe:stop(),
|
||||
flush_consumed(),
|
||||
ok
|
||||
end.
|
||||
|
||||
common_init_per_testcase(TestCase, Config0) ->
|
||||
ct:timetrap(timer:seconds(60)),
|
||||
emqx_bridge_v2_testlib:delete_all_bridges(),
|
||||
UniqueNum = integer_to_binary(erlang:unique_integer()),
|
||||
PulsarTopic =
|
||||
<<
|
||||
(atom_to_binary(TestCase))/binary,
|
||||
UniqueNum/binary
|
||||
>>,
|
||||
Config1 = [{pulsar_topic, PulsarTopic} | Config0],
|
||||
ConsumerConfig = start_consumer(TestCase, Config1),
|
||||
Config = ConsumerConfig ++ Config1,
|
||||
ok = snabbkaffe:start_trace(),
|
||||
Config.
|
||||
|
||||
create_connector(Name, Config) ->
|
||||
Connector = pulsar_connector(Config),
|
||||
{ok, _} = emqx_connector:create(?TYPE, Name, Connector).
|
||||
|
||||
delete_connector(Name) ->
|
||||
ok = emqx_connector:remove(?TYPE, Name).
|
||||
|
||||
create_action(Name, Config) ->
|
||||
Action = pulsar_action(Config),
|
||||
{ok, _} = emqx_bridge_v2:create(actions, ?TYPE, Name, Action).
|
||||
|
||||
delete_action(Name) ->
|
||||
ok = emqx_bridge_v2:remove(actions, ?TYPE, Name).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Testcases
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
t_action_probe(Config) ->
|
||||
Name = atom_to_binary(?FUNCTION_NAME),
|
||||
Action = pulsar_action(Config),
|
||||
{ok, Res0} = emqx_bridge_v2_testlib:probe_bridge_api(action, ?TYPE, Name, Action),
|
||||
?assertMatch({{_, 204, _}, _, _}, Res0),
|
||||
ok.
|
||||
|
||||
t_action(Config) ->
|
||||
Name = atom_to_binary(?FUNCTION_NAME),
|
||||
create_action(Name, Config),
|
||||
Actions = emqx_bridge_v2:list(actions),
|
||||
Any = fun(#{name := BName}) -> BName =:= Name end,
|
||||
?assert(lists:any(Any, Actions), Actions),
|
||||
Topic = <<"lkadfdaction">>,
|
||||
{ok, #{id := RuleId}} = emqx_rule_engine:create_rule(
|
||||
#{
|
||||
sql => <<"select * from \"", Topic/binary, "\"">>,
|
||||
id => atom_to_binary(?FUNCTION_NAME),
|
||||
actions => [<<"pulsar:", Name/binary>>],
|
||||
description => <<"bridge_v2 send msg to pulsar action">>
|
||||
}
|
||||
),
|
||||
on_exit(fun() -> emqx_rule_engine:delete_rule(RuleId) end),
|
||||
MQTTClientID = <<"pulsar_mqtt_clientid">>,
|
||||
{ok, C1} = emqtt:start_link([{clean_start, true}, {clientid, MQTTClientID}]),
|
||||
{ok, _} = emqtt:connect(C1),
|
||||
ReqPayload = payload(),
|
||||
ReqPayloadBin = emqx_utils_json:encode(ReqPayload),
|
||||
{ok, _} = emqtt:publish(C1, Topic, #{}, ReqPayloadBin, [{qos, 1}, {retain, false}]),
|
||||
[#{<<"clientid">> := ClientID, <<"payload">> := RespPayload}] = receive_consumed(5000),
|
||||
?assertEqual(MQTTClientID, ClientID),
|
||||
?assertEqual(ReqPayload, emqx_utils_json:decode(RespPayload)),
|
||||
ok = emqtt:disconnect(C1),
|
||||
InstanceId = instance_id(actions, Name),
|
||||
#{counters := Counters} = emqx_resource:get_metrics(InstanceId),
|
||||
ok = delete_action(Name),
|
||||
ActionsAfterDelete = emqx_bridge_v2:list(actions),
|
||||
?assertNot(lists:any(Any, ActionsAfterDelete), ActionsAfterDelete),
|
||||
?assertMatch(
|
||||
#{
|
||||
dropped := 0,
|
||||
success := 1,
|
||||
matched := 1,
|
||||
failed := 0,
|
||||
received := 0
|
||||
},
|
||||
Counters
|
||||
),
|
||||
ok.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Helper fns
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
pulsar_connector(Config) ->
|
||||
PulsarHost = ?config(pulsar_host, Config),
|
||||
PulsarPort = ?config(pulsar_port, Config),
|
||||
UseTLS = proplists:get_value(use_tls, Config, false),
|
||||
Name = atom_to_binary(?MODULE),
|
||||
Prefix =
|
||||
case UseTLS of
|
||||
true -> <<"pulsar+ssl://">>;
|
||||
false -> <<"pulsar://">>
|
||||
end,
|
||||
ServerURL = iolist_to_binary([
|
||||
Prefix,
|
||||
PulsarHost,
|
||||
":",
|
||||
integer_to_binary(PulsarPort)
|
||||
]),
|
||||
Connector = #{
|
||||
<<"connectors">> => #{
|
||||
<<"pulsar">> => #{
|
||||
Name => #{
|
||||
<<"enable">> => true,
|
||||
<<"ssl">> => #{
|
||||
<<"enable">> => UseTLS,
|
||||
<<"verify">> => <<"verify_none">>,
|
||||
<<"server_name_indication">> => <<"auto">>
|
||||
},
|
||||
<<"authentication">> => <<"none">>,
|
||||
<<"servers">> => ServerURL
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
parse_and_check(<<"connectors">>, emqx_connector_schema, Connector, Name).
|
||||
|
||||
pulsar_action(Config) ->
|
||||
Name = atom_to_binary(?MODULE),
|
||||
Action = #{
|
||||
<<"actions">> => #{
|
||||
<<"pulsar">> => #{
|
||||
Name => #{
|
||||
<<"connector">> => Name,
|
||||
<<"enable">> => true,
|
||||
<<"parameters">> => #{
|
||||
<<"retention_period">> => <<"infinity">>,
|
||||
<<"max_batch_bytes">> => <<"1MB">>,
|
||||
<<"batch_size">> => 100,
|
||||
<<"strategy">> => <<"random">>,
|
||||
<<"buffer">> => #{
|
||||
<<"mode">> => <<"memory">>,
|
||||
<<"per_partition_limit">> => <<"10MB">>,
|
||||
<<"segment_bytes">> => <<"5MB">>,
|
||||
<<"memory_overload_protection">> => true
|
||||
},
|
||||
<<"message">> => #{
|
||||
<<"key">> => <<"${.clientid}">>,
|
||||
<<"value">> => <<"${.}">>
|
||||
},
|
||||
<<"pulsar_topic">> => ?config(pulsar_topic, Config)
|
||||
},
|
||||
<<"resource_opts">> => #{
|
||||
<<"health_check_interval">> => <<"1s">>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
parse_and_check(<<"actions">>, emqx_bridge_v2_schema, Action, Name).
|
||||
|
||||
parse_and_check(Key, Mod, Conf, Name) ->
|
||||
ConfStr = hocon_pp:do(Conf, #{}),
|
||||
ct:pal(ConfStr),
|
||||
{ok, RawConf} = hocon:binary(ConfStr, #{format => map}),
|
||||
hocon_tconf:check_plain(Mod, RawConf, #{required => false, atom_key => false}),
|
||||
#{Key := #{<<"pulsar">> := #{Name := RetConf}}} = RawConf,
|
||||
RetConf.
|
||||
|
||||
instance_id(Type, Name) ->
|
||||
ConnectorId = emqx_bridge_resource:resource_id(Type, ?TYPE, Name),
|
||||
BridgeId = emqx_bridge_resource:bridge_id(?TYPE, Name),
|
||||
TypeBin =
|
||||
case Type of
|
||||
sources -> <<"source:">>;
|
||||
actions -> <<"action:">>
|
||||
end,
|
||||
<<TypeBin/binary, BridgeId/binary, ":", ConnectorId/binary>>.
|
||||
|
||||
start_consumer(TestCase, Config) ->
|
||||
PulsarHost = ?config(pulsar_host, Config),
|
||||
PulsarPort = ?config(pulsar_port, Config),
|
||||
PulsarTopic = ?config(pulsar_topic, Config),
|
||||
UseTLS = ?config(use_tls, Config),
|
||||
Scheme =
|
||||
case UseTLS of
|
||||
true -> <<"pulsar+ssl://">>;
|
||||
false -> <<"pulsar://">>
|
||||
end,
|
||||
URL =
|
||||
binary_to_list(
|
||||
<<Scheme/binary, (list_to_binary(PulsarHost))/binary, ":",
|
||||
(integer_to_binary(PulsarPort))/binary>>
|
||||
),
|
||||
ConsumerClientId = list_to_atom(
|
||||
atom_to_list(TestCase) ++ integer_to_list(erlang:unique_integer())
|
||||
),
|
||||
CertsPath = emqx_common_test_helpers:deps_path(emqx, "etc/certs"),
|
||||
SSLOpts = #{
|
||||
enable => UseTLS,
|
||||
keyfile => filename:join([CertsPath, "key.pem"]),
|
||||
certfile => filename:join([CertsPath, "cert.pem"]),
|
||||
cacertfile => filename:join([CertsPath, "cacert.pem"])
|
||||
},
|
||||
Opts = #{enable_ssl => UseTLS, ssl_opts => emqx_tls_lib:to_client_opts(SSLOpts)},
|
||||
{ok, _ClientPid} = pulsar:ensure_supervised_client(ConsumerClientId, [URL], Opts),
|
||||
ConsumerOpts = Opts#{
|
||||
cb_init_args => #{send_to => self()},
|
||||
cb_module => pulsar_echo_consumer,
|
||||
sub_type => 'Shared',
|
||||
subscription => atom_to_list(TestCase) ++ integer_to_list(erlang:unique_integer()),
|
||||
max_consumer_num => 1,
|
||||
%% Note! This must not coincide with the client
|
||||
%% id, or else weird bugs will happen, like the
|
||||
%% consumer never starts...
|
||||
name => list_to_atom("test_consumer" ++ integer_to_list(erlang:unique_integer())),
|
||||
consumer_id => 1
|
||||
},
|
||||
{ok, Consumer} = pulsar:ensure_supervised_consumers(
|
||||
ConsumerClientId,
|
||||
PulsarTopic,
|
||||
ConsumerOpts
|
||||
),
|
||||
%% since connection is async, and there's currently no way to
|
||||
%% specify the subscription initial position as `Earliest', we
|
||||
%% need to wait until the consumer is connected to avoid
|
||||
%% flakiness.
|
||||
ok = wait_until_consumer_connected(Consumer),
|
||||
[
|
||||
{consumer_client_id, ConsumerClientId},
|
||||
{pulsar_consumer, Consumer}
|
||||
].
|
||||
|
||||
stop_consumer(Config) ->
|
||||
ConsumerClientId = ?config(consumer_client_id, Config),
|
||||
Consumer = ?config(pulsar_consumer, Config),
|
||||
ok = pulsar:stop_and_delete_supervised_consumers(Consumer),
|
||||
ok = pulsar:stop_and_delete_supervised_client(ConsumerClientId),
|
||||
ok.
|
||||
|
||||
wait_until_consumer_connected(Consumer) ->
|
||||
?retry(
|
||||
_Sleep = 300,
|
||||
_Attempts0 = 20,
|
||||
true = pulsar_consumers:all_connected(Consumer)
|
||||
),
|
||||
ok.
|
||||
|
||||
wait_until_producer_connected() ->
|
||||
wait_until_connected(pulsar_producers_sup, pulsar_producer).
|
||||
|
||||
wait_until_connected(SupMod, Mod) ->
|
||||
Pids = get_pids(SupMod, Mod),
|
||||
?retry(
|
||||
_Sleep = 300,
|
||||
_Attempts0 = 20,
|
||||
begin
|
||||
true = length(Pids) > 0,
|
||||
lists:foreach(fun(P) -> {connected, _} = sys:get_state(P) end, Pids)
|
||||
end
|
||||
),
|
||||
ok.
|
||||
|
||||
get_pulsar_producers() ->
|
||||
get_pids(pulsar_producers_sup, pulsar_producer).
|
||||
|
||||
get_pids(SupMod, Mod) ->
|
||||
[
|
||||
P
|
||||
|| {_Name, SupPid, _Type, _Mods} <- supervisor:which_children(SupMod),
|
||||
P <- element(2, process_info(SupPid, links)),
|
||||
case proc_lib:initial_call(P) of
|
||||
{Mod, init, _} -> true;
|
||||
_ -> false
|
||||
end
|
||||
].
|
||||
|
||||
receive_consumed(Timeout) ->
|
||||
receive
|
||||
{pulsar_message, #{payloads := Payloads}} ->
|
||||
lists:map(fun try_decode_json/1, Payloads)
|
||||
after Timeout ->
|
||||
ct:pal("mailbox: ~p", [process_info(self(), messages)]),
|
||||
ct:fail("no message consumed")
|
||||
end.
|
||||
|
||||
flush_consumed() ->
|
||||
receive
|
||||
{pulsar_message, _} -> flush_consumed()
|
||||
after 0 -> ok
|
||||
end.
|
||||
|
||||
try_decode_json(Payload) ->
|
||||
case emqx_utils_json:safe_decode(Payload, [return_maps]) of
|
||||
{error, _} ->
|
||||
Payload;
|
||||
{ok, JSON} ->
|
||||
JSON
|
||||
end.
|
||||
|
||||
payload() ->
|
||||
#{<<"key">> => 42, <<"data">> => <<"pulsar">>, <<"timestamp">> => 10000}.
|
||||
|
||||
maybe_skip_without_ci() ->
|
||||
case os:getenv("IS_CI") of
|
||||
"yes" ->
|
||||
throw(no_pulsar);
|
||||
_ ->
|
||||
{skip, no_pulsar}
|
||||
end.
|
Loading…
Reference in New Issue