Merge remote-tracking branch 'origin/master' into fix-buffer-clear-replayq-on-delete-v50

This commit is contained in:
Zaiming (Stone) Shi 2023-01-18 11:39:47 +01:00
commit d4f3b4c8c2
33 changed files with 267 additions and 131 deletions

View File

@ -101,7 +101,7 @@ jobs:
- name: unzip source code - name: unzip source code
run: Expand-Archive -Path source.zip -DestinationPath ./ run: Expand-Archive -Path source.zip -DestinationPath ./
- uses: ilammy/msvc-dev-cmd@v1.12.0 - uses: ilammy/msvc-dev-cmd@v1.12.0
- uses: emqx/setup-beam@v1.16.1-emqx - uses: erlef/setup-beam@v1.15.2
with: with:
otp-version: 24.3.4.6 otp-version: 24.3.4.6
- name: build - name: build

View File

@ -94,7 +94,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: ilammy/msvc-dev-cmd@v1.12.0 - uses: ilammy/msvc-dev-cmd@v1.12.0
- uses: emqx/setup-beam@v1.16.1-emqx - uses: erlef/setup-beam@v1.15.2
with: with:
otp-version: ${{ matrix.otp }} otp-version: ${{ matrix.otp }}
- name: build - name: build

View File

@ -57,7 +57,7 @@ jobs:
arch: arch:
- amd64 - amd64
steps: steps:
- uses: emqx/setup-beam@v1.16.1-emqx - uses: erlef/setup-beam@v1.15.2
with: with:
otp-version: 24.3.4.6 otp-version: 24.3.4.6
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
@ -132,7 +132,7 @@ jobs:
# - emqx-enterprise # TODO test enterprise # - emqx-enterprise # TODO test enterprise
steps: steps:
- uses: emqx/setup-beam@v1.16.1-emqx - uses: erlef/setup-beam@v1.15.2
with: with:
otp-version: 24.3.4.6 otp-version: 24.3.4.6
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3

View File

