From e9163f2752a47c022b79b2bacd5200a7f2ade045 Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Fri, 24 May 2024 19:09:17 +0800
Subject: [PATCH 01/16] feat: generate relup tarball
To generate a tarball, tag the release and then:
```
make emqx-enterprise-relup
```
---
build | 33 ++-------------------------------
rebar.config.erl | 2 +-
2 files changed, 3 insertions(+), 32 deletions(-)
diff --git a/build b/build
index fd01de2f4..bc2fa4b52 100755
--- a/build
+++ b/build
@@ -213,37 +213,8 @@ make_elixir_rel() {
## extract previous version .tar.gz files to _build/$PROFILE/rel/emqx before making relup
make_relup() {
- local rel_dir="_build/$PROFILE/rel/emqx"
- local name_pattern
- name_pattern="${PROFILE}-$(./pkg-vsn.sh "$PROFILE" --vsn_matcher --long)"
- local releases=()
- mkdir -p _upgrade_base
- while read -r tgzfile ; do
- local base_vsn
- base_vsn="$(echo "$tgzfile" | grep -oE "[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc)\.[0-9])?(-[0-9a-f]{8})?" | head -1)"
- ## we have to create tmp dir to untar old tgz, as `tar --skip-old-files` is not supported on all plantforms
- local tmp_dir
- tmp_dir="$(mktemp -d -t emqx.XXXXXXX)"
- $TAR -C "$tmp_dir" -zxf "$tgzfile"
- mkdir -p "${rel_dir}/releases/"
- cp -npr "$tmp_dir/releases"/* "${rel_dir}/releases/"
- ## There is for some reason a copy of the '$PROFILE.rel' file to releases dir,
- ## the content is duplicated to releases/5.0.0/$PROFILE.rel.
- ## This file seems to be useless, but yet confusing as it does not change after upgrade/downgrade
- ## Hence we force delete this file.
- rm -f "${rel_dir}/releases/${PROFILE}.rel"
- mkdir -p "${rel_dir}/lib/"
- cp -npr "$tmp_dir/lib"/* "${rel_dir}/lib/"
- rm -rf "$tmp_dir"
- releases+=( "$base_vsn" )
- done < <("$FIND" _upgrade_base -maxdepth 1 -name "${name_pattern}.tar.gz" -type f)
- if [ ${#releases[@]} -eq 0 ]; then
- log "No upgrade base found, relup ignored"
- return 0
- fi
- RELX_BASE_VERSIONS="$(IFS=, ; echo "${releases[*]}")"
- export RELX_BASE_VERSIONS
- ./rebar3 as "$PROFILE" relup --relname emqx --relvsn "${PKG_VSN}"
+ ./rebar3 emqx relup_gen --relup-dir=./relup
+ make rel -C _build/default/plugins/emqx_relup
}
cp_dyn_libs() {
diff --git a/rebar.config.erl b/rebar.config.erl
index 5e949ff5a..d01990f33 100644
--- a/rebar.config.erl
+++ b/rebar.config.erl
@@ -184,7 +184,7 @@ project_app_excluded("apps/" ++ AppStr, ExcludedApps) ->
plugins() ->
[
- %{relup_helper, {git, "https://github.com/emqx/relup_helper", {tag, "2.1.0"}}},
+ {emqx_relup, {git, "https://github.com/emqx/emqx-relup.git", {branch, "main"}}},
%% emqx main project does not require port-compiler
%% pin at root level for deterministic
{pc, "v1.14.0"}
From 92594d042be8b15a847fdebc9f12a60d63e4179a Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Fri, 24 May 2024 19:26:45 +0800
Subject: [PATCH 02/16] feat: add some relup examples
---
relup/examples/5.6.0-to-5.6.1.relup | 8 ++++
relup/examples/5.6.1-to-5.6.1+patch.A.relup | 42 +++++++++++++++++++++
relup/examples/upgrade_path.list | 3 ++
3 files changed, 53 insertions(+)
create mode 100644 relup/examples/5.6.0-to-5.6.1.relup
create mode 100644 relup/examples/5.6.1-to-5.6.1+patch.A.relup
create mode 100644 relup/examples/upgrade_path.list
diff --git a/relup/examples/5.6.0-to-5.6.1.relup b/relup/examples/5.6.0-to-5.6.1.relup
new file mode 100644
index 000000000..517e41fc8
--- /dev/null
+++ b/relup/examples/5.6.0-to-5.6.1.relup
@@ -0,0 +1,8 @@
+#{
+ target_version => "5.6.1",
+ from_version => "5.6.0",
+ code_changes =>
+ [ {load_module, emqx_broker}
+ ],
+ post_upgrade_callbacks => []
+}.
diff --git a/relup/examples/5.6.1-to-5.6.1+patch.A.relup b/relup/examples/5.6.1-to-5.6.1+patch.A.relup
new file mode 100644
index 000000000..db2d2c813
--- /dev/null
+++ b/relup/examples/5.6.1-to-5.6.1+patch.A.relup
@@ -0,0 +1,42 @@
+%% This file contains instructions for upgrading from the LAST version to
+%% the CURRENT version.
+%%
+%% We only need to write instructions for the changes in the specific pull request,
+%% these files will be automatically merged/aggregated into a complete relup file
+%% before a new version is released.
+%%
+%% Note that we do not support the 'apply' command in the 'code_changes' section.
+%% If any complex operations are needed during the upgrade process, please add
+%% them in the 'post_upgrade_callbacks' section, and implement them in the
+%% 'emqx_post_upgrade' module.
+
+#{
+ target_version => "5.6.1+patch.A",
+ from_version => "5.6.1",
+ code_changes =>
+ [ {load_module, emqx_post_upgrade}
+ , {load_module, emqx_broker}
+ , {load_module, emqx_metrics}
+ , {load_module, emqx_persistent_message}
+ , {load_module, emqx_dashboard_monitor}
+ , {load_module, emqx_dashboard_monitor_api}
+ , {load_module, emqx_ds_builtin_metrics}
+ , {load_module, emqx_ds_storage_bitfield_lts}
+ , {load_module, emqx_prometheus}
+ , {load_module, emqx_ds_builtin_db_sup}
+ , {load_module, emqx_ds_builtin_sup}
+ , {load_module, emqx_ds_replication_layer}
+ , {load_module, emqx_ds_storage_layer}
+ , {load_module, emqx_broker_helper}
+ , {load_module, emqx_shared_sub}
+ , {load_module, emqx_ds_replication_shard_allocator}
+ , {load_module, emqx_mgmt_api_metrics}
+ , {update, emqx_ds_replication_layer_egress, {advanced, #{}}}
+ ],
+ post_upgrade_callbacks =>
+ [ {pr12781_init_db_metrics, pr12781_termiate_db_metrics}
+ , {pr12781_create_persist_msg_pterm, pr12781_erase_persist_msg_pterm}
+ , {pr12781_trans_tables, pr12781_revert_tables}
+ , {pr12765_update_stats_timer, pr12765_revert_stats_timer}
+ ]
+}.
diff --git a/relup/examples/upgrade_path.list b/relup/examples/upgrade_path.list
new file mode 100644
index 000000000..945dcfcbe
--- /dev/null
+++ b/relup/examples/upgrade_path.list
@@ -0,0 +1,3 @@
+[ "5.6.1+patch.A <- 5.6.1 <- 5.6.0"
+, "5.7.0 <- 5.6.1 <- 5.6.0"
+].
\ No newline at end of file
From 5fca0a16f957d009d1d60f02c19f62f618653087 Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Tue, 28 May 2024 16:56:56 +0800
Subject: [PATCH 03/16] feat: rename emqx_relup to emqx_post_upgrade
---
apps/emqx/src/emqx_post_upgrade.erl | 38 +++++++++++++++++++
apps/emqx/src/emqx_relup.erl | 42 ---------------------
relup/examples/5.6.1-to-5.6.1+patch.A.relup | 5 +--
3 files changed, 39 insertions(+), 46 deletions(-)
create mode 100644 apps/emqx/src/emqx_post_upgrade.erl
delete mode 100644 apps/emqx/src/emqx_relup.erl
diff --git a/apps/emqx/src/emqx_post_upgrade.erl b/apps/emqx/src/emqx_post_upgrade.erl
new file mode 100644
index 000000000..5cbe3877c
--- /dev/null
+++ b/apps/emqx/src/emqx_post_upgrade.erl
@@ -0,0 +1,38 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2017-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%--------------------------------------------------------------------
+
+-module(emqx_post_upgrade).
+
+%% PR#12765
+-export([
+ pr12765_update_stats_timer/1,
+ pr12765_revert_stats_timer/1
+]).
+
+-include("logger.hrl").
+
+%%------------------------------------------------------------------------------
+%% Hot Upgrade Callback Functions.
+%%------------------------------------------------------------------------------
+pr12765_update_stats_timer(_FromVsn) ->
+ emqx_stats:update_interval(broker_stats, fun emqx_broker_helper:stats_fun/0).
+
+pr12765_revert_stats_timer(_ToVsn) ->
+ emqx_stats:update_interval(broker_stats, fun emqx_broker:stats_fun/0).
+
+%%------------------------------------------------------------------------------
+%% Helper functions
+%%------------------------------------------------------------------------------
diff --git a/apps/emqx/src/emqx_relup.erl b/apps/emqx/src/emqx_relup.erl
deleted file mode 100644
index bf9157b96..000000000
--- a/apps/emqx/src/emqx_relup.erl
+++ /dev/null
@@ -1,42 +0,0 @@
-%%--------------------------------------------------------------------
-%% Copyright (c) 2017-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%--------------------------------------------------------------------
-
--module(emqx_relup).
-
-%% NOTE: DO NOT remove this `-include`.
-%% We use this to force this module to be upgraded every release.
--include("emqx_release.hrl").
-
--export([
- post_release_upgrade/2,
- post_release_downgrade/2
-]).
-
--define(INFO(FORMAT), io:format("[emqx_relup] " ++ FORMAT ++ "~n")).
--define(INFO(FORMAT, ARGS), io:format("[emqx_relup] " ++ FORMAT ++ "~n", ARGS)).
-
-%% What to do after upgraded from an old release vsn.
-post_release_upgrade(FromRelVsn, _) ->
- ?INFO("emqx has been upgraded from ~s to ~s!", [FromRelVsn, emqx_release:version()]),
- reload_components().
-
-%% What to do after downgraded to an old release vsn.
-post_release_downgrade(ToRelVsn, _) ->
- ?INFO("emqx has been downgraded from ~s to ~s!", [emqx_release:version(), ToRelVsn]),
- reload_components().
-
-reload_components() ->
- ok.
diff --git a/relup/examples/5.6.1-to-5.6.1+patch.A.relup b/relup/examples/5.6.1-to-5.6.1+patch.A.relup
index db2d2c813..3f0d7676e 100644
--- a/relup/examples/5.6.1-to-5.6.1+patch.A.relup
+++ b/relup/examples/5.6.1-to-5.6.1+patch.A.relup
@@ -34,9 +34,6 @@
, {update, emqx_ds_replication_layer_egress, {advanced, #{}}}
],
post_upgrade_callbacks =>
- [ {pr12781_init_db_metrics, pr12781_termiate_db_metrics}
- , {pr12781_create_persist_msg_pterm, pr12781_erase_persist_msg_pterm}
- , {pr12781_trans_tables, pr12781_revert_tables}
- , {pr12765_update_stats_timer, pr12765_revert_stats_timer}
+ [ {pr12765_update_stats_timer, pr12765_revert_stats_timer}
]
}.
From c6b02bc13f8def3e0c9a53cdcd9a1ec70d804eca Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Thu, 30 May 2024 16:47:33 +0800
Subject: [PATCH 04/16] feat: support starting emqx from relup dir
We put all of the unpacked files into `relup` dir, and warn the user if boot from it
---
bin/emqx | 109 +++++++++++---------
rebar.config.erl | 4 +-
relup/examples/5.6.1-to-5.6.1+patch.A.relup | 3 +-
3 files changed, 63 insertions(+), 53 deletions(-)
diff --git a/bin/emqx b/bin/emqx
index a2f9e2691..d455b97c9 100755
--- a/bin/emqx
+++ b/bin/emqx
@@ -13,53 +13,6 @@ if [ "$DEBUG" -eq 2 ]; then
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
fi
-# We need to find real directory with emqx files on all platforms
-# even when bin/emqx is symlinked on several levels
-# - readlink -f works perfectly, but `-f` flag has completely different meaning in BSD version,
-# so we can't use it universally.
-# - `stat -f%R` on MacOS does exactly what `readlink -f` does on Linux, but we can't use it
-# as a universal solution either because GNU stat has different syntax and this argument is invalid.
-# Also, version of stat which supports this syntax is only available since MacOS 12
-if [ "$(uname -s)" == 'Darwin' ]; then
- product_version="$(sw_vers -productVersion | cut -d '.' -f 1)"
- if [ "$product_version" -ge 12 ]; then
- # if homebrew coreutils package is installed, GNU version of stat can take precedence,
- # so we use absolute path to ensure we are calling MacOS default
- RUNNER_ROOT_DIR="$(cd "$(dirname "$(/usr/bin/stat -f%R "$0" || echo "$0")")"/..; pwd -P)"
- else
- # try our best to resolve link on MacOS <= 11
- RUNNER_ROOT_DIR="$(cd "$(dirname "$(readlink "$0" || echo "$0")")"/..; pwd -P)"
- fi
-else
- RUNNER_ROOT_DIR="$(cd "$(dirname "$(realpath "$0" || echo "$0")")"/..; pwd -P)"
-fi
-
-# shellcheck disable=SC1090,SC1091
-. "$RUNNER_ROOT_DIR"/releases/emqx_vars
-
-# defined in emqx_vars
-export RUNNER_ROOT_DIR
-export EMQX_ETC_DIR
-export REL_VSN
-export SCHEMA_MOD
-export IS_ENTERPRISE
-
-RUNNER_SCRIPT="$RUNNER_BIN_DIR/$REL_NAME"
-CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"
-REL_DIR="$RUNNER_ROOT_DIR/releases/$REL_VSN"
-
-WHOAMI=$(whoami 2>/dev/null || id -u)
-
-# hocon try to read environment variables starting with "EMQX_"
-export HOCON_ENV_OVERRIDE_PREFIX='EMQX_'
-
-export ERTS_DIR="$RUNNER_ROOT_DIR/erts-$ERTS_VSN"
-export BINDIR="$ERTS_DIR/bin"
-export EMU="beam"
-export PROGNAME="erl"
-export ERTS_LIB_DIR="$RUNNER_ROOT_DIR/lib"
-DYNLIBS_DIR="$RUNNER_ROOT_DIR/dynlibs"
-
logerr() {
if [ "${TERM:-dumb}" = dumb ]; then
echo -e "ERROR: $*" 1>&2
@@ -89,6 +42,64 @@ die() {
exit "$errno"
}
+# We need to find real directory with emqx files on all platforms
+# even when bin/emqx is symlinked on several levels
+# - readlink -f works perfectly, but `-f` flag has completely different meaning in BSD version,
+# so we can't use it universally.
+# - `stat -f%R` on MacOS does exactly what `readlink -f` does on Linux, but we can't use it
+# as a universal solution either because GNU stat has different syntax and this argument is invalid.
+# Also, version of stat which supports this syntax is only available since MacOS 12
+if [ "$(uname -s)" == 'Darwin' ]; then
+ product_version="$(sw_vers -productVersion | cut -d '.' -f 1)"
+ if [ "$product_version" -ge 12 ]; then
+ # if homebrew coreutils package is installed, GNU version of stat can take precedence,
+ # so we use absolute path to ensure we are calling MacOS default
+ RUNNER_ROOT_DIR="$(cd "$(dirname "$(/usr/bin/stat -f%R "$0" || echo "$0")")"/..; pwd -P)"
+ else
+ # try our best to resolve link on MacOS <= 11
+ RUNNER_ROOT_DIR="$(cd "$(dirname "$(readlink "$0" || echo "$0")")"/..; pwd -P)"
+ fi
+else
+ RUNNER_ROOT_DIR="$(cd "$(dirname "$(realpath "$0" || echo "$0")")"/..; pwd -P)"
+fi
+
+BASE_RUNNER_ROOT_DIR="${BASE_RUNNER_ROOT_DIR:-$RUNNER_ROOT_DIR}"
+
+if [ -f "$RUNNER_ROOT_DIR/relup/version" ]; then
+ TARGET_VSN=$(cat "$RUNNER_ROOT_DIR/relup/version")
+ export BASE_RUNNER_ROOT_DIR
+ logwarn "Loading emqx from hot upgrade dir: $RUNNER_ROOT_DIR/relup"
+ exec $RUNNER_ROOT_DIR/relup/$TARGET_VSN/bin/emqx "$@"
+else
+ logdebug "Loading emqx from $RUNNER_ROOT_DIR"
+fi
+
+# shellcheck disable=SC1090,SC1091
+. "$RUNNER_ROOT_DIR"/releases/emqx_vars
+
+# defined in emqx_vars
+export RUNNER_ROOT_DIR
+export EMQX_ETC_DIR
+export REL_VSN
+export SCHEMA_MOD
+export IS_ENTERPRISE
+
+RUNNER_SCRIPT="$RUNNER_BIN_DIR/$REL_NAME"
+CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"
+REL_DIR="$RUNNER_ROOT_DIR/releases/$REL_VSN"
+
+WHOAMI=$(whoami 2>/dev/null || id -u)
+
+# hocon try to read environment variables starting with "EMQX_"
+export HOCON_ENV_OVERRIDE_PREFIX='EMQX_'
+
+export ERTS_DIR="$RUNNER_ROOT_DIR/erts-$ERTS_VSN"
+export BINDIR="$ERTS_DIR/bin"
+export EMU="beam"
+export PROGNAME="erl"
+export ERTS_LIB_DIR="$RUNNER_ROOT_DIR/lib"
+DYNLIBS_DIR="$RUNNER_ROOT_DIR/dynlibs"
+
assert_node_alive() {
if ! relx_nodetool "ping" > /dev/null; then
exit 1
@@ -598,7 +609,7 @@ DATA_DIR="$(get_boot_config 'node.data_dir')"
DATA_DIR="${DATA_DIR%/}"
if [[ $DATA_DIR != /* ]]; then
# relative path
- DATA_DIR="${RUNNER_ROOT_DIR}/${DATA_DIR}"
+ DATA_DIR="${BASE_RUNNER_ROOT_DIR}/${DATA_DIR}"
fi
CONFIGS_DIR="$DATA_DIR/configs"
mkdir -p "$CONFIGS_DIR"
@@ -1060,7 +1071,7 @@ nodetool_shutdown() {
logger -t "${REL_NAME}[${PID}]" "STOP: OK"
}
-cd "$RUNNER_ROOT_DIR"
+cd "$BASE_RUNNER_ROOT_DIR"
case "${COMMAND}" in
start)
diff --git a/rebar.config.erl b/rebar.config.erl
index d01990f33..d96b8165d 100644
--- a/rebar.config.erl
+++ b/rebar.config.erl
@@ -392,9 +392,9 @@ overlay_vars_pkg(bin) ->
{platform_etc_dir, "etc"},
{platform_plugins_dir, "plugins"},
{runner_bin_dir, "$RUNNER_ROOT_DIR/bin"},
- {emqx_etc_dir, "$RUNNER_ROOT_DIR/etc"},
+ {emqx_etc_dir, "$BASE_RUNNER_ROOT_DIR/etc"},
{runner_lib_dir, "$RUNNER_ROOT_DIR/lib"},
- {runner_log_dir, "$RUNNER_ROOT_DIR/log"},
+ {runner_log_dir, "$BASE_RUNNER_ROOT_DIR/log"},
{runner_user, ""},
{is_elixir, "no"}
];
diff --git a/relup/examples/5.6.1-to-5.6.1+patch.A.relup b/relup/examples/5.6.1-to-5.6.1+patch.A.relup
index 3f0d7676e..1c833c579 100644
--- a/relup/examples/5.6.1-to-5.6.1+patch.A.relup
+++ b/relup/examples/5.6.1-to-5.6.1+patch.A.relup
@@ -14,8 +14,7 @@
target_version => "5.6.1+patch.A",
from_version => "5.6.1",
code_changes =>
- [ {load_module, emqx_post_upgrade}
- , {load_module, emqx_broker}
+ [ {load_module, emqx_broker}
, {load_module, emqx_metrics}
, {load_module, emqx_persistent_message}
, {load_module, emqx_dashboard_monitor}
From 2008130071b2d3cc97c23e89ab9ce0fc494d209d Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Tue, 9 Jul 2024 16:05:33 +0800
Subject: [PATCH 05/16] feat: add HTTP APIs for relup
---
.../src/emqx_mgmt_api_plugins.erl | 3 +
.../src/emqx_mgmt_api_relup.erl | 643 ++++++++++++++++++
.../proto/emqx_mgmt_api_relup_proto_v1.erl | 43 ++
apps/emqx_plugins/src/emqx_plugins.erl | 14 +-
build | 2 +-
5 files changed, 703 insertions(+), 2 deletions(-)
create mode 100644 apps/emqx_management/src/emqx_mgmt_api_relup.erl
create mode 100644 apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl
diff --git a/apps/emqx_management/src/emqx_mgmt_api_plugins.erl b/apps/emqx_management/src/emqx_mgmt_api_plugins.erl
index 5e6d07a49..97b2b562e 100644
--- a/apps/emqx_management/src/emqx_mgmt_api_plugins.erl
+++ b/apps/emqx_management/src/emqx_mgmt_api_plugins.erl
@@ -566,6 +566,8 @@ install_package(FileName, Bin) ->
ok = filelib:ensure_dir(File),
ok = file:write_file(File, Bin),
PackageName = string:trim(FileName, trailing, ".tar.gz"),
+ MD5 = emqx_utils:bin_to_hexstr(crypto:hash(md5, Bin), lower),
+ ok = file:write_file(emqx_plugins:md5sum_file(PackageName), MD5),
case emqx_plugins:ensure_installed(PackageName, ?fresh_install) of
{error, #{reason := plugin_not_found}} = NotFound ->
NotFound;
@@ -596,6 +598,7 @@ delete_package(Name, _Opts) ->
_ = emqx_plugins:ensure_disabled(Name),
_ = emqx_plugins:ensure_uninstalled(Name),
_ = emqx_plugins:delete_package(Name),
+ _ = file:delete(emqx_plugins:md5sum_file(Name)),
ok;
Error ->
Error
diff --git a/apps/emqx_management/src/emqx_mgmt_api_relup.erl b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
new file mode 100644
index 000000000..5704de8f4
--- /dev/null
+++ b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
@@ -0,0 +1,643 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2020-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%--------------------------------------------------------------------
+-module(emqx_mgmt_api_relup).
+
+-behaviour(minirest_api).
+
+-include_lib("typerefl/include/types.hrl").
+-include_lib("emqx/include/logger.hrl").
+
+-export([get_upgrade_status/0]).
+
+-export([
+ api_spec/0,
+ fields/1,
+ paths/0,
+ schema/1,
+ namespace/0,
+ validate_name/1
+]).
+
+-export([
+ '/relup/package/upload'/2,
+ '/relup/package'/2,
+ '/relup/status'/2,
+ '/relup/status/:node'/2,
+ '/relup/upgrade'/2,
+ '/relup/upgrade/:node'/2
+]).
+
+-define(TAGS, [<<"Relup">>]).
+-define(NAME_RE, "^[A-Za-z]+[A-Za-z0-9-_.]*$").
+-define(CONTENT_PACKAGE, plugin).
+-define(PLUGIN_NAME, <<"emqx_relup">>).
+
+-define(EXAM_VSN1, <<"5.8.0">>).
+-define(EXAM_VSN2, <<"5.8.1">>).
+-define(EXAM_VSN3, <<"5.8.2">>).
+-define(EXAM_PACKAGE_NAME_2, <<"emqx_relup-5.8.1.tar.gz">>).
+-define(EXAM_PACKAGE_NAME_3, <<"emqx_relup-5.8.2.tar.gz">>).
+
+-define(ASSERT_PKG_READY(EXPR),
+ case code:is_loaded(emqx_relup_main) of
+ false -> return_bad_request(<<"No relup package is installed">>);
+ {file, _} -> EXPR
+ end
+).
+
+%%==============================================================================
+%% API Spec
+namespace() ->
+ "relup".
+
+api_spec() ->
+ emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
+
+paths() ->
+ [
+ "/relup/package",
+ "/relup/package/upload",
+ "/relup/status",
+ "/relup/status/:node",
+ "/relup/upgrade",
+ "/relup/upgrade/:node"
+ ].
+
+schema("/relup/package/upload") ->
+ #{
+ 'operationId' => '/relup/package/upload',
+ post => #{
+ summary => <<"Upload a hot upgrade package">>,
+ description => <<
+ "Upload a hot upgrade package (emqx_relup-vsn.tar.gz).
"
+ "Note that only one package is alllowed to be installed at a time."
+ >>,
+ tags => ?TAGS,
+ 'requestBody' => #{
+ content => #{
+ 'multipart/form-data' => #{
+ schema => #{
+ type => object,
+ properties => #{
+ ?CONTENT_PACKAGE => #{type => string, format => binary}
+ }
+ },
+ encoding => #{?CONTENT_PACKAGE => #{'contentType' => 'application/gzip'}}
+ }
+ }
+ },
+ responses => #{
+ 204 => <<"Package is uploaded successfully">>,
+ 400 => emqx_dashboard_swagger:error_codes(
+ ['UNEXPECTED_ERROR', 'BAD_PLUGIN_INFO']
+ )
+ }
+ }
+ };
+schema("/relup/package") ->
+ #{
+ 'operationId' => '/relup/package',
+ get => #{
+ summary => <<"Get the installed hot upgrade package">>,
+ description =>
+ <<"Get information of the installed hot upgrade package.
">>,
+ tags => ?TAGS,
+ responses => #{
+ 200 => hoconsc:ref(package),
+ 404 => emqx_dashboard_swagger:error_codes(
+ ['NOT_FOUND'],
+ <<"No relup package is installed">>
+ )
+ }
+ },
+ delete => #{
+ summary => <<"Delete the installed hot upgrade package">>,
+ description =>
+ <<"Delete the installed hot upgrade package.
">>,
+ tags => ?TAGS,
+ responses => #{
+ 204 => <<"Packages are deleted successfully">>
+ }
+ }
+ };
+schema("/relup/status") ->
+ #{
+ 'operationId' => '/relup/status',
+ get => #{
+ summary => <<"Get the hot upgrade status of all nodes">>,
+ description => <<"Get the hot upgrade status of all nodes">>,
+ tags => ?TAGS,
+ responses => #{
+ 200 => hoconsc:array(hoconsc:ref(running_status))
+ }
+ }
+ };
+schema("/relup/status/:node") ->
+ #{
+ 'operationId' => '/relup/status/:node',
+ get => #{
+ summary => <<"Get the hot upgrade status of a specified node">>,
+ description => <<"Get the hot upgrade status of a specified node">>,
+ tags => ?TAGS,
+ parameters => [hoconsc:ref(node_name)],
+ responses => #{
+ 200 => hoconsc:ref(running_status)
+ }
+ }
+ };
+schema("/relup/upgrade") ->
+ #{
+ 'operationId' => '/relup/upgrade',
+ post => #{
+ summary => <<"Upgrade all nodes">>,
+ description => <<
+ "Upgrade all nodes to the target version with the installed package."
+ >>,
+ tags => ?TAGS,
+ responses => #{
+ 204 => <<"Upgrade is started successfully">>,
+ 400 => emqx_dashboard_swagger:error_codes(
+ ['UNEXPECTED_ERROR'],
+ <<"Upgrade failed because of invalid input or environment">>
+ ),
+ 500 => emqx_dashboard_swagger:error_codes(
+ ['INTERNAL_ERROR'], <<"Upgrade failed because of internal errors">>
+ )
+ }
+ }
+ };
+schema("/relup/upgrade/:node") ->
+ #{
+ 'operationId' => '/relup/upgrade/:node',
+ post => #{
+ summary => <<"Upgrade a specified node">>,
+ description => <<
+ "Upgrade a specified node to the target version with the installed package."
+ >>,
+ tags => ?TAGS,
+ parameters => [hoconsc:ref(node_name)],
+ responses => #{
+ 204 => <<"Upgrade is started successfully">>,
+ 400 => emqx_dashboard_swagger:error_codes(
+ ['UNEXPECTED_ERROR'],
+ <<"Upgrade failed because of invalid input or environment">>
+ ),
+ 404 => emqx_dashboard_swagger:error_codes(
+ ['NOT_FOUND'],
+ <<"Node not found">>
+ ),
+ 500 => emqx_dashboard_swagger:error_codes(
+ ['INTERNAL_ERROR'], <<"Upgrade failed because of internal errors">>
+ )
+ }
+ }
+ }.
+
+%%==============================================================================
+%% Field definitions
+fields(package) ->
+ [
+ {name,
+ hoconsc:mk(
+ binary(),
+ #{
+ desc => <<"File name of the package">>,
+ validator => fun ?MODULE:validate_name/1,
+ example => ?EXAM_PACKAGE_NAME_3
+ }
+ )},
+ {target_vsn,
+ hoconsc:mk(
+ binary(),
+ #{
+ desc => <<"Target emqx version for this package">>,
+ example => ?EXAM_VSN3
+ }
+ )},
+ {built_on_otp_release, hoconsc:mk(binary(), #{example => <<"24">>})},
+ {applicable_vsns,
+ hoconsc:mk(hoconsc:array(binary()), #{
+ example => [?EXAM_VSN1, ?EXAM_VSN2],
+ desc => <<"The emqx versions that this package can be applied to.">>
+ })},
+ {build_date,
+ hoconsc:mk(binary(), #{
+ example => <<"2021-12-25">>,
+ desc => <<"The date when the package was built.">>
+ })},
+ {change_logs,
+ hoconsc:mk(
+ hoconsc:array(binary()),
+ #{
+ desc => <<"Changes that this package brings">>,
+ example => [
+ <<
+ "1. Fix a bug foo in the plugin."
+ "2. Add a new bar feature."
+ >>
+ ]
+ }
+ )},
+ {md5_sum, hoconsc:mk(binary(), #{example => <<"d41d8cd98f00b204e9800998ecf8427e">>})}
+ ];
+fields(upgrade_history) ->
+ [
+ {started_at,
+ hoconsc:mk(
+ binary(),
+ #{
+ desc => <<"The timestamp (in format of RFC3339) when the upgrade started">>,
+ example => <<"2024-07-15T13:48:02.648559+08:00">>
+ }
+ )},
+ {finished_at,
+ hoconsc:mk(
+ binary(),
+ #{
+ desc => <<"The timestamp (in format of RFC3339) when the upgrade finished">>,
+ example => <<"2024-07-16T11:00:01.875627+08:00">>
+ }
+ )},
+ {from_vsn,
+ hoconsc:mk(
+ binary(),
+ #{
+ desc => <<"The version before the upgrade">>,
+ example => ?EXAM_VSN1
+ }
+ )},
+ {target_vsn,
+ hoconsc:mk(
+ binary(),
+ #{
+ desc => <<"The target version of the upgrade">>,
+ example => ?EXAM_VSN3
+ }
+ )},
+ {upgrade_opts,
+ hoconsc:mk(
+ map(),
+ #{
+ desc => <<"The options used for the upgrade">>,
+ example => #{deploy_inplace => false}
+ }
+ )},
+ {status,
+ hoconsc:mk(
+ hoconsc:enum(['in-progress', finished]),
+ #{
+ desc => <<"The upgrade status of the node">>,
+ example => 'in-progress'
+ }
+ )},
+ {result,
+ hoconsc:mk(
+ hoconsc:union([success, hoconsc:ref(?MODULE, upgrade_error)]),
+ #{
+ desc => <<"The upgrade result">>,
+ example => success
+ }
+ )}
+ ];
+fields(running_status) ->
+ [
+ {node, hoconsc:mk(binary(), #{example => <<"emqx@127.0.0.1">>})},
+ {status,
+ hoconsc:mk(hoconsc:enum(['in-progress', idle]), #{
+ desc => <<
+ "The upgrade status of a node:
"
+ "1. in-progress: hot upgrade is in progress.
"
+ "2. idle: hot upgrade is not started.
"
+ >>
+ })},
+ {role,
+ hoconsc:mk(hoconsc:enum([core, replicant]), #{
+ desc => <<"The role of the node">>,
+ example => core
+ })},
+ {live_connections,
+ hoconsc:mk(integer(), #{
+ desc => <<"The number of live connections">>,
+ example => 100
+ })},
+ {current_vsn,
+ hoconsc:mk(binary(), #{
+ desc => <<"The current version of the node">>,
+ example => ?EXAM_VSN1
+ })},
+ {upgrade_history,
+ hoconsc:mk(
+ hoconsc:array(hoconsc:ref(upgrade_history)),
+ #{
+ desc => <<"The upgrade history of the node">>,
+ example => [
+ #{
+ started_at => <<"2024-07-15T13:48:02.648559+08:00">>,
+ finished_at => <<"2024-07-16T11:00:01.875627+08:00">>,
+ from_vsn => ?EXAM_VSN1,
+ target_vsn => ?EXAM_VSN2,
+ upgrade_opts => #{deploy_inplace => false},
+ status => finished,
+ result => success
+ }
+ ]
+ }
+ )}
+ ];
+fields(upgrade_error) ->
+ [
+ {err_type,
+ hoconsc:mk(
+ binary(),
+ #{
+ desc => <<"The type of the error">>,
+ example => <<"no_write_permission">>
+ }
+ )},
+ {details,
+ hoconsc:mk(
+ map(),
+ #{
+ desc => <<"The details of the error">>,
+ example => #{
+ dir => <<"emqx/relup">>,
+ msg => <<"no write permission in dir 'emqx/relup'">>
+ }
+ }
+ )}
+ ];
+fields(node_name) ->
+ [
+ {node,
+ hoconsc:mk(
+ binary(),
+ #{
+ default => all,
+ in => path,
+ desc => <<"The node to be upgraded">>,
+ example => <<"emqx@127.0.0.1">>
+ }
+ )}
+ ].
+
+validate_name(Name) ->
+ NameLen = byte_size(Name),
+ case NameLen > 0 andalso NameLen =< 256 of
+ true ->
+ case re:run(Name, ?NAME_RE) of
+ nomatch -> {error, <<"Name should be " ?NAME_RE>>};
+ _ -> ok
+ end;
+ false ->
+ {error, <<"Name Length must =< 256">>}
+ end.
+
+%%==============================================================================
+%% HTTP API CallBacks
+
+'/relup/package/upload'(post, #{body := #{<<"plugin">> := Plugin}} = Params) ->
+ case emqx_plugins:list() of
+ [] ->
+ [{FileName, _Bin}] = maps:to_list(maps:without([type], Plugin)),
+ NameVsn = string:trim(FileName, trailing, ".tar.gz"),
+ %% we install a relup package as a "hidden" plugin
+ case emqx_mgmt_api_plugins:upload_install(post, Params) of
+ {204} ->
+ case emqx_mgmt_api_plugins_proto_v3:ensure_action(NameVsn, start) of
+ ok ->
+ {204};
+ {error, Reason} ->
+ %% try our best to clean up if start failed
+ _ = emqx_mgmt_api_plugins_proto_v3:delete_package(NameVsn),
+ return_internal_error(Reason)
+ end;
+ ErrResp ->
+ ErrResp
+ end;
+ _ ->
+ {400, #{
+ code => 'BAD_REQUEST',
+ message => <<
+ "Only one relup package can be installed at a time."
+ "Please delete the existing package first."
+ >>
+ }}
+ end.
+
+'/relup/package'(get, _) ->
+ case get_installed_packages() of
+ [PluginInfo] ->
+ {200, format_package_info(PluginInfo)};
+ [] ->
+ return_not_found(<<"No relup package is installed">>)
+ end;
+'/relup/package'(delete, _) ->
+ delete_installed_packages(),
+ {204}.
+
+'/relup/status'(get, _) ->
+ ?ASSERT_PKG_READY(begin
+ {[_ | _] = Res, []} = emqx_mgmt_api_relup_proto_v1:get_upgrade_status_from_all_nodes(),
+ case
+ lists:filter(
+ fun
+ (R) when is_map(R) -> false;
+ (_) -> true
+ end,
+ Res
+ )
+ of
+ [] ->
+ {200, Res};
+ Filtered ->
+ return_internal_error(
+ case hd(Filtered) of
+ {badrpc, Reason} -> Reason;
+ Reason -> Reason
+ end
+ )
+ end
+ end).
+
+'/relup/status/:node'(get, #{bindings := #{node := NodeNameStr}}) ->
+ ?ASSERT_PKG_READY(
+ emqx_utils_api:with_node(
+ NodeNameStr,
+ fun
+ (Node) when node() =:= Node ->
+ {200, get_upgrade_status()};
+ (Node) when is_atom(Node) ->
+ {200, emqx_mgmt_api_relup_proto_v1:get_upgrade_status(Node)}
+ end
+ )
+ ).
+
+'/relup/upgrade'(post, _) ->
+ ?ASSERT_PKG_READY(
+ upgrade_with_targe_vsn(fun(TargetVsn) ->
+ run_upgrade_on_nodes(emqx:running_nodes(), TargetVsn)
+ end)
+ ).
+
+'/relup/upgrade/:node'(post, #{bindings := #{node := NodeNameStr}}) ->
+ ?ASSERT_PKG_READY(
+ upgrade_with_targe_vsn(
+ fun(TargetVsn) ->
+ emqx_utils_api:with_node(
+ NodeNameStr,
+ fun
+ (Node) when node() =:= Node ->
+ run_upgrade(TargetVsn);
+ (Node) when is_atom(Node) ->
+ run_upgrade_on_nodes([Node], TargetVsn)
+ end
+ )
+ end
+ )
+ ).
+%%==============================================================================
+%% Helper functions
+
+get_upgrade_status() ->
+ #{
+ node => node(),
+ role => mria_rlog:role(),
+ live_connections => emqx_cm:get_connected_client_count(),
+ current_vsn => list_to_binary(emqx_release:version()),
+ status => emqx_relup_main:get_latest_upgrade_status(),
+ upgrade_history => emqx_relup_main:get_all_upgrade_logs()
+ }.
+
+upgrade_with_targe_vsn(Fun) ->
+ case get_target_vsn() of
+ {ok, TargetVsn} ->
+ Fun(TargetVsn);
+ {error, no_relup_package_installed} ->
+ return_bad_request(<<"No relup package is installed">>);
+ {error, multiple_relup_packages_installed} ->
+ return_internal_error(<<"Multiple relup package installed">>)
+ end.
+
+run_upgrade_on_nodes(Nodes, TargetVsn) ->
+ {[_ | _] = Res, []} = emqx_mgmt_api_relup_proto_v1:run_upgrade(Nodes, TargetVsn),
+ case lists:filter(fun(R) -> R =/= ok end, Res) of
+ [] ->
+ {204};
+ Filtered ->
+ upgrade_return(
+ case hd(Filtered) of
+ {badrpc, Reason} -> Reason;
+ {error, Reason} -> Reason;
+ Reason -> Reason
+ end
+ )
+ end.
+
+run_upgrade(TargetVsn) ->
+ case emqx_relup_main:upgrade(TargetVsn) of
+ ok -> {204};
+ {error, Reason} -> upgrade_return(Reason)
+ end.
+
+get_target_vsn() ->
+ case get_installed_packages() of
+ [PackageInfo] -> {ok, target_vsn_from_rel_vsn(maps_get(rel_vsn, PackageInfo))};
+ [] -> {error, no_relup_package_installed};
+ _ -> {error, multiple_relup_packages_installed}
+ end.
+
+get_installed_packages() ->
+ lists:filtermap(
+ fun(PackageInfo) ->
+ case maps_get(name, PackageInfo) of
+ ?PLUGIN_NAME -> true;
+ _ -> false
+ end
+ end,
+ emqx_plugins:list()
+ ).
+
+target_vsn_from_rel_vsn(Vsn) ->
+ case string:split(binary_to_list(Vsn), "-") of
+ [VsnStr | _] -> VsnStr;
+ _ -> throw({invalid_vsn, Vsn})
+ end.
+
+delete_installed_packages() ->
+ lists:foreach(
+ fun(PackageInfo) ->
+ ok = emqx_mgmt_api_plugins_proto_v3:delete_package(
+ name_vsn(?PLUGIN_NAME, maps_get(rel_vsn, PackageInfo))
+ )
+ end,
+ get_installed_packages()
+ ).
+
+format_package_info(PluginInfo) when is_map(PluginInfo) ->
+ Vsn = maps_get(rel_vsn, PluginInfo),
+ case emqx_relup_main:get_package_info(target_vsn_from_rel_vsn(Vsn)) of
+ {error, Reason} ->
+ throw({get_pkg_info_failed, Reason});
+ {ok, #{base_vsns := BaseVsns, change_logs := ChangeLogs}} ->
+ #{
+ name => name_vsn(?PLUGIN_NAME, Vsn),
+ target_vsn => Vsn,
+ built_on_otp_release => maps_get(built_on_otp_release, PluginInfo),
+ applicable_vsns => BaseVsns,
+ build_date => maps_get(git_commit_or_build_date, PluginInfo),
+ change_logs => ChangeLogs,
+ md5_sum => maps_get(md5sum, PluginInfo)
+ }
+ end.
+
+maps_get(Key, Map) when is_atom(Key) ->
+ maps_get(Key, Map, unknown).
+
+maps_get(Key, Map, Def) when is_atom(Key) ->
+ case maps:find(Key, Map) of
+ {ok, Value} -> Value;
+ error -> maps:get(atom_to_binary(Key, utf8), Map, Def)
+ end.
+
+upgrade_return(#{stage := check_and_unpack} = Reason) ->
+ return_bad_request(Reason);
+upgrade_return(Reason) ->
+ return_internal_error(Reason).
+
+return_not_found(Reason) ->
+ {404, #{
+ code => 'NOT_FOUND',
+ message => emqx_utils:readable_error_msg(Reason)
+ }}.
+
+return_bad_request(Reason) ->
+ {400, #{
+ code => 'BAD_REQUEST',
+ message => emqx_utils:readable_error_msg(Reason)
+ }}.
+
+return_internal_error(Reason) ->
+ {500, #{
+ code => 'INTERNAL_ERROR',
+ message => emqx_utils:readable_error_msg(Reason)
+ }}.
+
+name_vsn(Name, Vsn) ->
+ bin([Name, "-", Vsn]).
+
+bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
+bin(L) when is_list(L) -> unicode:characters_to_binary(L, utf8);
+bin(B) when is_binary(B) -> B.
diff --git a/apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl b/apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl
new file mode 100644
index 000000000..8f389f003
--- /dev/null
+++ b/apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl
@@ -0,0 +1,43 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%--------------------------------------------------------------------
+-module(emqx_mgmt_api_relup_proto_v1).
+
+-behaviour(emqx_bpapi).
+
+-include_lib("emqx/include/bpapi.hrl").
+
+-export([
+ introduced_in/0,
+ run_upgrade/2,
+ get_upgrade_status_from_all_nodes/0,
+ get_upgrade_status/1
+]).
+
+-define(RPC_TIMEOUT_OP, 180_000).
+-define(RPC_TIMEOUT_INFO, 15_000).
+
+introduced_in() ->
+ "5.8.0".
+
+-spec run_upgrade([node()], string()) -> emqx_rpc:multicall_result().
+run_upgrade(Nodes, TargetVsn) ->
+ rpc:multicall(Nodes, emqx_relup_main, upgrade, [TargetVsn], ?RPC_TIMEOUT_OP).
+
+get_upgrade_status_from_all_nodes() ->
+ rpc:multicall(emqx_mgmt_api_relup, get_upgrade_status, [], ?RPC_TIMEOUT_INFO).
+
+get_upgrade_status(Node) ->
+ rpc:call(Node, emqx_mgmt_api_relup, get_upgrade_status, [], ?RPC_TIMEOUT_INFO).
diff --git a/apps/emqx_plugins/src/emqx_plugins.erl b/apps/emqx_plugins/src/emqx_plugins.erl
index 253d1b4ef..c7ae9fd2c 100644
--- a/apps/emqx_plugins/src/emqx_plugins.erl
+++ b/apps/emqx_plugins/src/emqx_plugins.erl
@@ -73,6 +73,7 @@
decode_plugin_config_map/2,
install_dir/0,
avsc_file_path/1,
+ md5sum_file/1,
with_plugin_avsc/1
]).
@@ -736,7 +737,8 @@ do_read_plugin(NameVsn, InfoFilePath, Options) ->
{ok, PlainMap} = (read_file_fun(InfoFilePath, "bad_info_file", #{read_mode => ?JSON_MAP}))(),
Info0 = check_plugin(PlainMap, NameVsn, InfoFilePath),
Info1 = plugins_readme(NameVsn, Options, Info0),
- plugin_status(NameVsn, Info1).
+ Info2 = plugins_package_info(NameVsn, Info1),
+ plugin_status(NameVsn, Info2).
read_plugin_avsc(NameVsn) ->
read_plugin_avsc(NameVsn, #{read_mode => ?JSON_MAP}).
@@ -837,6 +839,12 @@ get_plugin_config_from_any_node([Node | T], NameVsn, Errors) ->
get_plugin_config_from_any_node(T, NameVsn, [{Node, Err} | Errors])
end.
+plugins_package_info(NameVsn, Info) ->
+ case file:read_file(md5sum_file(NameVsn)) of
+ {ok, MD5} -> Info#{md5sum => MD5};
+ _ -> Info#{md5sum => <<>>}
+ end.
+
plugins_readme(NameVsn, #{fill_readme := true}, Info) ->
case file:read_file(readme_file(NameVsn)) of
{ok, Bin} -> Info#{readme => Bin};
@@ -1489,6 +1497,10 @@ default_plugin_config_file(NameVsn) ->
i18n_file_path(NameVsn) ->
wrap_to_list(filename:join([plugin_priv_dir(NameVsn), "config_i18n.json"])).
+-spec md5sum_file(name_vsn()) -> string().
+md5sum_file(NameVsn) ->
+ plugin_dir(NameVsn) ++ ".tar.gz.md5sum".
+
-spec readme_file(name_vsn()) -> string().
readme_file(NameVsn) ->
wrap_to_list(filename:join([plugin_dir(NameVsn), "README.md"])).
diff --git a/build b/build
index bc2fa4b52..8a8fd7776 100755
--- a/build
+++ b/build
@@ -211,8 +211,8 @@ make_elixir_rel() {
assert_no_excluded_deps emqx-enterprise emqx_telemetry
}
-## extract previous version .tar.gz files to _build/$PROFILE/rel/emqx before making relup
make_relup() {
+ export RELUP_TARGET_VSN="$(./pkg-vsn.sh "$PROFILE" --long)"
./rebar3 emqx relup_gen --relup-dir=./relup
make rel -C _build/default/plugins/emqx_relup
}
From 79b65a28c18de780c8332e2b369dd39583391e08 Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Thu, 18 Jul 2024 18:54:10 +0800
Subject: [PATCH 06/16] chore: use emqx-relup 0.1.0
---
rebar.config.erl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config.erl b/rebar.config.erl
index d96b8165d..6b857a610 100644
--- a/rebar.config.erl
+++ b/rebar.config.erl
@@ -184,7 +184,7 @@ project_app_excluded("apps/" ++ AppStr, ExcludedApps) ->
plugins() ->
[
- {emqx_relup, {git, "https://github.com/emqx/emqx-relup.git", {branch, "main"}}},
+ {emqx_relup, {git, "https://github.com/emqx/emqx-relup.git", {branch, "0.1.0"}}},
%% emqx main project does not require port-compiler
%% pin at root level for deterministic
{pc, "v1.14.0"}
From 3c8ef35b18507a6d36f81a20934c6dfe91ee736d Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Fri, 19 Jul 2024 11:20:03 +0800
Subject: [PATCH 07/16] fix: show relup status even if no packages installed
---
.../src/emqx_mgmt_api_relup.erl | 136 ++++++++++--------
1 file changed, 77 insertions(+), 59 deletions(-)
diff --git a/apps/emqx_management/src/emqx_mgmt_api_relup.erl b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
index 5704de8f4..a31551b6d 100644
--- a/apps/emqx_management/src/emqx_mgmt_api_relup.erl
+++ b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
@@ -408,33 +408,22 @@ validate_name(Name) ->
%%==============================================================================
%% HTTP API CallBacks
-'/relup/package/upload'(post, #{body := #{<<"plugin">> := Plugin}} = Params) ->
- case emqx_plugins:list() of
- [] ->
- [{FileName, _Bin}] = maps:to_list(maps:without([type], Plugin)),
- NameVsn = string:trim(FileName, trailing, ".tar.gz"),
- %% we install a relup package as a "hidden" plugin
- case emqx_mgmt_api_plugins:upload_install(post, Params) of
- {204} ->
- case emqx_mgmt_api_plugins_proto_v3:ensure_action(NameVsn, start) of
- ok ->
- {204};
- {error, Reason} ->
- %% try our best to clean up if start failed
- _ = emqx_mgmt_api_plugins_proto_v3:delete_package(NameVsn),
- return_internal_error(Reason)
- end;
- ErrResp ->
- ErrResp
+'/relup/package/upload'(post, #{body := #{<<"plugin">> := UploadBody}} = Params) ->
+ case assert_relup_pkg_name(UploadBody) of
+ {ok, NameVsn} ->
+ case get_installed_packages() of
+ [] ->
+ install_as_hidden_plugin(NameVsn, Params);
+ _ ->
+ return_bad_request(
+ <<
+ "Only one relup package can be installed at a time."
+ "Please delete the existing package first."
+ >>
+ )
end;
- _ ->
- {400, #{
- code => 'BAD_REQUEST',
- message => <<
- "Only one relup package can be installed at a time."
- "Please delete the existing package first."
- >>
- }}
+ {error, Reason} ->
+ return_bad_request(Reason)
end.
'/relup/package'(get, _) ->
@@ -449,40 +438,36 @@ validate_name(Name) ->
{204}.
'/relup/status'(get, _) ->
- ?ASSERT_PKG_READY(begin
- {[_ | _] = Res, []} = emqx_mgmt_api_relup_proto_v1:get_upgrade_status_from_all_nodes(),
- case
- lists:filter(
- fun
- (R) when is_map(R) -> false;
- (_) -> true
- end,
- Res
+ {[_ | _] = Res, []} = emqx_mgmt_api_relup_proto_v1:get_upgrade_status_from_all_nodes(),
+ case
+ lists:filter(
+ fun
+ (R) when is_map(R) -> false;
+ (_) -> true
+ end,
+ Res
+ )
+ of
+ [] ->
+ {200, Res};
+ Filtered ->
+ return_internal_error(
+ case hd(Filtered) of
+ {badrpc, Reason} -> Reason;
+ Reason -> Reason
+ end
)
- of
- [] ->
- {200, Res};
- Filtered ->
- return_internal_error(
- case hd(Filtered) of
- {badrpc, Reason} -> Reason;
- Reason -> Reason
- end
- )
- end
- end).
+ end.
'/relup/status/:node'(get, #{bindings := #{node := NodeNameStr}}) ->
- ?ASSERT_PKG_READY(
- emqx_utils_api:with_node(
- NodeNameStr,
- fun
- (Node) when node() =:= Node ->
- {200, get_upgrade_status()};
- (Node) when is_atom(Node) ->
- {200, emqx_mgmt_api_relup_proto_v1:get_upgrade_status(Node)}
- end
- )
+ emqx_utils_api:with_node(
+ NodeNameStr,
+ fun
+ (Node) when node() =:= Node ->
+ {200, get_upgrade_status()};
+ (Node) when is_atom(Node) ->
+ {200, emqx_mgmt_api_relup_proto_v1:get_upgrade_status(Node)}
+ end
).
'/relup/upgrade'(post, _) ->
@@ -511,16 +496,49 @@ validate_name(Name) ->
%%==============================================================================
%% Helper functions
+install_as_hidden_plugin(NameVsn, Params) ->
+ case emqx_mgmt_api_plugins:upload_install(post, Params) of
+ {204} ->
+ case emqx_mgmt_api_plugins_proto_v3:ensure_action(NameVsn, start) of
+ ok ->
+ {204};
+ {error, Reason} ->
+ %% try our best to clean up if start failed
+ _ = emqx_mgmt_api_plugins_proto_v3:delete_package(NameVsn),
+ return_internal_error(Reason)
+ end;
+ ErrResp ->
+ ErrResp
+ end.
+
+assert_relup_pkg_name(UploadBody) ->
+ [{FileName, _Bin}] = maps:to_list(maps:without([type], UploadBody)),
+ case string:split(FileName, "-") of
+ [?PLUGIN_NAME, _] ->
+ {ok, string:trim(FileName, trailing, ".tar.gz")};
+ _ ->
+ {error, <<"Invalid relup package name: ", FileName/binary>>}
+ end.
+
get_upgrade_status() ->
#{
node => node(),
role => mria_rlog:role(),
live_connections => emqx_cm:get_connected_client_count(),
current_vsn => list_to_binary(emqx_release:version()),
- status => emqx_relup_main:get_latest_upgrade_status(),
- upgrade_history => emqx_relup_main:get_all_upgrade_logs()
+ status => call_emqx_relup_main(get_latest_upgrade_status, [], idle),
+ upgrade_history => call_emqx_relup_main(get_all_upgrade_logs, [], [])
}.
+call_emqx_relup_main(Fun, Args, Default) ->
+ case erlang:function_exported(emqx_relup_main, Fun, length(Args)) of
+ true ->
+ apply(emqx_relup_main, Fun, Args);
+ false ->
+ %% relup package is not installed
+ Default
+ end.
+
upgrade_with_targe_vsn(Fun) ->
case get_target_vsn() of
{ok, TargetVsn} ->
From 3ad7dc262bb88d1b63ed0adf8a14c472e9a13384 Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Thu, 18 Jul 2024 19:00:55 +0800
Subject: [PATCH 08/16] fix: some sanity-checks
---
Makefile | 4 ++--
apps/emqx/src/emqx_post_upgrade.erl | 16 ++++++++--------
apps/emqx_management/src/emqx_mgmt_api_relup.erl | 2 ++
bin/emqx | 2 +-
build | 3 ++-
rebar.config.erl | 2 +-
relup/examples/upgrade_path.list | 2 +-
7 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/Makefile b/Makefile
index ae92feced..30a23793b 100644
--- a/Makefile
+++ b/Makefile
@@ -238,7 +238,7 @@ $(foreach zt,$(ALL_ZIPS),$(eval $(call download-relup-packages,$(zt))))
## relup target is to create relup instructions
.PHONY: $(REL_PROFILES:%=%-relup)
define gen-relup-target
-$1-relup: $1-relup-downloads $(COMMON_DEPS)
+$1-relup: $(COMMON_DEPS)
@$(BUILD) $1 relup
endef
ALL_TGZS = $(REL_PROFILES)
@@ -247,7 +247,7 @@ $(foreach zt,$(ALL_TGZS),$(eval $(call gen-relup-target,$(zt))))
## tgz target is to create a release package .tar.gz with relup
.PHONY: $(REL_PROFILES:%=%-tgz)
define gen-tgz-target
-$1-tgz: $1-relup
+$1-tgz: $(COMMON_DEPS)
@$(BUILD) $1 tgz
endef
ALL_TGZS = $(REL_PROFILES)
diff --git a/apps/emqx/src/emqx_post_upgrade.erl b/apps/emqx/src/emqx_post_upgrade.erl
index 5cbe3877c..6b09f2a30 100644
--- a/apps/emqx/src/emqx_post_upgrade.erl
+++ b/apps/emqx/src/emqx_post_upgrade.erl
@@ -17,21 +17,21 @@
-module(emqx_post_upgrade).
%% PR#12765
--export([
- pr12765_update_stats_timer/1,
- pr12765_revert_stats_timer/1
-]).
+% -export([
+% pr12765_update_stats_timer/1,
+% pr12765_revert_stats_timer/1
+% ]).
-include("logger.hrl").
%%------------------------------------------------------------------------------
%% Hot Upgrade Callback Functions.
%%------------------------------------------------------------------------------
-pr12765_update_stats_timer(_FromVsn) ->
- emqx_stats:update_interval(broker_stats, fun emqx_broker_helper:stats_fun/0).
+% pr12765_update_stats_timer(_FromVsn) ->
+% emqx_stats:update_interval(broker_stats, fun emqx_broker_helper:stats_fun/0).
-pr12765_revert_stats_timer(_ToVsn) ->
- emqx_stats:update_interval(broker_stats, fun emqx_broker:stats_fun/0).
+% pr12765_revert_stats_timer(_ToVsn) ->
+% emqx_stats:update_interval(broker_stats, fun emqx_broker:stats_fun/0).
%%------------------------------------------------------------------------------
%% Helper functions
diff --git a/apps/emqx_management/src/emqx_mgmt_api_relup.erl b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
index a31551b6d..b347cd483 100644
--- a/apps/emqx_management/src/emqx_mgmt_api_relup.erl
+++ b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
@@ -40,6 +40,8 @@
'/relup/upgrade/:node'/2
]).
+-ignore_xref(emqx_relup_main).
+
-define(TAGS, [<<"Relup">>]).
-define(NAME_RE, "^[A-Za-z]+[A-Za-z0-9-_.]*$").
-define(CONTENT_PACKAGE, plugin).
diff --git a/bin/emqx b/bin/emqx
index d455b97c9..c32c00474 100755
--- a/bin/emqx
+++ b/bin/emqx
@@ -69,7 +69,7 @@ if [ -f "$RUNNER_ROOT_DIR/relup/version" ]; then
TARGET_VSN=$(cat "$RUNNER_ROOT_DIR/relup/version")
export BASE_RUNNER_ROOT_DIR
logwarn "Loading emqx from hot upgrade dir: $RUNNER_ROOT_DIR/relup"
- exec $RUNNER_ROOT_DIR/relup/$TARGET_VSN/bin/emqx "$@"
+ exec "$RUNNER_ROOT_DIR"/relup/"$TARGET_VSN"/bin/emqx "$@"
else
logdebug "Loading emqx from $RUNNER_ROOT_DIR"
fi
diff --git a/build b/build
index 8a8fd7776..b20b1f70f 100755
--- a/build
+++ b/build
@@ -212,7 +212,8 @@ make_elixir_rel() {
}
make_relup() {
- export RELUP_TARGET_VSN="$(./pkg-vsn.sh "$PROFILE" --long)"
+ RELUP_TARGET_VSN="$(./pkg-vsn.sh "$PROFILE" --long)"
+ export RELUP_TARGET_VSN
./rebar3 emqx relup_gen --relup-dir=./relup
make rel -C _build/default/plugins/emqx_relup
}
diff --git a/rebar.config.erl b/rebar.config.erl
index 6b857a610..fcfd2595f 100644
--- a/rebar.config.erl
+++ b/rebar.config.erl
@@ -184,7 +184,7 @@ project_app_excluded("apps/" ++ AppStr, ExcludedApps) ->
plugins() ->
[
- {emqx_relup, {git, "https://github.com/emqx/emqx-relup.git", {branch, "0.1.0"}}},
+ {emqx_relup, {git, "https://github.com/emqx/emqx-relup.git", {tag, "0.1.0"}}},
%% emqx main project does not require port-compiler
%% pin at root level for deterministic
{pc, "v1.14.0"}
diff --git a/relup/examples/upgrade_path.list b/relup/examples/upgrade_path.list
index 945dcfcbe..82b3d91d2 100644
--- a/relup/examples/upgrade_path.list
+++ b/relup/examples/upgrade_path.list
@@ -1,3 +1,3 @@
[ "5.6.1+patch.A <- 5.6.1 <- 5.6.0"
, "5.7.0 <- 5.6.1 <- 5.6.0"
-].
\ No newline at end of file
+].
From 5c2a7dfdfaa58685502d957685b3fea80448583f Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Fri, 19 Jul 2024 12:17:12 +0800
Subject: [PATCH 09/16] fix: rename relup dir to relup_info to avoid tgz
failure
---
build | 2 +-
{relup => relup_info}/examples/5.6.0-to-5.6.1.relup | 0
{relup => relup_info}/examples/5.6.1-to-5.6.1+patch.A.relup | 0
{relup => relup_info}/examples/upgrade_path.list | 0
4 files changed, 1 insertion(+), 1 deletion(-)
rename {relup => relup_info}/examples/5.6.0-to-5.6.1.relup (100%)
rename {relup => relup_info}/examples/5.6.1-to-5.6.1+patch.A.relup (100%)
rename {relup => relup_info}/examples/upgrade_path.list (100%)
diff --git a/build b/build
index b20b1f70f..deec58c45 100755
--- a/build
+++ b/build
@@ -214,7 +214,7 @@ make_elixir_rel() {
make_relup() {
RELUP_TARGET_VSN="$(./pkg-vsn.sh "$PROFILE" --long)"
export RELUP_TARGET_VSN
- ./rebar3 emqx relup_gen --relup-dir=./relup
+ ./rebar3 emqx relup_gen --relup-dir=./relup_info
make rel -C _build/default/plugins/emqx_relup
}
diff --git a/relup/examples/5.6.0-to-5.6.1.relup b/relup_info/examples/5.6.0-to-5.6.1.relup
similarity index 100%
rename from relup/examples/5.6.0-to-5.6.1.relup
rename to relup_info/examples/5.6.0-to-5.6.1.relup
diff --git a/relup/examples/5.6.1-to-5.6.1+patch.A.relup b/relup_info/examples/5.6.1-to-5.6.1+patch.A.relup
similarity index 100%
rename from relup/examples/5.6.1-to-5.6.1+patch.A.relup
rename to relup_info/examples/5.6.1-to-5.6.1+patch.A.relup
diff --git a/relup/examples/upgrade_path.list b/relup_info/examples/upgrade_path.list
similarity index 100%
rename from relup/examples/upgrade_path.list
rename to relup_info/examples/upgrade_path.list
From 4d25f28bb2e1902efa71f1328997e30a8f597ade Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Fri, 19 Jul 2024 14:19:50 +0800
Subject: [PATCH 10/16] fix: dialyzer checks
---
.../src/emqx_mgmt_api_relup.erl | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/apps/emqx_management/src/emqx_mgmt_api_relup.erl b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
index b347cd483..e616dbd7e 100644
--- a/apps/emqx_management/src/emqx_mgmt_api_relup.erl
+++ b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
@@ -535,7 +535,7 @@ get_upgrade_status() ->
call_emqx_relup_main(Fun, Args, Default) ->
case erlang:function_exported(emqx_relup_main, Fun, length(Args)) of
true ->
- apply(emqx_relup_main, Fun, Args);
+ erlang:apply(emqx_relup_main, Fun, Args);
false ->
%% relup package is not installed
Default
@@ -567,7 +567,8 @@ run_upgrade_on_nodes(Nodes, TargetVsn) ->
end.
run_upgrade(TargetVsn) ->
- case emqx_relup_main:upgrade(TargetVsn) of
+ case call_emqx_relup_main(upgrade, [TargetVsn], no_pkg_installed) of
+ no_pkg_installed -> return_bad_request(<<"No relup package is installed">>);
ok -> {204};
{error, Reason} -> upgrade_return(Reason)
end.
@@ -592,8 +593,8 @@ get_installed_packages() ->
target_vsn_from_rel_vsn(Vsn) ->
case string:split(binary_to_list(Vsn), "-") of
- [VsnStr | _] -> VsnStr;
- _ -> throw({invalid_vsn, Vsn})
+ [_] -> throw({invalid_vsn, Vsn});
+ [VsnStr | _] -> VsnStr
end.
delete_installed_packages() ->
@@ -608,7 +609,10 @@ delete_installed_packages() ->
format_package_info(PluginInfo) when is_map(PluginInfo) ->
Vsn = maps_get(rel_vsn, PluginInfo),
- case emqx_relup_main:get_package_info(target_vsn_from_rel_vsn(Vsn)) of
+ TargetVsn = target_vsn_from_rel_vsn(Vsn),
+ case call_emqx_relup_main(get_package_info, [TargetVsn], no_pkg_installed) of
+ no_pkg_installed ->
+ throw({get_pkg_info_failed, <<"No relup package is installed">>});
{error, Reason} ->
throw({get_pkg_info_failed, Reason});
{ok, #{base_vsns := BaseVsns, change_logs := ChangeLogs}} ->
@@ -656,8 +660,4 @@ return_internal_error(Reason) ->
}}.
name_vsn(Name, Vsn) ->
- bin([Name, "-", Vsn]).
-
-bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
-bin(L) when is_list(L) -> unicode:characters_to_binary(L, utf8);
-bin(B) when is_binary(B) -> B.
+ iolist_to_binary([Name, "-", Vsn]).
From c61828460a347fc6458b7b4cbe49805f543a8952 Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Fri, 19 Jul 2024 14:38:55 +0800
Subject: [PATCH 11/16] chore: emqx_utils_api:with_node/2 support simple
http-code
---
apps/emqx_utils/src/emqx_utils_api.erl | 2 ++
bin/node_dump | 1 +
2 files changed, 3 insertions(+)
diff --git a/apps/emqx_utils/src/emqx_utils_api.erl b/apps/emqx_utils/src/emqx_utils_api.erl
index 7e5799a7a..b88cc84f8 100644
--- a/apps/emqx_utils/src/emqx_utils_api.erl
+++ b/apps/emqx_utils/src/emqx_utils_api.erl
@@ -73,5 +73,7 @@ handle_result({ok, Result}) ->
?OK(Result);
handle_result({error, Reason}) ->
?BAD_REQUEST(Reason);
+handle_result({HTTPCode}) when is_integer(HTTPCode) ->
+ {HTTPCode};
handle_result({HTTPCode, Content}) when is_integer(HTTPCode) ->
{HTTPCode, Content}.
diff --git a/bin/node_dump b/bin/node_dump
index 60c995885..90f08d107 100755
--- a/bin/node_dump
+++ b/bin/node_dump
@@ -4,6 +4,7 @@ set -eu
# shellcheck disable=SC1090,SC1091
RUNNER_ROOT_DIR="$(cd "$(dirname "$(readlink "$0" || echo "$0")")"/..; pwd -P)"
echo "Running node dump in ${RUNNER_ROOT_DIR}"
+BASE_RUNNER_ROOT_DIR="${BASE_RUNNER_ROOT_DIR:-$RUNNER_ROOT_DIR}"
# shellcheck disable=SC1090,SC1091
. "$RUNNER_ROOT_DIR"/releases/emqx_vars
From f11dfce292c389f4c5931efa9194367e0ff9d669 Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Fri, 19 Jul 2024 17:11:49 +0800
Subject: [PATCH 12/16] ci: suppress dialyzer checks for quicer and odbc types
---
apps/emqx/src/emqx_connection.erl | 5 +++++
apps/emqx/src/emqx_listeners.erl | 12 +++++++++++-
.../src/emqx_bridge_sqlserver.app.src | 2 +-
.../src/emqx_bridge_sqlserver_connector.erl | 2 +-
rebar.config | 5 ++++-
5 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/apps/emqx/src/emqx_connection.erl b/apps/emqx/src/emqx_connection.erl
index 8c0482587..562366eb3 100644
--- a/apps/emqx/src/emqx_connection.erl
+++ b/apps/emqx/src/emqx_connection.erl
@@ -173,7 +173,9 @@
system_code_change/4
]}
).
+-dialyzer({no_missing_calls, [handle_msg/2]}).
+-ifndef(BUILD_WITHOUT_QUIC).
-spec start_link
(esockd:transport(), esockd:socket(), emqx_channel:opts()) ->
{ok, pid()};
@@ -183,6 +185,9 @@
emqx_quic_connection:cb_state()
) ->
{ok, pid()}.
+-else.
+-spec start_link(esockd:transport(), esockd:socket(), emqx_channel:opts()) -> {ok, pid()}.
+-endif.
start_link(Transport, Socket, Options) ->
Args = [self(), Transport, Socket, Options],
diff --git a/apps/emqx/src/emqx_listeners.erl b/apps/emqx/src/emqx_listeners.erl
index e325263c5..ba50b3630 100644
--- a/apps/emqx/src/emqx_listeners.erl
+++ b/apps/emqx/src/emqx_listeners.erl
@@ -64,6 +64,17 @@
-export_type([listener_id/0]).
+-dialyzer(
+ {no_unknown, [
+ is_running/3,
+ current_conns/3,
+ do_stop_listener/3,
+ do_start_listener/4,
+ do_update_listener/4,
+ quic_listener_conf_rollback/3
+ ]}
+).
+
-type listener_id() :: atom() | binary().
-type listener_type() :: tcp | ssl | ws | wss | quic | dtls.
@@ -1018,7 +1029,6 @@ ensure_max_conns(<<"infinity">>) -> <<"infinity">>;
ensure_max_conns(MaxConn) when is_binary(MaxConn) -> binary_to_integer(MaxConn);
ensure_max_conns(MaxConn) -> MaxConn.
--spec quic_listen_on(X :: any()) -> quicer:listen_on().
quic_listen_on(Bind) ->
case Bind of
{Addr, Port} when tuple_size(Addr) == 4 ->
diff --git a/apps/emqx_bridge_sqlserver/src/emqx_bridge_sqlserver.app.src b/apps/emqx_bridge_sqlserver/src/emqx_bridge_sqlserver.app.src
index 3bc62734c..009a8d16b 100644
--- a/apps/emqx_bridge_sqlserver/src/emqx_bridge_sqlserver.app.src
+++ b/apps/emqx_bridge_sqlserver/src/emqx_bridge_sqlserver.app.src
@@ -1,6 +1,6 @@
{application, emqx_bridge_sqlserver, [
{description, "EMQX Enterprise SQL Server Bridge"},
- {vsn, "0.2.2"},
+ {vsn, "0.2.3"},
{registered, []},
{applications, [kernel, stdlib, emqx_resource, odbc]},
{env, [
diff --git a/apps/emqx_bridge_sqlserver/src/emqx_bridge_sqlserver_connector.erl b/apps/emqx_bridge_sqlserver/src/emqx_bridge_sqlserver_connector.erl
index 603ef18d0..521a215a5 100644
--- a/apps/emqx_bridge_sqlserver/src/emqx_bridge_sqlserver_connector.erl
+++ b/apps/emqx_bridge_sqlserver/src/emqx_bridge_sqlserver_connector.erl
@@ -78,7 +78,7 @@
%% https://www.erlang.org/doc/man/odbc.html
%% as returned by connect/2
--type connection_reference() :: odbc:connection_reference().
+-type connection_reference() :: term().
-type time_out() :: milliseconds() | infinity.
-type sql() :: string() | binary().
-type milliseconds() :: pos_integer().
diff --git a/rebar.config b/rebar.config
index d2f223f40..57d3e2722 100644
--- a/rebar.config
+++ b/rebar.config
@@ -127,7 +127,10 @@
% generated code for protobuf
emqx_exhook_pb,
% generated code for protobuf
- emqx_exproto_pb
+ emqx_exproto_pb,
+ % maybe BUILD_WITHOUT_QUIC
+ emqx_quic_connection,
+ quicer_listener
]}.
{eunit_opts, [verbose, {print_depth, 100}]}.
From fc3405fe4c6777fd45392d03c164af6a49010a56 Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Fri, 19 Jul 2024 17:35:36 +0800
Subject: [PATCH 13/16] fix: bp_api for relup
---
apps/emqx/priv/bpapi.versions | 1 +
.../src/emqx_mgmt_api_relup.erl | 29 +++++++++++--------
.../proto/emqx_mgmt_api_relup_proto_v1.erl | 4 ++-
3 files changed, 21 insertions(+), 13 deletions(-)
diff --git a/apps/emqx/priv/bpapi.versions b/apps/emqx/priv/bpapi.versions
index 7c78d43d9..5758a9fc8 100644
--- a/apps/emqx/priv/bpapi.versions
+++ b/apps/emqx/priv/bpapi.versions
@@ -47,6 +47,7 @@
{emqx_mgmt_api_plugins,1}.
{emqx_mgmt_api_plugins,2}.
{emqx_mgmt_api_plugins,3}.
+{emqx_mgmt_api_relup,1}.
{emqx_mgmt_cluster,1}.
{emqx_mgmt_cluster,2}.
{emqx_mgmt_cluster,3}.
diff --git a/apps/emqx_management/src/emqx_mgmt_api_relup.erl b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
index e616dbd7e..e7ee7c317 100644
--- a/apps/emqx_management/src/emqx_mgmt_api_relup.erl
+++ b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
@@ -20,7 +20,7 @@
-include_lib("typerefl/include/types.hrl").
-include_lib("emqx/include/logger.hrl").
--export([get_upgrade_status/0]).
+-export([get_upgrade_status/0, emqx_relup_upgrade/1]).
-export([
api_spec/0,
@@ -55,7 +55,7 @@
-define(ASSERT_PKG_READY(EXPR),
case code:is_loaded(emqx_relup_main) of
- false -> return_bad_request(<<"No relup package is installed">>);
+ false -> return_package_not_installed();
{file, _} -> EXPR
end
).
@@ -546,7 +546,7 @@ upgrade_with_targe_vsn(Fun) ->
{ok, TargetVsn} ->
Fun(TargetVsn);
{error, no_relup_package_installed} ->
- return_bad_request(<<"No relup package is installed">>);
+ return_package_not_installed();
{error, multiple_relup_packages_installed} ->
return_internal_error(<<"Multiple relup package installed">>)
end.
@@ -557,22 +557,24 @@ run_upgrade_on_nodes(Nodes, TargetVsn) ->
[] ->
{204};
Filtered ->
- upgrade_return(
- case hd(Filtered) of
- {badrpc, Reason} -> Reason;
- {error, Reason} -> Reason;
- Reason -> Reason
- end
- )
+ case hd(Filtered) of
+ no_pkg_installed -> return_package_not_installed();
+ {badrpc, Reason} -> return_internal_error(Reason);
+ {error, Reason} -> upgrade_return(Reason);
+ Reason -> return_internal_error(Reason)
+ end
end.
run_upgrade(TargetVsn) ->
- case call_emqx_relup_main(upgrade, [TargetVsn], no_pkg_installed) of
- no_pkg_installed -> return_bad_request(<<"No relup package is installed">>);
+ case emqx_relup_upgrade(TargetVsn) of
+ no_pkg_installed -> return_package_not_installed();
ok -> {204};
{error, Reason} -> upgrade_return(Reason)
end.
+emqx_relup_upgrade(TargetVsn) ->
+ call_emqx_relup_main(upgrade, [TargetVsn], no_pkg_installed).
+
get_target_vsn() ->
case get_installed_packages() of
[PackageInfo] -> {ok, target_vsn_from_rel_vsn(maps_get(rel_vsn, PackageInfo))};
@@ -647,6 +649,9 @@ return_not_found(Reason) ->
message => emqx_utils:readable_error_msg(Reason)
}}.
+return_package_not_installed() ->
+ return_bad_request(<<"No relup package is installed">>).
+
return_bad_request(Reason) ->
{400, #{
code => 'BAD_REQUEST',
diff --git a/apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl b/apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl
index 8f389f003..56c4631d8 100644
--- a/apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl
+++ b/apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl
@@ -34,10 +34,12 @@ introduced_in() ->
-spec run_upgrade([node()], string()) -> emqx_rpc:multicall_result().
run_upgrade(Nodes, TargetVsn) ->
- rpc:multicall(Nodes, emqx_relup_main, upgrade, [TargetVsn], ?RPC_TIMEOUT_OP).
+ rpc:multicall(Nodes, emqx_mgmt_api_relup, emqx_relup_upgrade, [TargetVsn], ?RPC_TIMEOUT_OP).
+-spec get_upgrade_status_from_all_nodes() -> emqx_rpc:multicall_result().
get_upgrade_status_from_all_nodes() ->
rpc:multicall(emqx_mgmt_api_relup, get_upgrade_status, [], ?RPC_TIMEOUT_INFO).
+-spec get_upgrade_status(node()) -> emqx_rpc:call_result(map()).
get_upgrade_status(Node) ->
rpc:call(Node, emqx_mgmt_api_relup, get_upgrade_status, [], ?RPC_TIMEOUT_INFO).
From 862336a2cbf1f37bdadfcbff31744c241011532d Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Mon, 22 Jul 2024 15:45:08 +0800
Subject: [PATCH 14/16] feat: hide relup plugins from APIs and CLIs
---
.../src/emqx_mgmt_api_relup.erl | 4 ++--
apps/emqx_plugins/src/emqx_plugins.erl | 24 ++++++++++++++-----
2 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/apps/emqx_management/src/emqx_mgmt_api_relup.erl b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
index e7ee7c317..05078c1ac 100644
--- a/apps/emqx_management/src/emqx_mgmt_api_relup.erl
+++ b/apps/emqx_management/src/emqx_mgmt_api_relup.erl
@@ -104,7 +104,7 @@ schema("/relup/package/upload") ->
responses => #{
204 => <<"Package is uploaded successfully">>,
400 => emqx_dashboard_swagger:error_codes(
- ['UNEXPECTED_ERROR', 'BAD_PLUGIN_INFO']
+ ['UNEXPECTED_ERROR', 'ALREADY_INSTALLED', 'BAD_PLUGIN_INFO']
)
}
}
@@ -590,7 +590,7 @@ get_installed_packages() ->
_ -> false
end
end,
- emqx_plugins:list()
+ emqx_plugins:list(hidden)
).
target_vsn_from_rel_vsn(Vsn) ->
diff --git a/apps/emqx_plugins/src/emqx_plugins.erl b/apps/emqx_plugins/src/emqx_plugins.erl
index c7ae9fd2c..37df54d3f 100644
--- a/apps/emqx_plugins/src/emqx_plugins.erl
+++ b/apps/emqx_plugins/src/emqx_plugins.erl
@@ -56,7 +56,8 @@
ensure_stopped/0,
ensure_stopped/1,
restart/1,
- list/0
+ list/0,
+ list/1
]).
%% Plugin config APIs
@@ -378,13 +379,17 @@ restart(NameVsn) ->
%% Including the ones that are installed, but not enabled in config.
-spec list() -> [plugin_info()].
list() ->
+ list(normal).
+
+-spec list(all | normal | hidden) -> [plugin_info()].
+list(Type) ->
Pattern = filename:join([install_dir(), "*", "release.json"]),
All = lists:filtermap(
fun(JsonFilePath) ->
[_, NameVsn | _] = lists:reverse(filename:split(JsonFilePath)),
case read_plugin_info(NameVsn, #{}) of
{ok, Info} ->
- {true, Info};
+ filter_plugin_of_type(Type, Info);
{error, Reason} ->
?SLOG(warning, Reason#{msg => "failed_to_read_plugin_info"}),
false
@@ -394,6 +399,17 @@ list() ->
),
do_list(configured(), All).
+filter_plugin_of_type(all, Info) ->
+ {true, Info};
+filter_plugin_of_type(normal, #{<<"hidden">> := true}) ->
+ false;
+filter_plugin_of_type(normal, Info) ->
+ {true, Info};
+filter_plugin_of_type(hidden, #{<<"hidden">> := true} = Info) ->
+ {true, Info};
+filter_plugin_of_type(hidden, _Info) ->
+ false.
+
%%--------------------------------------------------------------------
%% Package utils
@@ -662,10 +678,6 @@ do_list([#{name_vsn := NameVsn} | Rest], All) ->
end,
case lists:splitwith(SplitF, All) of
{_, []} ->
- ?SLOG(warning, #{
- msg => "configured_plugin_not_installed",
- name_vsn => NameVsn
- }),
do_list(Rest, All);
{Front, [I | Rear]} ->
[I | do_list(Rest, Front ++ Rear)]
From eb71477f437f67f7c1bebde2a5fa77fb40a01d70 Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Tue, 23 Jul 2024 09:32:54 +0800
Subject: [PATCH 15/16] chore: move relup_info to rel/relup
---
build | 2 +-
{relup_info => rel/relup}/examples/5.6.0-to-5.6.1.relup | 0
{relup_info => rel/relup}/examples/5.6.1-to-5.6.1+patch.A.relup | 0
{relup_info => rel/relup}/examples/upgrade_path.list | 0
4 files changed, 1 insertion(+), 1 deletion(-)
rename {relup_info => rel/relup}/examples/5.6.0-to-5.6.1.relup (100%)
rename {relup_info => rel/relup}/examples/5.6.1-to-5.6.1+patch.A.relup (100%)
rename {relup_info => rel/relup}/examples/upgrade_path.list (100%)
diff --git a/build b/build
index deec58c45..2792e32d2 100755
--- a/build
+++ b/build
@@ -214,7 +214,7 @@ make_elixir_rel() {
make_relup() {
RELUP_TARGET_VSN="$(./pkg-vsn.sh "$PROFILE" --long)"
export RELUP_TARGET_VSN
- ./rebar3 emqx relup_gen --relup-dir=./relup_info
+ ./rebar3 emqx relup_gen --relup-dir=./rel/relup
make rel -C _build/default/plugins/emqx_relup
}
diff --git a/relup_info/examples/5.6.0-to-5.6.1.relup b/rel/relup/examples/5.6.0-to-5.6.1.relup
similarity index 100%
rename from relup_info/examples/5.6.0-to-5.6.1.relup
rename to rel/relup/examples/5.6.0-to-5.6.1.relup
diff --git a/relup_info/examples/5.6.1-to-5.6.1+patch.A.relup b/rel/relup/examples/5.6.1-to-5.6.1+patch.A.relup
similarity index 100%
rename from relup_info/examples/5.6.1-to-5.6.1+patch.A.relup
rename to rel/relup/examples/5.6.1-to-5.6.1+patch.A.relup
diff --git a/relup_info/examples/upgrade_path.list b/rel/relup/examples/upgrade_path.list
similarity index 100%
rename from relup_info/examples/upgrade_path.list
rename to rel/relup/examples/upgrade_path.list
From 439abe430b157e996b7a10f67dc3e11c990fd154 Mon Sep 17 00:00:00 2001
From: Shawn <506895667@qq.com>
Date: Tue, 23 Jul 2024 10:50:09 +0800
Subject: [PATCH 16/16] refactor: remove relup revert callback functions
---
apps/emqx/src/emqx_post_upgrade.erl | 28 ++++++++-------
rebar.config.erl | 2 +-
.../examples/5.6.1-to-5.6.1+patch.A.relup | 36 +++++++++++--------
3 files changed, 37 insertions(+), 29 deletions(-)
diff --git a/apps/emqx/src/emqx_post_upgrade.erl b/apps/emqx/src/emqx_post_upgrade.erl
index 6b09f2a30..900278799 100644
--- a/apps/emqx/src/emqx_post_upgrade.erl
+++ b/apps/emqx/src/emqx_post_upgrade.erl
@@ -16,23 +16,25 @@
-module(emqx_post_upgrade).
+%% Example of a hot upgrade callback function.
%% PR#12765
% -export([
% pr12765_update_stats_timer/1,
-% pr12765_revert_stats_timer/1
+% pr20000_ensure_sup_started/3
% ]).
--include("logger.hrl").
-
-%%------------------------------------------------------------------------------
-%% Hot Upgrade Callback Functions.
-%%------------------------------------------------------------------------------
+%% Please ensure that every callback function is reentrant.
+%% This way, users can attempt upgrade multiple times if an issue arises.
+%%
% pr12765_update_stats_timer(_FromVsn) ->
% emqx_stats:update_interval(broker_stats, fun emqx_broker_helper:stats_fun/0).
-
-% pr12765_revert_stats_timer(_ToVsn) ->
-% emqx_stats:update_interval(broker_stats, fun emqx_broker:stats_fun/0).
-
-%%------------------------------------------------------------------------------
-%% Helper functions
-%%------------------------------------------------------------------------------
+%
+% pr20000_ensure_sup_started(_FromVsn, "5.6.1" ++ _, ChildSpec) ->
+% ChildId = maps:get(id, ChildSpec),
+% case supervisor:terminate_child(emqx_sup, ChildId) of
+% ok -> supervisor:delete_child(emqx_sup, ChildId);
+% Error -> Error
+% end,
+% supervisor:start_child(emqx_sup, ChildSpec);
+% pr20000_ensure_sup_started(_FromVsn, _TargetVsn, _) ->
+% ok.
diff --git a/rebar.config.erl b/rebar.config.erl
index fcfd2595f..522770f68 100644
--- a/rebar.config.erl
+++ b/rebar.config.erl
@@ -184,7 +184,7 @@ project_app_excluded("apps/" ++ AppStr, ExcludedApps) ->
plugins() ->
[
- {emqx_relup, {git, "https://github.com/emqx/emqx-relup.git", {tag, "0.1.0"}}},
+ {emqx_relup, {git, "https://github.com/emqx/emqx-relup.git", {tag, "0.1.1"}}},
%% emqx main project does not require port-compiler
%% pin at root level for deterministic
{pc, "v1.14.0"}
diff --git a/rel/relup/examples/5.6.1-to-5.6.1+patch.A.relup b/rel/relup/examples/5.6.1-to-5.6.1+patch.A.relup
index 1c833c579..8e2170dda 100644
--- a/rel/relup/examples/5.6.1-to-5.6.1+patch.A.relup
+++ b/rel/relup/examples/5.6.1-to-5.6.1+patch.A.relup
@@ -8,31 +8,37 @@
%% Note that we do not support the 'apply' command in the 'code_changes' section.
%% If any complex operations are needed during the upgrade process, please add
%% them in the 'post_upgrade_callbacks' section, and implement them in the
-%% 'emqx_post_upgrade' module.
+%% 'emqx_post_upgrade' module. We consolidate the hot upgrade callback code into
+%% a single module ('emqx_post_upgrade') is to avoid mixing the hot upgrade-related
+%% code with the main logic code.
+
+ChildSpec = fun(Mod) ->
+ #{
+ id => Mod,
+ start => {Mod, start_link, []},
+ restart => permanent,
+ shutdown => 5000,
+ type => worker,
+ modules => [Mod]
+ }
+ end.
#{
target_version => "5.6.1+patch.A",
from_version => "5.6.1",
code_changes =>
+ %% the 'emqx_release' and 'emqx_post_upgrade' will be automatically added,
+ %% no need to include them here
[ {load_module, emqx_broker}
, {load_module, emqx_metrics}
- , {load_module, emqx_persistent_message}
- , {load_module, emqx_dashboard_monitor}
- , {load_module, emqx_dashboard_monitor_api}
- , {load_module, emqx_ds_builtin_metrics}
- , {load_module, emqx_ds_storage_bitfield_lts}
- , {load_module, emqx_prometheus}
- , {load_module, emqx_ds_builtin_db_sup}
- , {load_module, emqx_ds_builtin_sup}
- , {load_module, emqx_ds_replication_layer}
- , {load_module, emqx_ds_storage_layer}
- , {load_module, emqx_broker_helper}
- , {load_module, emqx_shared_sub}
, {load_module, emqx_ds_replication_shard_allocator}
- , {load_module, emqx_mgmt_api_metrics}
, {update, emqx_ds_replication_layer_egress, {advanced, #{}}}
],
post_upgrade_callbacks =>
- [ {pr12765_update_stats_timer, pr12765_revert_stats_timer}
+ [
+ %% emqx_post_upgrade:pr12765_update_stats_timer/1
+ pr12765_update_stats_timer,
+ %% emqx_post_upgrade:pr20000_ensure_sup_started/3
+ {pr20000_ensure_sup_started, ["5.6.1+patch.A", ChildSpec(some_mod)]}
]
}.