diff --git a/.github/workflows/run_automate_tests.yaml b/.github/workflows/run_automate_tests.yaml index 5589fd7b3..e427ba5ae 100644 --- a/.github/workflows/run_automate_tests.yaml +++ b/.github/workflows/run_automate_tests.yaml @@ -39,6 +39,7 @@ jobs: echo "::set-output name=imgname::emqx-ee" echo "::set-output name=version::$(./pkg-vsn.sh)" else + make emqx-docker echo "::set-output name=imgname::emqx" echo "::set-output name=version::$(./pkg-vsn.sh)" fi diff --git a/.github/workflows/run_fvt_tests.yaml b/.github/workflows/run_fvt_tests.yaml index f314770fc..de2b65ca9 100644 --- a/.github/workflows/run_fvt_tests.yaml +++ b/.github/workflows/run_fvt_tests.yaml @@ -24,10 +24,12 @@ jobs: echo "TARGET=emqx/emqx-ee" >> $GITHUB_ENV echo "PROFILE=emqx-ee" >> $GITHUB_ENV echo "EMQX_TAG=$(./pkg-vsn.sh)" >> $GITHUB_ENV + make emqx-ee-docker else echo "TARGET=emqx/emqx" >> $GITHUB_ENV echo "PROFILE=emqx" >> $GITHUB_ENV echo "EMQX_TAG=$(./pkg-vsn.sh)" >> $GITHUB_ENV + make emqx-docker fi - name: make emqx image env: diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 000000000..a442ff6c8 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,98 @@ +# EMQ X 4.3 Changes + +Started tracking changes in CHANGE.md since EMQ X v4.3.11 + +NOTE: Keep prepending to the head of the file instead of the tail + +File format: + +- Use weight-2 heading for releases +- One list item per change topic + Change log ends with a list of github PRs + +## v4.3.11 + +Important notes: + +- For Debian/Ubuntu users + + We changed the package installed service from init.d to systemd. + The upgrade from init.d to systemd is verified, however it is + recommended to verify it before rolling out to production. + At least to ensure systemd is available in your system. + +- For Centos Users + + RPM package now depends on `openssl11` which is NOT available + in certain centos distributions. + Please make sure the yum repo [epel-release](https://docs.fedoraproject.org/en-US/epel) is installed. + +### Important changes + +* Debian/Ubuntu package (deb) installed EMQ X now runs on systemd [#6389]
+ This is to take advantage of systemd's supervision functionality to ensure + EMQ X service is restarted after crashes. + +### Minor changes + +* Clustering malfunction fixes [#6221, #6381] + Mostly changes made in [ekka](https://github.com/emqx/ekka/pull/134)
+ From 0.8.1.4 to 0.8.1.6, fixes included intra-cluster RPC call timeouts,
+ also fixed `ekka_locker` process crashe after killing a hanged lock owner. + +* Improved log message when TCP proxy is in use but proxy_protocol configuration is not turned on [#6416]
+ "please check proxy_protocol config for specific listeners and zones" to hint a misconfiguration + +* Helm chart supports networking.k8s.io/v1 [#6368] + +* Fix session takeover race condition which may lead to message loss [#6396] + +* EMQ X docker images are pushed to aws public ecr in an automated CI job [#6271]
+ `docker pull public.ecr.aws/emqx/emqx:4.3.10` + +* Fix webhook URL path to allow rule-engine variable substitution [#6399] + +* Corrected RAM usage display [#6379] + +* Changed emqx_sn_registry table creation to runtime [#6357]
+ This was a bug introduced in 4.3.3, in which the table is changed from ets to mnesia
+ this will cause upgrade to fail when a later version node joins a 4.3.0-2 cluster
+ +* Log level for normal termination changed from info to debug [#6358] + +* Added config `retainer.stop_publish_clear_msg` to enable/disable empty message retained message publish [#6343]
+ In MQTT 3.1.1, it is unclear if a MQTT broker should publish the 'clear' (no payload) message
+ to the subscribers, or just delete the retained message. So we have made it configurable + +* Fix mqtt bridge malfunction when remote host is unreachable (hangs the connection) [#6286, #6323] + +* System monitor now inspects `current_stacktrace` of suspicious process [#6290]
+ `current_function` was not quite helpful + +* Changed default `max_topc_levels` config value to 128 [#6294, #6420]
+ previously it has no limit (config value = 0), which can be a potential DoS threat + +* Collect only libcrypto and libtinfo so files for zip package [#6259]
+ in 4.3.10 we tried to collect all so files, however glibc is not quite portable + +* Added openssl-1.1 to RPM dependency [#6239] + +* Http client duplicated header fix [#6195] + +* Fix `node_dump` issues when working with deb or rpm installation [#6209] + +* Pin Erlang/OTP 23.2.7.2-emqx-3 [#6246]
+ 4.3.10 is on 23.2.7.2-emqx-2, this bump is to fix an ECC signature name typo: + ecdsa_secp512r1_sha512 -> ecdsa_secp521r1_sha512 + +* HTTP client performance improvement [#6474, #6414]
+ The changes are mostly done in the dependency [repo](https://github.com/emqx/ehttpc). + +* For messages from gateways add message properties as MQTT message headers [#6142]
+ e.g. messages from CoAP, LwM2M, Stomp, ExProto, when translated into MQTT message
+ properties such as protocol name, protocol version, username (if any) peer-host
+ etc. are filled as MQTT message headers. + +## v4.3.0~10 + +Older version changes are not tracked here. diff --git a/apps/emqx_management/src/emqx_management.appup.src b/apps/emqx_management/src/emqx_management.appup.src index f0478add1..1463334b4 100644 --- a/apps/emqx_management/src/emqx_management.appup.src +++ b/apps/emqx_management/src/emqx_management.appup.src @@ -1,13 +1,13 @@ %% -*- mode: erlang -*- {VSN, - [ {<<"4\\.3\\.[0-8]+">>, + [ {<<"4\\.3\\.[0-9]+">>, [ {apply,{minirest,stop_http,['http:management']}}, {apply,{minirest,stop_http,['https:management']}}, {restart_application, emqx_management} ]}, {<<".*">>, []} ], - [ {<<"4\\.3\\.[0-8]+">>, + [ {<<"4\\.3\\.[0-9]+">>, [ {apply,{minirest,stop_http,['http:management']}}, {apply,{minirest,stop_http,['https:management']}}, {restart_application, emqx_management} diff --git a/apps/emqx_management/src/emqx_mgmt_api.erl b/apps/emqx_management/src/emqx_mgmt_api.erl index d42d2a486..b688b9cd2 100644 --- a/apps/emqx_management/src/emqx_mgmt_api.erl +++ b/apps/emqx_management/src/emqx_mgmt_api.erl @@ -326,10 +326,12 @@ to_integer(I) when is_integer(I) -> to_integer(B) when is_binary(B) -> binary_to_integer(B). +%% @doc The input timestamp time is in seconds, which needs to be +%% converted to internal milliseconds here to_timestamp(I) when is_integer(I) -> - I; + I * 1000; to_timestamp(B) when is_binary(B) -> - binary_to_integer(B). + binary_to_integer(B) * 1000. aton(B) when is_binary(B) -> list_to_tuple([binary_to_integer(T) || T <- re:split(B, "[.]")]). @@ -361,7 +363,7 @@ params2qs_test() -> ExpectedQs = [{str, '=:=', <<"abc">>}, {int, '=:=', 123}, {atom, '=:=', connected}, - {ts, '=:=', 156000}, + {ts, '=:=', 156000000}, {range, '>=', 1, '=<', 5} ], FuzzyQs = [{fuzzy, like, <<"user">>}, diff --git a/apps/emqx_management/src/emqx_mgmt_api_clients.erl b/apps/emqx_management/src/emqx_mgmt_api_clients.erl index a25cc9ff5..71e94f841 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_clients.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_clients.erl @@ -361,31 +361,24 @@ query({Qs, Fuzzy}, Start, Limit) -> match_fun(Ms, Fuzzy) -> MsC = ets:match_spec_compile(Ms), - REFuzzy = lists:map(fun({K, like, S}) -> - {ok, RE} = re:compile(escape(S)), - {K, like, RE} - end, Fuzzy), fun(Rows) -> case ets:match_spec_run(Rows, MsC) of [] -> []; Ls -> lists:filter(fun(E) -> - run_fuzzy_match(E, REFuzzy) + run_fuzzy_match(E, Fuzzy) end, Ls) end end. -escape(B) when is_binary(B) -> - re:replace(B, <<"\\\\">>, <<"\\\\\\\\">>, [{return, binary}, global]). - run_fuzzy_match(_, []) -> true; -run_fuzzy_match(E = {_, #{clientinfo := ClientInfo}, _}, [{Key, _, RE} | Fuzzy]) -> - Val = case maps:get(Key, ClientInfo, "") of - undefined -> ""; +run_fuzzy_match(E = {_, #{clientinfo := ClientInfo}, _}, [{Key, like, SubStr}|Fuzzy]) -> + Val = case maps:get(Key, ClientInfo, undefined) of + undefined -> <<>>; V -> V end, - re:run(Val, RE, [{capture, none}]) == match andalso run_fuzzy_match(E, Fuzzy). + binary:match(Val, SubStr) /= nomatch andalso run_fuzzy_match(E, Fuzzy). %%-------------------------------------------------------------------- %% QueryString to Match Spec @@ -473,10 +466,10 @@ params2qs_test() -> proto_ver => 4, connected_at => '$3'}, session => #{created_at => '$2'}}, - ExpectedCondi = [{'>=','$2', 1}, - {'=<','$2', 5}, - {'>=','$3', 1}, - {'=<','$3', 5}], + ExpectedCondi = [{'>=','$2', 1000}, + {'=<','$2', 5000}, + {'>=','$3', 1000}, + {'=<','$3', 5000}], {10, {Qs1, []}} = emqx_mgmt_api:params2qs(Params, QsSchema), [{{'$1', MtchHead, _}, Condi, _}] = qs2ms(Qs1), ?assertEqual(ExpectedMtchHead, MtchHead), @@ -484,9 +477,24 @@ params2qs_test() -> [{{'$1', #{}, '_'}, [], ['$_']}] = qs2ms([]). -escape_test() -> - Str = <<"\\n">>, - {ok, Re} = re:compile(escape(Str)), - {match, _} = re:run(<<"\\name">>, Re). +fuzzy_match_test() -> + Info = {emqx_channel_info, + #{clientinfo => + #{ clientid => <<"abcde">> + , username => <<"abc\\name*[]()">> + }}, [] + }, + true = run_fuzzy_match(Info, [{clientid, like, <<"abcde">>}]), + true = run_fuzzy_match(Info, [{clientid, like, <<"bcd">>}]), + false = run_fuzzy_match(Info, [{clientid, like, <<"defh">>}]), + + true = run_fuzzy_match(Info, [{username, like, <<"\\name">>}]), + true = run_fuzzy_match(Info, [{username, like, <<"*">>}]), + true = run_fuzzy_match(Info, [{username, like, <<"[]">>}]), + true = run_fuzzy_match(Info, [{username, like, <<"()">>}]), + false = run_fuzzy_match(Info, [{username, like, <<"))">>}]), + + true = run_fuzzy_match(Info, [{clientid, like, <<"de">>}, + {username, like, <<"[]">>}]). -endif. diff --git a/apps/emqx_management/src/emqx_mgmt_api_nodes.erl b/apps/emqx_management/src/emqx_mgmt_api_nodes.erl index 2be151aaa..2266ccfa3 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_nodes.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_nodes.erl @@ -36,11 +36,10 @@ list(_Bindings, _Params) -> minirest:return({ok, [format(Node, Info) || {Node, Info} <- emqx_mgmt:list_nodes()]}). get(#{node := Node}, _Params) -> - minirest:return({ok, emqx_mgmt:lookup_node(Node)}). + minirest:return({ok, format(Node, emqx_mgmt:lookup_node(Node))}). format(Node, {error, Reason}) -> #{node => Node, error => Reason}; format(_Node, Info = #{memory_total := Total, memory_used := Used}) -> Info#{memory_total := emqx_mgmt_util:kmg(Total), memory_used := emqx_mgmt_util:kmg(Used)}. - diff --git a/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/make_data.sh b/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/make_data.sh index e31729784..d8cce6697 100755 --- a/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/make_data.sh +++ b/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE_data/make_data.sh @@ -1,5 +1,7 @@ -#!/bin/bash +#!/usr/bin/env bash + set -eux pipefail + # Helper script for creating data export files container() { diff --git a/apps/emqx_retainer/src/emqx_retainer.appup.src b/apps/emqx_retainer/src/emqx_retainer.appup.src index 19e8e835f..45ec6420c 100644 --- a/apps/emqx_retainer/src/emqx_retainer.appup.src +++ b/apps/emqx_retainer/src/emqx_retainer.appup.src @@ -1,15 +1,14 @@ -%% -*-: erlang -*- +%% -*- mode: erlang -*- {VSN, - [ - {<<"4\\.3\\.[0-1]+">>, [ - {load_module, emqx_retainer, brutal_purge, soft_purge, []} - ]}, - {<<".*">>, []} - ], - [ - {<<"4\\.3\\.[0-1]+">>, [ - {load_module, emqx_retainer, brutal_purge, soft_purge, []} - ]}, - {<<".*">>, []} - ] -}. + [{"4.3.2", + [{load_module,emqx_retainer_cli,brutal_purge,soft_purge,[]}]}, + {<<"4\\.3\\.[0-1]">>, + [{load_module,emqx_retainer_cli,brutal_purge,soft_purge,[]}, + {load_module,emqx_retainer,brutal_purge,soft_purge,[]}]}, + {<<".*">>,[]}], + [{"4.3.2", + [{load_module,emqx_retainer_cli,brutal_purge,soft_purge,[]}]}, + {<<"4\\.3\\.[0-1]">>, + [{load_module,emqx_retainer_cli,brutal_purge,soft_purge,[]}, + {load_module,emqx_retainer,brutal_purge,soft_purge,[]}]}, + {<<".*">>,[]}]}. diff --git a/apps/emqx_retainer/src/emqx_retainer_cli.erl b/apps/emqx_retainer/src/emqx_retainer_cli.erl index fe8fa9578..23fb98252 100644 --- a/apps/emqx_retainer/src/emqx_retainer_cli.erl +++ b/apps/emqx_retainer/src/emqx_retainer_cli.erl @@ -33,7 +33,9 @@ cmd(["info"]) -> cmd(["topics"]) -> case mnesia:dirty_all_keys(?TAB) of [] -> ignore; - Topics -> lists:foreach(fun(Topic) -> emqx_ctl:print("~s~n", [Topic]) end, Topics) + Topics -> lists:foreach(fun(Topic) -> + emqx_ctl:print("~s~n", [emqx_topic:join(Topic)]) + end, Topics) end; cmd(["clean"]) -> @@ -55,4 +57,3 @@ cmd(_) -> unload() -> emqx_ctl:unregister_command(retainer). - diff --git a/apps/emqx_retainer/test/emqx_retainer_cli_SUITE.erl b/apps/emqx_retainer/test/emqx_retainer_cli_SUITE.erl index f9f657483..2e60e9ac4 100644 --- a/apps/emqx_retainer/test/emqx_retainer_cli_SUITE.erl +++ b/apps/emqx_retainer/test/emqx_retainer_cli_SUITE.erl @@ -23,18 +23,32 @@ all() -> emqx_ct:all(?MODULE). -init_per_testcase(_TestCase, Config) -> +init_per_suite(Config) -> + emqx_ct_helpers:start_apps([emqx_retainer]), + Config. + +end_per_suite(_Config) -> + emqx_ct_helpers:stop_apps([emqx_retainer]). + +init_per_testcase(TestCase, Config) -> Config. end_per_testcase(_TestCase, Config) -> - Config. + emqx_retainer:clean(<<"#">>). -% t_cmd(_) -> -% error('TODO'). +t_cmd(_) -> + {ok, C1} = emqtt:start_link([{clean_start, true}, {proto_ver, v5}]), + {ok, _} = emqtt:connect(C1), + emqtt:publish(C1, <<"/retained">>, <<"this is a retained message">>, [{qos, 0}, {retain, true}]), + emqtt:publish(C1, <<"/retained/2">>, <<"this is a retained message">>, [{qos, 0}, {retain, true}]), + timer:sleep(1000), + ?assertMatch(ok, emqx_retainer_cli:cmd(["topics"])), + ?assertMatch(ok, emqx_retainer_cli:cmd(["info"])), + ?assertMatch(ok, emqx_retainer_cli:cmd(["clean", "retained"])), + ?assertMatch(ok, emqx_retainer_cli:cmd(["clean"])). % t_unload(_) -> % error('TODO'). % t_load(_) -> % error('TODO'). - diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine.appup.src b/apps/emqx_rule_engine/src/emqx_rule_engine.appup.src index c94dc994d..d97c6542f 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_engine.appup.src +++ b/apps/emqx_rule_engine/src/emqx_rule_engine.appup.src @@ -1,52 +1,93 @@ %% -*- mode: erlang -*- {VSN, - [{"4.3.5",[{load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, - {"4.3.0", - [{load_module,emqx_rule_funcs,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, - {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, - {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, - {"4.3.1", - [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, - {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, - {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, - {"4.3.2", - [{load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, - {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, - {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, - {"4.3.3", - [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, + [ + {"4.3.6", + [ {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + ]}, + {"4.3.5", + [ {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + ]}, {"4.3.4", - [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, - {<<".*">>,[]}], - [{"4.3.5",[{load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, - {"4.3.0", - [{load_module,emqx_rule_funcs,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, - {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, - {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, - {"4.3.1", - [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, - {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, - {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, - {"4.3.2", - [{load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, - {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, - {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, + [ {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + ]}, {"4.3.3", - [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, + [ {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + ]}, + {"4.3.2", + [ {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {apply,{emqx_stats,cancel_update,[rule_registery_stats]}} + , {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + ]}, + {"4.3.1", + [ {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {apply,{emqx_stats,cancel_update,[rule_registery_stats]}} + , {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + ]}, + {"4.3.0", + [ {load_module,emqx_rule_funcs,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {apply,{emqx_stats,cancel_update,[rule_registery_stats]}} + , {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + ]}, + {<<".*">>, []} + ], + [ + {"4.3.6", + [ {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + ]}, + {"4.3.5", + [ {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + ]}, {"4.3.4", - [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, - {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, - {<<".*">>,[]}]}. + [ {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + ]}, + {"4.3.3", + [ {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + ]}, + {"4.3.2", + [ {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {apply,{emqx_stats,cancel_update,[rule_registery_stats]}} + , {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + ]}, + {"4.3.1", + [ {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {apply,{emqx_stats,cancel_update,[rule_registery_stats]}} + , {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + ]}, + {"4.3.0", + [ {load_module,emqx_rule_funcs,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]} + , {apply,{emqx_stats,cancel_update,[rule_registery_stats]}} + , {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]} + , {load_module,emqx_rule_runtime,brutal_purge,soft_purge,[]} + ]}, + {<<".*">>, []} + ] +}. diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine.erl b/apps/emqx_rule_engine/src/emqx_rule_engine.erl index 6c8b95064..0dabb1c52 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_engine.erl +++ b/apps/emqx_rule_engine/src/emqx_rule_engine.erl @@ -491,11 +491,24 @@ may_update_rule_params(Rule, Params = #{on_action_failed := OnFailed}) -> may_update_rule_params(Rule = #rule{actions = OldActions}, Params = #{actions := Actions}) -> %% prepare new actions before removing old ones NewActions = prepare_actions(Actions, maps:get(enabled, Params, true)), + ok = restore_action_metrics(OldActions, NewActions), _ = ?CLUSTER_CALL(clear_actions, [OldActions]), may_update_rule_params(Rule#rule{actions = NewActions}, maps:remove(actions, Params)); may_update_rule_params(Rule, _Params) -> %% ignore all the unsupported params Rule. +%% NOTE: if the user removed an action, but the action is not the last one in the list, +%% the `restore_action_metrics/2` will not work as expected! +restore_action_metrics([#action_instance{id = OldId} | OldActions], + [#action_instance{id = NewId} | NewActions]) -> + emqx_rule_metrics:inc_actions_taken(NewId, emqx_rule_metrics:get_actions_taken(OldId)), + emqx_rule_metrics:inc_actions_success(NewId, emqx_rule_metrics:get_actions_success(OldId)), + emqx_rule_metrics:inc_actions_error(NewId, emqx_rule_metrics:get_actions_error(OldId)), + emqx_rule_metrics:inc_actions_exception(NewId, emqx_rule_metrics:get_actions_exception(OldId)), + restore_action_metrics(OldActions, NewActions); +restore_action_metrics(_, _) -> + ok. + ignore_lib_apps(Apps) -> LibApps = [kernel, stdlib, sasl, appmon, eldap, erts, syntax_tools, ssl, crypto, mnesia, os_mon, diff --git a/apps/emqx_rule_engine/src/emqx_rule_runtime.erl b/apps/emqx_rule_engine/src/emqx_rule_runtime.erl index f9e210ab3..080498d82 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_runtime.erl +++ b/apps/emqx_rule_engine/src/emqx_rule_runtime.erl @@ -204,6 +204,10 @@ match_conditions({}, _Data) -> true. %% comparing numbers against strings +compare(Op, undefined, undefined) -> + do_compare(Op, undefined, undefined); +compare(_Op, L, R) when L == undefined; R == undefined -> + false; compare(Op, L, R) when is_number(L), is_binary(R) -> do_compare(Op, L, number(R)); compare(Op, L, R) when is_binary(L), is_number(R) -> diff --git a/apps/emqx_sn/src/emqx_sn.appup.src b/apps/emqx_sn/src/emqx_sn.appup.src index caf715adc..22d2cd606 100644 --- a/apps/emqx_sn/src/emqx_sn.appup.src +++ b/apps/emqx_sn/src/emqx_sn.appup.src @@ -1,27 +1,14 @@ -%% -*-: erlang -*- +%% -*- mode: erlang -*- {VSN, - [ - {"4.3.3", [ - {load_module, emqx_sn_registry, brutal_purge, soft_purge, []} - ]}, - {"4.3.2", [ - {load_module, emqx_sn_gateway, brutal_purge, soft_purge, []}, - {load_module, emqx_sn_registry, brutal_purge, soft_purge, []} - ]}, - {<<"4\\.3\\.[0-1]">>, [ - {restart_application, emqx_sn} - ]} - ], - [ - {"4.3.3", [ - {load_module, emqx_sn_registry, brutal_purge, soft_purge, []} - ]}, - {"4.3.2", [ - {load_module, emqx_sn_gateway, brutal_purge, soft_purge, []}, - {load_module, emqx_sn_registry, brutal_purge, soft_purge, []} - ]}, - {<<"4\\.3\\.[0-1]">>, [ - {restart_application, emqx_sn} - ]} - ] -}. + [{"4.3.3",[{load_module,emqx_sn_registry,brutal_purge,soft_purge,[]}]}, + {"4.3.2", + [{load_module,emqx_sn_gateway,brutal_purge,soft_purge,[]}, + {load_module,emqx_sn_registry,brutal_purge,soft_purge,[]}]}, + {<<"4\\.3\\.[0-1]">>, + [{restart_application,emqx_sn}]}], + [{"4.3.3",[{load_module,emqx_sn_registry,brutal_purge,soft_purge,[]}]}, + {"4.3.2", + [{load_module,emqx_sn_gateway,brutal_purge,soft_purge,[]}, + {load_module,emqx_sn_registry,brutal_purge,soft_purge,[]}]}, + {<<"4\\.3\\.[0-1]">>, + [{restart_application,emqx_sn}]}]}. diff --git a/bin/emqx b/bin/emqx index 6882a3ba0..5b158ff19 100755 --- a/bin/emqx +++ b/bin/emqx @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # -*- tab-width:4;indent-tabs-mode:nil -*- # ex: ts=4 sw=4 et diff --git a/bin/emqx_ctl b/bin/emqx_ctl index 4c3aaa1ca..8d729083d 100755 --- a/bin/emqx_ctl +++ b/bin/emqx_ctl @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # -*- tab-width:4;indent-tabs-mode:nil -*- # ex: ts=4 sw=4 et diff --git a/build b/build index 8f43934fe..a5a8b729b 100755 --- a/build +++ b/build @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # This script helps to build release artifacts. # arg1: profile, e.g. emqx | emqx-edge | emqx-pkg | emqx-edge-pkg diff --git a/deploy/docker/docker-entrypoint.sh b/deploy/docker/docker-entrypoint.sh index 1abef430b..e58eecf85 100755 --- a/deploy/docker/docker-entrypoint.sh +++ b/deploy/docker/docker-entrypoint.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash ## EMQ docker image start script # Huang Rui # EMQ X Team diff --git a/lib-ce/emqx_telemetry/src/emqx_telemetry.app.src b/lib-ce/emqx_telemetry/src/emqx_telemetry.app.src index e65678c37..8394836ef 100644 --- a/lib-ce/emqx_telemetry/src/emqx_telemetry.app.src +++ b/lib-ce/emqx_telemetry/src/emqx_telemetry.app.src @@ -1,6 +1,6 @@ {application, emqx_telemetry, [{description, "EMQ X Telemetry"}, - {vsn, "4.3.1"}, % strict semver, bump manually! + {vsn, "4.3.2"}, % strict semver, bump manually! {modules, []}, {registered, [emqx_telemetry_sup]}, {applications, [kernel,stdlib]}, diff --git a/lib-ce/emqx_telemetry/src/emqx_telemetry.appup.src b/lib-ce/emqx_telemetry/src/emqx_telemetry.appup.src index 27998f0d5..23de107d9 100644 --- a/lib-ce/emqx_telemetry/src/emqx_telemetry.appup.src +++ b/lib-ce/emqx_telemetry/src/emqx_telemetry.appup.src @@ -1,13 +1,13 @@ %% -*- mode: erlang -*- {VSN, [ - {"4.3.0", [ + {<<"4\\.3\\.[0-1]">>, [ {load_module, emqx_telemetry, brutal_purge, soft_purge, []} ]}, {<<".*">>, []} ], [ - {"4.3.0", [ + {<<"4\\.3\\.[0-1]">>, [ {load_module, emqx_telemetry, brutal_purge, soft_purge, []} ]}, {<<".*">>, []} diff --git a/lib-ce/emqx_telemetry/src/emqx_telemetry.erl b/lib-ce/emqx_telemetry/src/emqx_telemetry.erl index 3e8d3ffd3..e07eeb7fb 100644 --- a/lib-ce/emqx_telemetry/src/emqx_telemetry.erl +++ b/lib-ce/emqx_telemetry/src/emqx_telemetry.erl @@ -82,7 +82,7 @@ timer = undefined :: undefined | reference() }). -%% The count of 100-nanosecond intervals between the UUID epoch +%% The count of 100-nanosecond intervals between the UUID epoch %% 1582-10-15 00:00:00 and the UNIX epoch 1970-01-01 00:00:00. -define(GREGORIAN_EPOCH_OFFSET, 16#01b21dd213814000). @@ -253,29 +253,23 @@ os_info() -> [{os_name, Name}, {os_version, Version}]; {unix, _} -> - case file:read_file_info("/etc/os-release") of + case file:read_file("/etc/os-release") of {error, _} -> [{os_name, "Unknown"}, {os_version, "Unknown"}]; - {ok, FileInfo} -> - case FileInfo#file_info.access of - Access when Access =:= read orelse Access =:= read_write -> - OSInfo = lists:foldl(fun(Line, Acc) -> - [Var, Value] = string:tokens(Line, "="), - NValue = case Value of - _ when is_list(Value) -> - lists:nth(1, string:tokens(Value, "\"")); - _ -> - Value - end, - [{Var, NValue} | Acc] - end, [], string:tokens(os:cmd("cat /etc/os-release"), "\n")), - [{os_name, get_value("NAME", OSInfo)}, - {os_version, get_value("VERSION", OSInfo, get_value("VERSION_ID", OSInfo))}]; - _ -> - [{os_name, "Unknown"}, - {os_version, "Unknown"}] - end + {ok, FileContent} -> + OSInfo = lists:foldl(fun(Line, Acc) -> + [Var, Value] = string:tokens(Line, "="), + NValue = case Value of + _ when is_list(Value) -> + lists:nth(1, string:tokens(Value, "\"")); + _ -> + Value + end, + [{Var, NValue} | Acc] + end, [], string:tokens(binary:bin_to_list(FileContent), "\n")), + [{os_name, get_value("NAME", OSInfo)}, + {os_version, get_value("VERSION", OSInfo, get_value("VERSION_ID", OSInfo, get_value("PRETTY_NAME", OSInfo)))}] end; {win32, nt} -> Ver = os:cmd("ver"), @@ -429,5 +423,7 @@ module_attributes(Module) -> bin(L) when is_list(L) -> list_to_binary(L); +bin(A) when is_atom(A) -> + atom_to_binary(A); bin(B) when is_binary(B) -> B. diff --git a/rebar.config b/rebar.config index f1dc55512..f335c257b 100644 --- a/rebar.config +++ b/rebar.config @@ -37,7 +37,7 @@ {deps, [ {gpb, "4.11.2"} %% gpb only used to build, but not for release, pin it here to avoid fetching a wrong version due to rebar plugins scattered in all the deps - , {ehttpc, {git, "https://github.com/emqx/ehttpc", {tag, "0.1.13"}}} + , {ehttpc, {git, "https://github.com/emqx/ehttpc", {tag, "0.1.14"}}} , {eredis_cluster, {git, "https://github.com/emqx/eredis_cluster", {tag, "0.6.5"}}} , {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}} , {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}} diff --git a/scripts/apps-version-check.sh b/scripts/apps-version-check.sh index 241d0fc15..ae4cd22ee 100755 --- a/scripts/apps-version-check.sh +++ b/scripts/apps-version-check.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -euo pipefail latest_release=$(git describe --abbrev=0 --tags) diff --git a/scripts/get-distro.sh b/scripts/get-distro.sh index 00e95e1d8..89eafc4ee 100755 --- a/scripts/get-distro.sh +++ b/scripts/get-distro.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash ## This script prints Linux distro name and its version number ## e.g. macos, centos8, ubuntu20.04 diff --git a/scripts/one-more-emqx-ee.sh b/scripts/one-more-emqx-ee.sh index f94681056..1b37ac5cb 100644 --- a/scripts/one-more-emqx-ee.sh +++ b/scripts/one-more-emqx-ee.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # shellcheck disable=2090 ############### ## args and env validation diff --git a/scripts/one-more-emqx.sh b/scripts/one-more-emqx.sh index d905f64c4..c6d362dbe 100644 --- a/scripts/one-more-emqx.sh +++ b/scripts/one-more-emqx.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # shellcheck disable=2090 ############### ## args and env validation diff --git a/scripts/update_appup.escript b/scripts/update_appup.escript index 7674a941c..965de1efe 100755 --- a/scripts/update_appup.escript +++ b/scripts/update_appup.escript @@ -189,8 +189,11 @@ find_appup_actions(CurrApps, PrevApps) -> maps:fold( fun(App, CurrAppIdx, Acc) -> case PrevApps of - #{App := PrevAppIdx} -> find_appup_actions(App, CurrAppIdx, PrevAppIdx) ++ Acc; - _ -> Acc %% New app, nothing to upgrade here. + #{App := PrevAppIdx} -> + find_appup_actions(App, CurrAppIdx, PrevAppIdx) ++ Acc; + _ -> + %% New app, nothing to upgrade here. + Acc end end, [], @@ -199,8 +202,12 @@ find_appup_actions(CurrApps, PrevApps) -> find_appup_actions(_App, AppIdx, AppIdx) -> %% No changes to the app, ignore: []; -find_appup_actions(App, CurrAppIdx, PrevAppIdx = #app{version = PrevVersion}) -> - {OldUpgrade, OldDowngrade} = find_old_appup_actions(App, PrevVersion), +find_appup_actions(App, + CurrAppIdx = #app{version = CurrVersion}, + PrevAppIdx = #app{version = PrevVersion}) -> + {OldUpgrade0, OldDowngrade0} = find_old_appup_actions(App, PrevVersion), + OldUpgrade = ensure_all_patch_versions(App, CurrVersion, OldUpgrade0), + OldDowngrade = ensure_all_patch_versions(App, CurrVersion, OldDowngrade0), Upgrade = merge_update_actions(App, diff_app(App, CurrAppIdx, PrevAppIdx), OldUpgrade), Downgrade = merge_update_actions(App, diff_app(App, PrevAppIdx, CurrAppIdx), OldDowngrade), if OldUpgrade =:= Upgrade andalso OldDowngrade =:= Downgrade -> @@ -210,14 +217,40 @@ find_appup_actions(App, CurrAppIdx, PrevAppIdx = #app{version = PrevVersion}) -> [{App, {Upgrade, Downgrade, OldUpgrade, OldDowngrade}}] end. +%% To avoid missing one patch version when upgrading, we try to +%% optimistically generate the list of expected versions that should +%% be covered by the upgrade. +ensure_all_patch_versions(App, CurrVsn, OldActions) -> + case is_app_external(App) of + true -> + %% we do not attempt to predict the version list for + %% external dependencies, as those may not follow our + %% conventions. + OldActions; + false -> + do_ensure_all_patch_versions(App, CurrVsn, OldActions) + end. + +do_ensure_all_patch_versions(App, CurrVsn, OldActions) -> + case enumerate_past_versions(CurrVsn) of + {ok, ExpectedVsns} -> + CoveredVsns = [V || {V, _} <- OldActions, V =/= <<".*">>], + ExpectedVsnStrs = [vsn_number_to_string(V) || V <- ExpectedVsns], + MissingActions = [{V, []} || V <- ExpectedVsnStrs, not contains_version(V, CoveredVsns)], + MissingActions ++ OldActions; + {error, bad_version} -> + log("WARN: Could not infer expected versions to upgrade from for ~p~n", [App]), + OldActions + end. + %% For external dependencies, show only the changes that are missing %% in their current appup. diff_appup_instructions(ComputedChanges, PresentChanges) -> lists:foldr( - fun({Vsn, ComputedActions}, Acc) -> - case find_matching_version(Vsn, PresentChanges) of + fun({VsnOrRegex, ComputedActions}, Acc) -> + case find_matching_version(VsnOrRegex, PresentChanges) of undefined -> - [{Vsn, ComputedActions} | Acc]; + [{VsnOrRegex, ComputedActions} | Acc]; PresentActions -> DiffActions = ComputedActions -- PresentActions, case DiffActions of @@ -225,7 +258,7 @@ diff_appup_instructions(ComputedChanges, PresentChanges) -> %% no diff Acc; _ -> - [{Vsn, DiffActions} | Acc] + [{VsnOrRegex, DiffActions} | Acc] end end end, @@ -250,8 +283,11 @@ parse_appup_diffs(Upgrade, OldUpgrade, Downgrade, OldDowngrade) -> end. %% TODO: handle regexes -find_matching_version(Vsn, PresentChanges) -> - proplists:get_value(Vsn, PresentChanges). +%% Since the first argument may be a regex itself, we would need to +%% check if it is "contained" within other regexes inside list of +%% versions in the second argument. +find_matching_version(VsnOrRegex, PresentChanges) -> + proplists:get_value(VsnOrRegex, PresentChanges). find_old_appup_actions(App, PrevVersion) -> {Upgrade0, Downgrade0} = @@ -289,11 +325,38 @@ do_merge_update_actions(App, {New0, Changed0, Deleted0}, OldActions) -> New = New0 -- AlreadyHandled, Changed = Changed0 -- AlreadyHandled, Deleted = Deleted0 -- AlreadyHandled, - [{load_module, M, brutal_purge, soft_purge, []} || M <- Changed ++ New] ++ - OldActions ++ + Reloads = [{load_module, M, brutal_purge, soft_purge, []} + || not contains_restart_application(App, OldActions), + M <- Changed ++ New], + {OldActionsWithStop, OldActionsAfterStop} = + find_application_stop_instruction(App, OldActions), + OldActionsWithStop ++ + Reloads ++ + OldActionsAfterStop ++ [{delete_module, M} || M <- Deleted] ++ AppSpecific. +%% If an entry restarts an application, there's no need to use +%% `load_module' instructions. +contains_restart_application(Application, Actions) -> + lists:member({restart_application, Application}, Actions). + +%% If there is an `application:stop(Application)' call in the +%% instructions, we insert `load_module' instructions after it. +find_application_stop_instruction(Application, Actions) -> + {Before, After0} = + lists:splitwith( + fun({apply, {application, stop, [App]}}) when App =:= Application -> + false; + (_) -> + true + end, Actions), + case After0 of + [StopInst | After] -> + {Before ++ [StopInst], After}; + [] -> + {[], Before} + end. %% @doc Process the existing actions to exclude modules that are %% already handled @@ -308,14 +371,57 @@ process_old_action(_) -> []. ensure_version(Version, OldInstructions) -> - OldVersions = [ensure_string(element(1, I)) || I <- OldInstructions], - case lists:member(Version, OldVersions) of + OldVersions = [element(1, I) || I <- OldInstructions], + case contains_version(Version, OldVersions) of false -> - [{Version, []}|OldInstructions]; - _ -> + [{Version, []} | OldInstructions]; + true -> OldInstructions end. +contains_version(Needle, Haystack) when is_list(Needle) -> + lists:any( + fun(Regex) when is_binary(Regex) -> + case re:run(Needle, Regex) of + {match, _} -> + true; + nomatch -> + false + end; + (Vsn) -> + Vsn =:= Needle + end, + Haystack). + +%% As a best effort approach, we assume that we only bump patch +%% version numbers between release upgrades for our dependencies and +%% that we deal only with 3-part version schemas +%% (`Major.Minor.Patch'). Using those assumptions, we enumerate the +%% past versions that should be covered by regexes in .appup file +%% instructions. +enumerate_past_versions(Vsn) when is_list(Vsn) -> + case parse_version_number(Vsn) of + {ok, ParsedVsn} -> + {ok, enumerate_past_versions(ParsedVsn)}; + Error -> + Error + end; +enumerate_past_versions({Major, Minor, Patch}) -> + [{Major, Minor, P} || P <- lists:seq(Patch - 1, 0, -1)]. + +parse_version_number(Vsn) when is_list(Vsn) -> + Nums = string:split(Vsn, ".", all), + Results = lists:map(fun string:to_integer/1, Nums), + case Results of + [{Major, []}, {Minor, []}, {Patch, []}] -> + {ok, {Major, Minor, Patch}}; + _ -> + {error, bad_version} + end. + +vsn_number_to_string({Major, Minor, Patch}) -> + io_lib:format("~b.~b.~b", [Major, Minor, Patch]). + read_appup(File) -> %% NOTE: appup file is a script, it may contain variables or functions. case file:script(File, [{'VSN', "VSN"}]) of @@ -339,7 +445,12 @@ update_appups(Changes) -> do_update_appup(App, Upgrade, Downgrade, OldUpgrade, OldDowngrade) -> case locate(src, App, ".appup.src") of {ok, AppupFile} -> - render_appfile(AppupFile, Upgrade, Downgrade); + case contains_contents(AppupFile, Upgrade, Downgrade) of + true -> + ok; + false -> + render_appfile(AppupFile, Upgrade, Downgrade) + end; undefined -> case create_stub(App) of {ok, AppupFile} -> @@ -367,7 +478,8 @@ render_appfile(File, Upgrade, Downgrade) -> ok = file:write_file(File, IOList). create_stub(App) -> - case locate(src, App, Ext = ".app.src") of + Ext = ".app.src", + case locate(src, App, Ext) of {ok, AppSrc} -> DirName = filename:dirname(AppSrc), AppupFile = filename:basename(AppSrc, Ext) ++ ".appup.src", @@ -379,6 +491,17 @@ create_stub(App) -> false end. +%% we check whether the destination file already has the contents we +%% want to write to avoid writing and losing indentation and comments. +contains_contents(File, Upgrade, Downgrade) -> + %% the file may contain the VSN variable, so it's a script + case file:script(File, [{'VSN', 'VSN'}]) of + {ok, {_, Upgrade, Downgrade}} -> + true; + _ -> + false + end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% application and release indexing %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -398,16 +521,18 @@ index_app(AppFile) -> , modules = Modules }}. -diff_app(App, #app{version = NewVersion, modules = NewModules}, #app{version = OldVersion, modules = OldModules}) -> +diff_app(App, + #app{version = NewVersion, modules = NewModules}, + #app{version = OldVersion, modules = OldModules}) -> {New, Changed} = maps:fold( fun(Mod, MD5, {New, Changed}) -> case OldModules of #{Mod := OldMD5} when MD5 =:= OldMD5 -> {New, Changed}; #{Mod := _} -> - {New, [Mod|Changed]}; + {New, [Mod | Changed]}; _ -> - {[Mod|New], Changed} + {[Mod | New], Changed} end end , {[], []} @@ -437,6 +562,15 @@ hashsums(EbinDir) -> filelib:wildcard("*.beam", EbinDir) )). +is_app_external(App) -> + Ext = ".app.src", + case locate(src, App, Ext) of + {ok, _} -> + false; + undefined -> + true + end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Global state %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -522,10 +656,5 @@ log(Msg) -> log(Msg, Args) -> io:format(standard_error, Msg, Args). -ensure_string(Str) when is_binary(Str) -> - binary_to_list(Str); -ensure_string(Str) when is_list(Str) -> - Str. - otp_standard_apps() -> [ssl, mnesia, kernel, asn1, stdlib]. diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index a575ea609..1d2294f98 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -600,7 +600,7 @@ do_publish(PacketId, Msg = #message{qos = ?QOS_2}, ?LOG(warning, "Dropped the qos2 packet ~w " "due to awaiting_rel is full.", [PacketId]), ok = emqx_metrics:inc('packets.publish.dropped'), - handle_out(pubrec, {PacketId, RC}, Channel) + handle_out(disconnect, RC, Channel) end. ensure_quota(_, Channel = #channel{quota = undefined}) -> diff --git a/test/emqx_channel_SUITE.erl b/test/emqx_channel_SUITE.erl index 92d191b2a..c2a29bf55 100644 --- a/test/emqx_channel_SUITE.erl +++ b/test/emqx_channel_SUITE.erl @@ -211,7 +211,8 @@ t_handle_in_qos2_publish_with_error_return(_) -> {ok, ?PUBREC_PACKET(2, ?RC_NO_MATCHING_SUBSCRIBERS), Channel1} = emqx_channel:handle_in(Publish2, Channel), Publish3 = ?PUBLISH_PACKET(?QOS_2, <<"topic">>, 3, <<"payload">>), - {ok, ?PUBREC_PACKET(3, ?RC_RECEIVE_MAXIMUM_EXCEEDED), Channel1} = + {ok, [{outgoing, ?DISCONNECT_PACKET(?RC_RECEIVE_MAXIMUM_EXCEEDED)}, + {close, receive_maximum_exceeded}], Channel1} = emqx_channel:handle_in(Publish3, Channel1). t_handle_in_puback_ok(_) ->