@ -14,7 +14,7 @@ jobs:
outputs: outputs:
version: ${{ steps.build_docker.outputs.version}} version: ${{ steps.build_docker.outputs.version}}
steps: steps:
- uses: emqx/setup-beam@v1.16.1-emqx - uses: erlef/setup-beam@v1.15.2
with: with:
otp-version: 24.3.4.6 otp-version: 24.3.4.6
- name: download jmeter - name: download jmeter
@ -57,7 +57,7 @@ jobs:
needs: build_emqx_for_jmeter_tests needs: build_emqx_for_jmeter_tests
steps: steps:
- uses: emqx/setup-beam@v1.16.1-emqx - uses: erlef/setup-beam@v1.15.2
with: with:
otp-version: 24.3.4.6 otp-version: 24.3.4.6
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -126,7 +126,7 @@ jobs:
- name: check logs - name: check logs
run: | run: |
if cat jmeter_logs/${{ matrix.scripts_type }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then if cat jmeter_logs/${{ matrix.scripts_type }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then
echo "check logs filed" echo "check logs failed"
exit 1 exit 1
fi fi
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
@ -153,7 +153,7 @@ jobs:
needs: build_emqx_for_jmeter_tests needs: build_emqx_for_jmeter_tests
steps: steps:
- uses: emqx/setup-beam@v1.16.1-emqx - uses: erlef/setup-beam@v1.15.2
with: with:
otp-version: 24.3.4.6 otp-version: 24.3.4.6
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -235,7 +235,7 @@ jobs:
- name: check logs - name: check logs
run: | run: |
if cat jmeter_logs/${{ matrix.scripts_type }}_${{ matrix.pgsql_tag }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then if cat jmeter_logs/${{ matrix.scripts_type }}_${{ matrix.pgsql_tag }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then
echo "check logs filed" echo "check logs failed"
exit 1 exit 1
fi fi
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
@ -259,7 +259,7 @@ jobs:
needs: build_emqx_for_jmeter_tests needs: build_emqx_for_jmeter_tests
steps: steps:
- uses: emqx/setup-beam@v1.16.1-emqx - uses: erlef/setup-beam@v1.15.2
with: with:
otp-version: 24.3.4.6 otp-version: 24.3.4.6
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -341,7 +341,7 @@ jobs:
- name: check logs - name: check logs
run: | run: |
if cat jmeter_logs/${{ matrix.scripts_type }}_${{ matrix.mysql_tag }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then if cat jmeter_logs/${{ matrix.scripts_type }}_${{ matrix.mysql_tag }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then
echo "check logs filed" echo "check logs failed"
exit 1 exit 1
fi fi
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
@ -361,7 +361,7 @@ jobs:
needs: build_emqx_for_jmeter_tests needs: build_emqx_for_jmeter_tests
steps: steps:
- uses: emqx/setup-beam@v1.16.1-emqx - uses: erlef/setup-beam@v1.15.2
with: with:
otp-version: 24.3.4.6 otp-version: 24.3.4.6
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -439,7 +439,7 @@ jobs:
- name: check logs - name: check logs
run: | run: |
if cat jmeter_logs/${{ matrix.scripts_type }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then if cat jmeter_logs/${{ matrix.scripts_type }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then
echo "check logs filed" echo "check logs failed"
exit 1 exit 1
fi fi
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
@ -460,7 +460,7 @@ jobs:
needs: build_emqx_for_jmeter_tests needs: build_emqx_for_jmeter_tests
steps: steps:
- uses: emqx/setup-beam@v1.16.1-emqx - uses: erlef/setup-beam@v1.15.2
with: with:
otp-version: 24.3.4.6 otp-version: 24.3.4.6
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -531,7 +531,7 @@ jobs:
- name: check logs - name: check logs
run: | run: |
if cat jmeter_logs/${{ matrix.scripts_type }}_${{ matrix.mysql_tag }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then if cat jmeter_logs/${{ matrix.scripts_type }}_${{ matrix.mysql_tag }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then
echo "check logs filed" echo "check logs failed"
exit 1 exit 1
fi fi
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3

View File

@ -71,7 +71,7 @@ jobs:
shell: bash shell: bash
steps: steps:
# setup Erlang to run lux # setup Erlang to run lux
- uses: emqx/setup-beam@v1.16.1-emqx - uses: erlef/setup-beam@v1.15.2
with: with:
otp-version: 24.3.4.6 otp-version: 24.3.4.6
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@ -24,9 +24,6 @@
## Sets the maximum number of atoms the virtual machine can handle. ## Sets the maximum number of atoms the virtual machine can handle.
#+t 1048576 #+t 1048576
## Set the location of crash dumps
#-env ERL_CRASH_DUMP {{ platform_log_dir }}/crash.dump
## Set how many times generational garbages collections can be done without ## Set how many times generational garbages collections can be done without
## forcing a fullsweep collection. ## forcing a fullsweep collection.
-env ERL_FULLSWEEP_AFTER 1000 -env ERL_FULLSWEEP_AFTER 1000
@ -40,11 +37,6 @@
## Prevent user from accidentally calling a function from the prompt that could harm a running system. ## Prevent user from accidentally calling a function from the prompt that could harm a running system.
-stdlib restricted_shell emqx_restricted_shell -stdlib restricted_shell emqx_restricted_shell
## Specifies the net_kernel tick time in seconds.
## This is the approximate time a connected node may be unresponsive until
## it is considered down and thereby disconnected.
-kernel net_ticktime 120
## Sets the distribution buffer busy limit (dist_buf_busy_limit). ## Sets the distribution buffer busy limit (dist_buf_busy_limit).
## Preferably set in `emqx.conf`, ## Preferably set in `emqx.conf`,
#+zdbbl 8192 #+zdbbl 8192

View File

@ -327,9 +327,9 @@ atom(Bin) -> binary_to_existing_atom(Bin, utf8).
certs_dir(ChainName, ConfigOrID) -> certs_dir(ChainName, ConfigOrID) ->
DirName = dir(ChainName, ConfigOrID), DirName = dir(ChainName, ConfigOrID),
SubDir = iolist_to_binary(filename:join(["authn", DirName])), SubDir = iolist_to_binary(filename:join(["authn", DirName])),
binary:replace(SubDir, <<":">>, <<"-">>, [global]). emqx_misc:safe_filename(SubDir).
dir(ChainName, ID) when is_binary(ID) -> dir(ChainName, ID) when is_binary(ID) ->
binary:replace(iolist_to_binary([to_bin(ChainName), "-", ID]), <<":">>, <<"-">>); emqx_misc:safe_filename(iolist_to_binary([to_bin(ChainName), "-", ID]));
dir(ChainName, Config) when is_map(Config) -> dir(ChainName, Config) when is_map(Config) ->
dir(ChainName, authenticator_id(Config)). dir(ChainName, authenticator_id(Config)).

View File

@ -55,7 +55,8 @@
readable_error_msg/1, readable_error_msg/1,
safe_to_existing_atom/1, safe_to_existing_atom/1,
safe_to_existing_atom/2, safe_to_existing_atom/2,
pub_props_to_packet/1 pub_props_to_packet/1,
safe_filename/1
]). ]).
-export([ -export([
@ -708,3 +709,11 @@ pub_props_to_packet(Properties) ->
true true
end, end,
maps:filtermap(F, Properties). maps:filtermap(F, Properties).
%% fix filename by replacing characters which could be invalid on some filesystems
%% with safe ones
-spec safe_filename(binary() | unicode:chardata()) -> binary() | [unicode:chardata()].
safe_filename(Filename) when is_binary(Filename) ->
binary:replace(Filename, <<":">>, <<"-">>, [global]);
safe_filename(Filename) when is_list(Filename) ->
string:replace(Filename, ":", "-", all).

View File

@ -10,7 +10,16 @@ emqx_bridge_webhook_schema {
zh: "启用/禁用 Bridge" zh: "启用/禁用 Bridge"
} }
} }
config_direction {
desc {
en: """Deprecated, The direction of this bridge, MUST be 'egress'"""
zh: """已废弃Bridge 的方向,必须是 egress"""
}
label: {
en: "Bridge Direction"
zh: "Bridge 方向"
}
}
config_url { config_url {
desc { desc {
en: """ en: """

View File

@ -235,7 +235,9 @@ lookup(Type, Name, RawConf) ->
end. end.
maybe_upgrade(mqtt, Config) -> maybe_upgrade(mqtt, Config) ->
emqx_bridge_mqtt_config:maybe_upgrade(Config); emqx_bridge_compatible_config:maybe_upgrade(Config);
maybe_upgrade(webhook, Config) ->
emqx_bridge_compatible_config:webhook_maybe_upgrade(Config);
maybe_upgrade(_Other, Config) -> maybe_upgrade(_Other, Config) ->
Config. Config.

View File

@ -216,7 +216,8 @@ recreate(Type, Name, Conf, Opts) ->
). ).
create_dry_run(Type, Conf0) -> create_dry_run(Type, Conf0) ->
TmpPath = iolist_to_binary(["bridges-create-dry-run:", emqx_misc:gen_id(8)]), TmpPath0 = iolist_to_binary(["bridges-create-dry-run:", emqx_misc:gen_id(8)]),
TmpPath = emqx_misc:safe_filename(TmpPath0),
Conf = emqx_map_lib:safe_atom_key_map(Conf0), Conf = emqx_map_lib:safe_atom_key_map(Conf0),
case emqx_connector_ssl:convert_certs(TmpPath, Conf) of case emqx_connector_ssl:convert_certs(TmpPath, Conf) of
{error, Reason} -> {error, Reason} ->
@ -251,7 +252,9 @@ maybe_clear_certs(TmpPath, #{ssl := SslConf} = Conf) ->
case is_tmp_path_conf(TmpPath, SslConf) of case is_tmp_path_conf(TmpPath, SslConf) of
true -> emqx_connector_ssl:clear_certs(TmpPath, Conf); true -> emqx_connector_ssl:clear_certs(TmpPath, Conf);
false -> ok false -> ok
end. end;
maybe_clear_certs(_TmpPath, _ConfWithoutSsl) ->
ok.
is_tmp_path_conf(TmpPath, #{certfile := Certfile}) -> is_tmp_path_conf(TmpPath, #{certfile := Certfile}) ->
is_tmp_path(TmpPath, Certfile); is_tmp_path(TmpPath, Certfile);

View File

@ -15,22 +15,23 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc This module was created to convert old version (from v5.0.0 to v5.0.11) %% @doc This module was created to convert old version (from v5.0.0 to v5.0.11)
%% mqtt connector configs to newer version (developed for enterprise edition). %% mqtt/webhook connector configs to newer version (developed for enterprise edition).
-module(emqx_bridge_mqtt_config). -module(emqx_bridge_compatible_config).
-export([ -export([
upgrade_pre_ee/1, upgrade_pre_ee/2,
maybe_upgrade/1 maybe_upgrade/1,
webhook_maybe_upgrade/1
]). ]).
upgrade_pre_ee(undefined) -> upgrade_pre_ee(undefined, _UpgradeFunc) ->
undefined; undefined;
upgrade_pre_ee(Conf0) when is_map(Conf0) -> upgrade_pre_ee(Conf0, UpgradeFunc) when is_map(Conf0) ->
maps:from_list(upgrade_pre_ee(maps:to_list(Conf0))); maps:from_list(upgrade_pre_ee(maps:to_list(Conf0), UpgradeFunc));
upgrade_pre_ee([]) -> upgrade_pre_ee([], _UpgradeFunc) ->
[]; [];
upgrade_pre_ee([{Name, Config} | Bridges]) -> upgrade_pre_ee([{Name, Config} | Bridges], UpgradeFunc) ->
[{Name, maybe_upgrade(Config)} | upgrade_pre_ee(Bridges)]. [{Name, UpgradeFunc(Config)} | upgrade_pre_ee(Bridges, UpgradeFunc)].
maybe_upgrade(#{<<"connector">> := _} = Config0) -> maybe_upgrade(#{<<"connector">> := _} = Config0) ->
Config1 = up(Config0), Config1 = up(Config0),
@ -39,6 +40,12 @@ maybe_upgrade(#{<<"connector">> := _} = Config0) ->
maybe_upgrade(NewVersion) -> maybe_upgrade(NewVersion) ->
NewVersion. NewVersion.
webhook_maybe_upgrade(#{<<"direction">> := _} = Config0) ->
Config1 = maps:remove(<<"direction">>, Config0),
Config1#{<<"resource_opts">> => default_resource_opts()};
webhook_maybe_upgrade(NewVersion) ->
NewVersion.
binary_key({K, V}) -> binary_key({K, V}) ->
{atom_to_binary(K, utf8), V}. {atom_to_binary(K, utf8), V}.

View File

@ -121,7 +121,12 @@ fields(bridges) ->
hoconsc:map(name, ref(emqx_bridge_webhook_schema, "config")), hoconsc:map(name, ref(emqx_bridge_webhook_schema, "config")),
#{ #{
desc => ?DESC("bridges_webhook"), desc => ?DESC("bridges_webhook"),
required => false required => false,
converter => fun(X, _HoconOpts) ->
emqx_bridge_compatible_config:upgrade_pre_ee(
X, fun emqx_bridge_compatible_config:webhook_maybe_upgrade/1
)
end
} }
)}, )},
{mqtt, {mqtt,
@ -131,7 +136,9 @@ fields(bridges) ->
desc => ?DESC("bridges_mqtt"), desc => ?DESC("bridges_mqtt"),
required => false, required => false,
converter => fun(X, _HoconOpts) -> converter => fun(X, _HoconOpts) ->
emqx_bridge_mqtt_config:upgrade_pre_ee(X) emqx_bridge_compatible_config:upgrade_pre_ee(
X, fun emqx_bridge_compatible_config:maybe_upgrade/1
)
end end
} }
)} )}

View File

@ -81,6 +81,15 @@ request_config() ->
desc => ?DESC("config_url") desc => ?DESC("config_url")
} }
)}, )},
{direction,
mk(
egress,
#{
desc => ?DESC("config_direction"),
required => {false, recursively},
deprecated => {since, "5.0.12"}
}
)},
{local_topic, {local_topic,
mk( mk(
binary(), binary(),

View File

@ -86,6 +86,7 @@ init_per_testcase(_, Config) ->
{ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
{Port, Sock, Acceptor} = start_http_server(fun handle_fun_200_ok/2), {Port, Sock, Acceptor} = start_http_server(fun handle_fun_200_ok/2),
[{port, Port}, {sock, Sock}, {acceptor, Acceptor} | Config]. [{port, Port}, {sock, Sock}, {acceptor, Acceptor} | Config].
end_per_testcase(_, Config) -> end_per_testcase(_, Config) ->
Sock = ?config(sock, Config), Sock = ?config(sock, Config),
Acceptor = ?config(acceptor, Config), Acceptor = ?config(acceptor, Config),

View File

@ -13,7 +13,7 @@
%% limitations under the License. %% limitations under the License.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-module(emqx_bridge_mqtt_config_tests). -module(emqx_bridge_compatible_config_tests).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
@ -26,30 +26,54 @@ empty_config_test() ->
%% ensure webhook config can be checked %% ensure webhook config can be checked
webhook_config_test() -> webhook_config_test() ->
Conf = parse(webhook_v5011_hocon()), Conf1 = parse(webhook_v5011_hocon()),
Conf2 = parse(full_webhook_v5011_hocon()),
?assertMatch( ?assertMatch(
#{ #{
<<"bridges">> := <<"bridges">> := #{
#{ <<"webhook">> := #{
<<"webhook">> := #{ <<"the_name">> :=
<<"the_name">> := #{
#{ <<"method">> := get,
<<"method">> := get, <<"body">> := <<"${payload}">>
<<"body">> := <<"${payload}">> }
}
}
} }
}
}, },
check(Conf) check(Conf1)
), ),
?assertMatch(
#{
<<"bridges">> := #{
<<"webhook">> := #{
<<"the_name">> :=
#{
<<"method">> := get,
<<"body">> := <<"${payload}">>
}
}
}
},
check(Conf2)
),
ok. ok.
up(#{<<"bridges">> := Bridges0} = Conf0) -> up(#{<<"bridges">> := Bridges0} = Conf0) ->
Bridges = up(Bridges0), Bridges = up(Bridges0),
Conf0#{<<"bridges">> := Bridges}; Conf0#{<<"bridges">> := Bridges};
up(#{<<"mqtt">> := MqttBridges0} = Bridges) -> up(#{<<"mqtt">> := MqttBridges0} = Bridges) ->
MqttBridges = emqx_bridge_mqtt_config:upgrade_pre_ee(MqttBridges0), MqttBridges = emqx_bridge_compatible_config:upgrade_pre_ee(
Bridges#{<<"mqtt">> := MqttBridges}. MqttBridges0, fun emqx_bridge_compatible_config:maybe_upgrade/1
),
Bridges#{<<"mqtt">> := MqttBridges};
up(#{<<"webhook">> := WebhookBridges0} = Bridges) ->
WebhookBridges = emqx_bridge_compatible_config:upgrade_pre_ee(
WebhookBridges0, fun emqx_bridge_compatible_config:webhook_maybe_upgrade/1
),
Bridges#{<<"webhook">> := WebhookBridges}.
parse(HOCON) -> parse(HOCON) ->
{ok, Conf} = hocon:binary(HOCON), {ok, Conf} = hocon:binary(HOCON),
@ -108,6 +132,38 @@ bridges{
} }
""". """.
full_webhook_v5011_hocon() ->
""
"\n"
"bridges{\n"
" webhook {\n"
" the_name{\n"
" body = \"${payload}\"\n"
" connect_timeout = \"5s\"\n"
" direction = \"egress\"\n"
" enable_pipelining = 100\n"
" headers {\"content-type\" = \"application/json\"}\n"
" max_retries = 3\n"
" method = \"get\"\n"
" pool_size = 4\n"
" pool_type = \"random\"\n"
" request_timeout = \"5s\"\n"
" ssl {\n"
" ciphers = \"\"\n"
" depth = 10\n"
" enable = false\n"
" reuse_sessions = true\n"
" secure_renegotiate = true\n"
" user_lookup_fun = \"emqx_tls_psk:lookup\"\n"
" verify = \"verify_peer\"\n"
" versions = [\"tlsv1.3\", \"tlsv1.2\", \"tlsv1.1\", \"tlsv1\"]\n"
" }\n"
" url = \"http://localhost:8080\"\n"
" }\n"
" }\n"
"}\n"
"".
%% erlfmt-ignore %% erlfmt-ignore
%% this is a generated from v5.0.11 %% this is a generated from v5.0.11
mqtt_v5011_hocon() -> mqtt_v5011_hocon() ->

View File

@ -480,8 +480,16 @@ the old dir should be deleted first.<br/>
node_crash_dump_seconds { node_crash_dump_seconds {
desc { desc {
en: """The number of seconds that the broker is allowed to spend writing a crash dump.""" en: """This variable gives the number of seconds that the emulator is allowed to spend writing a crash dump. When the given number of seconds have elapsed, the emulator is terminated.
zh: """保存崩溃文件最大允许时间,如果文件太大,在规则时间内没有保存完成,则会直接结束。""" - If setting to 0 seconds, the runtime system does not even attempt to write the crash dump file. It only terminates.
- If setting to a positive value S, wait for S seconds to complete the crash dump file and then terminates the runtime system with a SIGALRM signal.
- A negative value causes the termination of the runtime system to wait indefinitely until the crash dump file has been completely written.
"""
zh: """该配置给出了运行时系统允许花费的写入崩溃转储的秒数。当给定的秒数已经过去,运行时系统将被终止。
- 如果设置为0秒运行时会立即终止不会尝试写入崩溃转储文件。
- 如果设置为一个正数 S节点会等待 S 秒来完成崩溃转储文件然后用SIGALRM信号终止运行时系统。
- 如果设置为一个负值导致运行时系统的终止等待无限期地直到崩溃转储文件已经完全写入。
"""
} }
label { label {
en: "Crash Dump Seconds" en: "Crash Dump Seconds"
@ -491,10 +499,14 @@ the old dir should be deleted first.<br/>
node_crash_dump_bytes { node_crash_dump_bytes {
desc { desc {
en: """The maximum size of a crash dump file in bytes.""" en: """This variable sets the maximum size of a crash dump file in bytes.
The crash dump will be truncated if this limit is exceeded.
If setting it to 0, the runtime system does not even attempt to write a crash dump file.
"""
zh: """限制崩溃文件的大小,当崩溃时节点内存太大, zh: """限制崩溃文件的大小,当崩溃时节点内存太大,
如果为了保存现场,需要全部存到崩溃文件中,此处限制最多能保存多大的文件。 如果为了保存现场,需要全部存到崩溃文件中,此处限制最多能保存多大的文件。
""" 如果超过此限制崩溃转储将被截断。如果设置为0系统不会尝试写入崩溃转储文件。
"""
} }
label { label {
en: "Crash Dump Bytes" en: "Crash Dump Bytes"

View File

@ -487,7 +487,7 @@ fields("node") ->
#{ #{
mapping => "vm_args.-env ERL_CRASH_DUMP", mapping => "vm_args.-env ERL_CRASH_DUMP",
desc => ?DESC(node_crash_dump_file), desc => ?DESC(node_crash_dump_file),
default => "log/erl_crash.dump", default => crash_dump_file_default(),
'readOnly' => true 'readOnly' => true
} }
)}, )},
@ -1296,6 +1296,15 @@ sort_log_levels(Levels) ->
Levels Levels
). ).
crash_dump_file_default() ->
case os:getenv("RUNNER_LOG_DIR") of
false ->
%% testing, or running emqx app as deps
"log/erl_crash.dump";
Dir ->
[filename:join([Dir, "erl_crash.dump"])]
end.
%% utils %% utils
-spec conf_get(string() | [string()], hocon:config()) -> term(). -spec conf_get(string() | [string()], hocon:config()) -> term().
conf_get(Key, Conf) -> conf_get(Key, Conf) ->

View File

@ -31,7 +31,7 @@ emqx_dashboard_api {
license { license {
desc { desc {
en: """EMQX License. opensource or enterprise""" en: """EMQX License. opensource or enterprise"""
zh: """EMQX 许可。开源版本 或者企业版""" zh: """EMQX 许可类型。可为 opensource 或 enterprise"""
} }
} }
@ -44,15 +44,15 @@ emqx_dashboard_api {
login_api { login_api {
desc { desc {
en: """Dashboard Auth. Get Token""" en: """Get Dashboard Auth Token."""
zh: """Dashboard 认证。获取 Token""" zh: """获取 Dashboard 认证 Token"""
} }
} }
login_success { login_success {
desc { desc {
en: """Dashboard Auth. Success""" en: """Dashboard Auth Success"""
zh: """Dashboard 认证成功""" zh: """Dashboard 认证成功"""
} }
} }

View File

@ -8,7 +8,10 @@ For example, an HTTP listener can listen on all configured IP addresses
on a given port for a machine by specifying the IP address 0.0.0.0. on a given port for a machine by specifying the IP address 0.0.0.0.
Alternatively, the HTTP listener can specify a unique IP address for each listener, Alternatively, the HTTP listener can specify a unique IP address for each listener,
but use the same port.""" but use the same port."""
zh: """仪表盘监听器设置。""" zh: """Dashboard 监听器设置。监听器必须有唯一的端口号和IP地址的组合。
例如可以通过指定IP地址 0.0.0.0 来监听机器上给定端口上的所有配置的IP地址。
或者可以为每个监听器指定唯一的IP地址但使用相同的端口。
"""
} }
label { label {
en: "Listeners" en: "Listeners"
@ -18,14 +21,14 @@ but use the same port."""
sample_interval { sample_interval {
desc { desc {
en: """How often to update metrics displayed in the dashboard. en: """How often to update metrics displayed in the dashboard.
Note: `sample_interval` should be a divisor of 60.""" Note: `sample_interval` should be a divisor of 60, default is 10s."""
zh: """更新仪表板中显示的指标的时间间隔。必须小于60且被60的整除。""" zh: """Dashboard 中图表指标的时间间隔。必须小于60且被60的整除,默认设置 10s。"""
} }
} }
token_expired_time { token_expired_time {
desc { desc {
en: "JWT token expiration time." en: "JWT token expiration time. Default is 60 minutes"
zh: "JWT token 过期时间" zh: "JWT token 过期时间。默认设置为 60 分钟。"
} }
label { label {
en: "Token expired time" en: "Token expired time"
@ -34,8 +37,8 @@ Note: `sample_interval` should be a divisor of 60."""
} }
num_acceptors { num_acceptors {
desc { desc {
en: "Socket acceptor pool size for TCP protocols." en: "Socket acceptor pool size for TCP protocols. Default is the number of schedulers online"
zh: "TCP协议的Socket acceptor池大小" zh: "TCP协议的Socket acceptor池大小, 默认设置在线的调度器数量(通常为 CPU 核数)"
} }
label { label {
en: "Number of acceptors" en: "Number of acceptors"
@ -45,7 +48,7 @@ Note: `sample_interval` should be a divisor of 60."""
max_connections { max_connections {
desc { desc {
en: "Maximum number of simultaneous connections." en: "Maximum number of simultaneous connections."
zh: "同时处理的最大连接数" zh: "同时处理的最大连接数"
} }
label { label {
en: "Maximum connections" en: "Maximum connections"
@ -55,7 +58,7 @@ Note: `sample_interval` should be a divisor of 60."""
backlog { backlog {
desc { desc {
en: "Defines the maximum length that the queue of pending connections can grow to." en: "Defines the maximum length that the queue of pending connections can grow to."
zh: "排队等待连接的队列的最大长度" zh: "排队等待连接的队列的最大长度"
} }
label { label {
en: "Backlog" en: "Backlog"
@ -65,7 +68,7 @@ Note: `sample_interval` should be a divisor of 60."""
send_timeout { send_timeout {
desc { desc {
en: "Send timeout for the socket." en: "Send timeout for the socket."
zh: "Socket发送超时时间" zh: "Socket发送超时时间"
} }
label { label {
en: "Send timeout" en: "Send timeout"
@ -75,7 +78,7 @@ Note: `sample_interval` should be a divisor of 60."""
inet6 { inet6 {
desc { desc {
en: "Enable IPv6 support, default is false, which means IPv4 only." en: "Enable IPv6 support, default is false, which means IPv4 only."
zh: "启用IPv6 如果机器不支持IPv6请关闭此选项否则会导致仪表盘无法使用。" zh: "启用IPv6 如果机器不支持IPv6请关闭此选项否则会导致 Dashboard 无法使用。"
} }
label { label {
en: "IPv6" en: "IPv6"
@ -84,7 +87,8 @@ Note: `sample_interval` should be a divisor of 60."""
} }
ipv6_v6only { ipv6_v6only {
desc { desc {
en: "Disable IPv4-to-IPv6 mapping for the listener." en: """Disable IPv4-to-IPv6 mapping for the listener.
The configuration is only valid when the inet6 is true."""
zh: "当开启 inet6 功能的同时禁用 IPv4-to-IPv6 映射。该配置仅在 inet6 功能开启时有效。" zh: "当开启 inet6 功能的同时禁用 IPv4-to-IPv6 映射。该配置仅在 inet6 功能开启时有效。"
} }
label { label {
@ -95,17 +99,17 @@ Note: `sample_interval` should be a divisor of 60."""
desc_dashboard { desc_dashboard {
desc { desc {
en: "Configuration for EMQX dashboard." en: "Configuration for EMQX dashboard."
zh: "EMQX仪表板配置" zh: "EMQX Dashboard 配置。"
} }
label { label {
en: "Dashboard" en: "Dashboard"
zh: "仪表板" zh: "Dashboard"
} }
} }
desc_listeners { desc_listeners {
desc { desc {
en: "Configuration for the dashboard listener." en: "Configuration for the dashboard listener."
zh: "仪表板监听器配置" zh: "Dashboard 监听器配置。"
} }
label { label {
en: "Listeners" en: "Listeners"
@ -115,7 +119,7 @@ Note: `sample_interval` should be a divisor of 60."""
desc_http { desc_http {
desc { desc {
en: "Configuration for the dashboard listener (plaintext)." en: "Configuration for the dashboard listener (plaintext)."
zh: "仪表板监听器(HTTP)配置" zh: "Dashboard 监听器(HTTP)配置。"
} }
label { label {
en: "HTTP" en: "HTTP"
@ -125,7 +129,7 @@ Note: `sample_interval` should be a divisor of 60."""
desc_https { desc_https {
desc { desc {
en: "Configuration for the dashboard listener (TLS)." en: "Configuration for the dashboard listener (TLS)."
zh: "仪表板监听器(HTTPS)配置" zh: "Dashboard 监听器(HTTPS)配置。"
} }
label { label {
en: "HTTPS" en: "HTTPS"
@ -135,7 +139,7 @@ Note: `sample_interval` should be a divisor of 60."""
listener_enable { listener_enable {
desc { desc {
en: "Ignore or enable this listener" en: "Ignore or enable this listener"
zh: "忽略或启用该监听器配置" zh: "忽略或启用该监听器"
} }
label { label {
en: "Enable" en: "Enable"
@ -145,7 +149,7 @@ Note: `sample_interval` should be a divisor of 60."""
bind { bind {
desc { desc {
en: "Port without IP(18083) or port with specified IP(127.0.0.1:18083)." en: "Port without IP(18083) or port with specified IP(127.0.0.1:18083)."
zh: "监听的地址与端口在dashboard更新此配置时会重启dashboard服务。" zh: "监听地址和端口,热更新此配置时,会重启 Dashboard 服务。"
} }
label { label {
en: "Bind" en: "Bind"
@ -155,7 +159,7 @@ Note: `sample_interval` should be a divisor of 60."""
default_username { default_username {
desc { desc {
en: "The default username of the automatically created dashboard user." en: "The default username of the automatically created dashboard user."
zh: "默认的仪表板用户名" zh: "Dashboard 的默认用户名"
} }
label { label {
en: "Default username" en: "Default username"
@ -165,9 +169,12 @@ Note: `sample_interval` should be a divisor of 60."""
default_password { default_password {
desc { desc {
en: """The initial default password for dashboard 'admin' user. en: """The initial default password for dashboard 'admin' user.
For safety, it should be changed as soon as possible.""" For safety, it should be changed as soon as possible.
zh: """默认的仪表板用户密码 This value is not valid when you log in to Dashboard for the first time via the web
为了安全,应该尽快修改密码。""" and change to a complex password as prompted.
"""
zh: """Dashboard 的默认密码,为了安全,应该尽快修改密码。
当通过网页首次登录 Dashboard 并按提示修改成复杂密码后,此值就会失效。"""
} }
label { label {
en: "Default password" en: "Default password"
@ -179,7 +186,7 @@ For safety, it should be changed as soon as possible."""
en: """Support Cross-Origin Resource Sharing (CORS). en: """Support Cross-Origin Resource Sharing (CORS).
Allows a server to indicate any origins (domain, scheme, or port) other than Allows a server to indicate any origins (domain, scheme, or port) other than
its own from which a browser should permit loading resources.""" its own from which a browser should permit loading resources."""
zh: """支持跨域资源共享(CORS) zh: """支持跨域资源共享(CORS)
允许服务器指示任何来源(域名、协议或端口),除了本服务器之外的任何浏览器应允许加载资源。""" 允许服务器指示任何来源(域名、协议或端口),除了本服务器之外的任何浏览器应允许加载资源。"""
} }
label { label {
@ -190,7 +197,7 @@ its own from which a browser should permit loading resources."""
i18n_lang { i18n_lang {
desc { desc {
en: "Internationalization language support." en: "Internationalization language support."
zh: "swagger多语言支持" zh: "设置 Swagger 多语言的版本,可为 en 或 zh。"
} }
label { label {
en: "I18n language" en: "I18n language"
@ -199,8 +206,8 @@ its own from which a browser should permit loading resources."""
} }
bootstrap_users_file { bootstrap_users_file {
desc { desc {
en: "Deprecated, use api_key.bootstrap_file" en: "Deprecated, use api_key.bootstrap_file."
zh: "已废弃,请使用 api_key.bootstrap_file" zh: "已废弃,请使用 api_key.bootstrap_file"
} }
label { label {
en: """Deprecated""" en: """Deprecated"""

View File

@ -117,7 +117,7 @@ common_listener_fields() ->
?HOCON( ?HOCON(
integer(), integer(),
#{ #{
default => 4, default => erlang:system_info(schedulers_online),
desc => ?DESC(num_acceptors) desc => ?DESC(num_acceptors)
} }
)}, )},
@ -141,7 +141,7 @@ common_listener_fields() ->
?HOCON( ?HOCON(
emqx_schema:duration(), emqx_schema:duration(),
#{ #{
default => "5s", default => "10s",
desc => ?DESC(send_timeout) desc => ?DESC(send_timeout)
} }
)}, )},

View File

@ -3,7 +3,7 @@
{id, "emqx_machine"}, {id, "emqx_machine"},
{description, "The EMQX Machine"}, {description, "The EMQX Machine"},
% strict semver, bump manually! % strict semver, bump manually!
{vsn, "0.1.2"}, {vsn, "0.1.3"},
{modules, []}, {modules, []},
{registered, []}, {registered, []},
{applications, [kernel, stdlib]}, {applications, [kernel, stdlib]},

View File

@ -26,6 +26,14 @@
%% API %% API
-export([lock/0, unlock/0]). -export([lock/0, unlock/0]).
-export([t/1, t2/1, t/2, t2/2, t/3, t2/3]).
lock() -> emqx_restricted_shell:lock(). lock() -> emqx_restricted_shell:lock().
unlock() -> emqx_restricted_shell:unlock(). unlock() -> emqx_restricted_shell:unlock().
t(M) -> recon_trace:calls({M, '_', return_trace}, 300).
t2(M) -> recon_trace:calls({M, '_', return_trace}, 300, [{args, arity}]).
t(M, F) -> recon_trace:calls({M, F, return_trace}, 300).
t2(M, F) -> recon_trace:calls({M, F, return_trace}, 300, [{args, arity}]).
t(M, F, A) -> recon_trace:calls({M, F, A}, 300).
t2(M, F, A) -> recon_trace:calls({M, F, A}, 300, [{args, arity}]).

View File

@ -171,7 +171,7 @@ create_dry_run(ResourceType, Config) ->
ok = emqx_resource_manager_sup:ensure_child( ok = emqx_resource_manager_sup:ensure_child(
MgrId, ResId, <<"dry_run">>, ResourceType, Config, #{} MgrId, ResId, <<"dry_run">>, ResourceType, Config, #{}
), ),
case wait_for_ready(ResId, 15000) of case wait_for_ready(ResId, 5000) of
ok -> ok ->
remove(ResId); remove(ResId);
{error, Reason} -> {error, Reason} ->

View File

@ -1167,12 +1167,9 @@ queue_count(Q) ->
replayq:count(Q). replayq:count(Q).
disk_queue_dir(Id, Index) -> disk_queue_dir(Id, Index) ->
QDir0 = binary_to_list(Id) ++ "_" ++ integer_to_list(Index), QDir0 = binary_to_list(Id) ++ ":" ++ integer_to_list(Index),
QDir = sanitize_file_path(QDir0), QDir = filename:join([emqx:data_dir(), "bufs", node(), QDir0]),
filename:join([emqx:data_dir(), "bufs", node(), QDir]). emqx_misc:safe_filename(QDir).
sanitize_file_path(Filepath) ->
iolist_to_binary(string:replace(Filepath, ":", "_", all)).
clear_disk_queue_dir(Id, Index) -> clear_disk_queue_dir(Id, Index) ->
ReplayQDir = disk_queue_dir(Id, Index), ReplayQDir = disk_queue_dir(Id, Index),

View File

@ -0,0 +1 @@
When creating disk queue directory for resource worker, substitute ':' with '-' in worker id.

View File

@ -0,0 +1 @@
在为资源缓存进程创建磁盘队列目录时在ID中用 '-' 代替 ':'。

View File

@ -0,0 +1 @@
Fix a compatible problem for the `webhook` bridge configuration which was created before the v5.0.12.

View File

@ -0,0 +1 @@
修复对在 v5.0.12 之前创建的 `webhook` 桥接配置的兼容问题。

View File

@ -98,15 +98,15 @@ spec:
{{- end }} {{- end }}
ports: ports:
- name: mqtt - name: mqtt
containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT | default 1883 }} containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT__BIND | default 1883 }}
- name: mqttssl - name: mqttssl
containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__SSL__DEFAULT | default 8883 }} containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__SSL__DEFAULT__BIND | default 8883 }}
- name: ws - name: ws
containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__WS__DEFAULT | default 8083 }} containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__WS__DEFAULT__BIND | default 8083 }}
- name: wss - name: wss
containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__WSS__DEFAULT | default 8084 }} containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__WSS__DEFAULT__BIND | default 8084 }}
- name: dashboard - name: dashboard
containerPort: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP | default 18083 }} containerPort: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP__BIND | default 18083 }}
{{- if not (empty .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT) }} {{- if not (empty .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT) }}
- name: internalmqtt - name: internalmqtt
containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT }} containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT }}
@ -143,14 +143,14 @@ spec:
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /status path: /status
port: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP | default 18083 }} port: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP__BIND | default 18083 }}
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
failureThreshold: 30 failureThreshold: 30
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /status path: /status
port: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP | default 18083 }} port: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP__BIND | default 18083 }}
initialDelaySeconds: 60 initialDelaySeconds: 60
periodSeconds: 30 periodSeconds: 30
failureThreshold: 10 failureThreshold: 10

View File

@ -98,15 +98,15 @@ spec:
{{- end }} {{- end }}
ports: ports:
- name: mqtt - name: mqtt
containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT | default 1883 }} containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT__BIND | default 1883 }}
- name: mqttssl - name: mqttssl
containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__SSL__DEFAULT | default 8883 }} containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__SSL__DEFAULT__BIND | default 8883 }}
- name: ws - name: ws
containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__WS__DEFAULT | default 8083 }} containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__WS__DEFAULT__BIND | default 8083 }}
- name: wss - name: wss
containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__WSS__DEFAULT | default 8084 }} containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__WSS__DEFAULT__BIND | default 8084 }}
- name: dashboard - name: dashboard
containerPort: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP | default 18083 }} containerPort: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP__BIND | default 18083 }}
{{- if not (empty .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT) }} {{- if not (empty .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT) }}
- name: internalmqtt - name: internalmqtt
containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT }} containerPort: {{ .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT }}
@ -143,14 +143,14 @@ spec:
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /status path: /status
port: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP | default 18083 }} port: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP__BIND | default 18083 }}
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
failureThreshold: 30 failureThreshold: 30
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /status path: /status
port: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP | default 18083 }} port: {{ .Values.emqxConfig.EMQX_DASHBOARD__LISTENER__HTTP__BIND | default 18083 }}
initialDelaySeconds: 60 initialDelaySeconds: 60
periodSeconds: 30 periodSeconds: 30
failureThreshold: 10 failureThreshold: 10

View File

@ -149,31 +149,31 @@ values(get) ->
maps:merge(values(post), ?METRICS_EXAMPLE); maps:merge(values(post), ?METRICS_EXAMPLE);
values(post) -> values(post) ->
#{ #{
<<"pubsub_topic">> => <<"mytopic">>, pubsub_topic => <<"mytopic">>,
<<"service_account_json">> => service_account_json =>
#{ #{
<<"auth_provider_x509_cert_url">> => auth_provider_x509_cert_url =>
<<"https://www.googleapis.com/oauth2/v1/certs">>, <<"https://www.googleapis.com/oauth2/v1/certs">>,
<<"auth_uri">> => auth_uri =>
<<"https://accounts.google.com/o/oauth2/auth">>, <<"https://accounts.google.com/o/oauth2/auth">>,
<<"client_email">> => client_email =>
<<"test@myproject.iam.gserviceaccount.com">>, <<"test@myproject.iam.gserviceaccount.com">>,
<<"client_id">> => <<"123812831923812319190">>, client_id => <<"123812831923812319190">>,
<<"client_x509_cert_url">> => client_x509_cert_url =>
<< <<
"https://www.googleapis.com/robot/v1/" "https://www.googleapis.com/robot/v1/"
"metadata/x509/test%40myproject.iam.gserviceaccount.com" "metadata/x509/test%40myproject.iam.gserviceaccount.com"
>>, >>,
<<"private_key">> => private_key =>
<< <<
"-----BEGIN PRIVATE KEY-----\n" "-----BEGIN PRIVATE KEY-----\n"
"MIIEvQI..." "MIIEvQI..."
>>, >>,
<<"private_key_id">> => <<"kid">>, private_key_id => <<"kid">>,
<<"project_id">> => <<"myproject">>, project_id => <<"myproject">>,
<<"token_uri">> => token_uri =>
<<"https://oauth2.googleapis.com/token">>, <<"https://oauth2.googleapis.com/token">>,
<<"type">> => <<"service_account">> type => <<"service_account">>
} }
}; };
values(put) -> values(put) ->

View File

@ -198,13 +198,17 @@ create_bridge_http(Config, GCPPubSubConfigOverrides) ->
Params = GCPPubSubConfig#{<<"type">> => TypeBin, <<"name">> => Name}, Params = GCPPubSubConfig#{<<"type">> => TypeBin, <<"name">> => Name},
Path = emqx_mgmt_api_test_util:api_path(["bridges"]), Path = emqx_mgmt_api_test_util:api_path(["bridges"]),
AuthHeader = emqx_mgmt_api_test_util:auth_header_(), AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
ProbePath = emqx_mgmt_api_test_util:api_path(["bridges_probe"]),
ProbeResult = emqx_mgmt_api_test_util:request_api(post, ProbePath, "", AuthHeader, Params),
ct:pal("creating bridge (via http): ~p", [Params]), ct:pal("creating bridge (via http): ~p", [Params]),
ct:pal("probe result: ~p", [ProbeResult]),
Res = Res =
case emqx_mgmt_api_test_util:request_api(post, Path, "", AuthHeader, Params) of case emqx_mgmt_api_test_util:request_api(post, Path, "", AuthHeader, Params) of
{ok, Res0} -> {ok, emqx_json:decode(Res0, [return_maps])}; {ok, Res0} -> {ok, emqx_json:decode(Res0, [return_maps])};
Error -> Error Error -> Error
end, end,
ct:pal("bridge creation result: ~p", [Res]), ct:pal("bridge creation result: ~p", [Res]),
?assertEqual(element(1, ProbeResult), element(1, Res)),
Res. Res.
create_rule_and_action_http(Config) -> create_rule_and_action_http(Config) ->
@ -681,7 +685,7 @@ t_create_via_http(Config) ->
create_bridge_http(Config), create_bridge_http(Config),
fun(Res, Trace) -> fun(Res, Trace) ->
?assertMatch({ok, _}, Res), ?assertMatch({ok, _}, Res),
?assertMatch([_], ?of_kind(gcp_pubsub_bridge_jwt_created, Trace)), ?assertMatch([_, _], ?of_kind(gcp_pubsub_bridge_jwt_created, Trace)),
ok ok
end end
), ),