diff --git a/Makefile b/Makefile index 6aaaf58d7..2f4519cfd 100644 --- a/Makefile +++ b/Makefile @@ -244,7 +244,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) @@ -253,7 +253,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/priv/bpapi.versions b/apps/emqx/priv/bpapi.versions index 6e3cc046f..f0a2adca7 100644 --- a/apps/emqx/priv/bpapi.versions +++ b/apps/emqx/priv/bpapi.versions @@ -48,6 +48,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/src/emqx_connection.erl b/apps/emqx/src/emqx_connection.erl index d62d778bc..a3c545ea6 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/src/emqx_post_upgrade.erl b/apps/emqx/src/emqx_post_upgrade.erl new file mode 100644 index 000000000..900278799 --- /dev/null +++ b/apps/emqx/src/emqx_post_upgrade.erl @@ -0,0 +1,40 @@ +%%-------------------------------------------------------------------- +%% 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). + +%% Example of a hot upgrade callback function. +%% PR#12765 +% -export([ +% pr12765_update_stats_timer/1, +% pr20000_ensure_sup_started/3 +% ]). + +%% 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). +% +% 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/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/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/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..05078c1ac --- /dev/null +++ b/apps/emqx_management/src/emqx_mgmt_api_relup.erl @@ -0,0 +1,668 @@ +%%-------------------------------------------------------------------- +%% 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, emqx_relup_upgrade/1]). + +-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 +]). + +-ignore_xref(emqx_relup_main). + +-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_package_not_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', 'ALREADY_INSTALLED', '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">> := 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; + {error, Reason} -> + return_bad_request(Reason) + 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, _) -> + {[_ | _] = 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. + +'/relup/status/:node'(get, #{bindings := #{node := NodeNameStr}}) -> + 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 + +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 => 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 -> + erlang: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} -> + Fun(TargetVsn); + {error, no_relup_package_installed} -> + return_package_not_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 -> + 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 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))}; + [] -> {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(hidden) + ). + +target_vsn_from_rel_vsn(Vsn) -> + case string:split(binary_to_list(Vsn), "-") of + [_] -> throw({invalid_vsn, Vsn}); + [VsnStr | _] -> VsnStr + 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), + 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}} -> + #{ + 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_package_not_installed() -> + return_bad_request(<<"No relup package is installed">>). + +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) -> + iolist_to_binary([Name, "-", Vsn]). 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..56c4631d8 --- /dev/null +++ b/apps/emqx_management/src/proto/emqx_mgmt_api_relup_proto_v1.erl @@ -0,0 +1,45 @@ +%%-------------------------------------------------------------------- +%% 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_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). diff --git a/apps/emqx_plugins/src/emqx_plugins.erl b/apps/emqx_plugins/src/emqx_plugins.erl index 253d1b4ef..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 @@ -73,6 +74,7 @@ decode_plugin_config_map/2, install_dir/0, avsc_file_path/1, + md5sum_file/1, with_plugin_avsc/1 ]). @@ -377,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 @@ -393,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 @@ -661,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)] @@ -736,7 +749,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 +851,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 +1509,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/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/emqx b/bin/emqx index a2f9e2691..c32c00474 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/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 diff --git a/build b/build index fd01de2f4..2792e32d2 100755 --- a/build +++ b/build @@ -211,39 +211,11 @@ 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() { - 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}" + RELUP_TARGET_VSN="$(./pkg-vsn.sh "$PROFILE" --long)" + export RELUP_TARGET_VSN + ./rebar3 emqx relup_gen --relup-dir=./rel/relup + make rel -C _build/default/plugins/emqx_relup } cp_dyn_libs() { diff --git a/rebar.config b/rebar.config index c7d9b019b..b260561cb 100644 --- a/rebar.config +++ b/rebar.config @@ -128,7 +128,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}]}. diff --git a/rebar.config.erl b/rebar.config.erl index c2f63a2ad..d03168060 100644 --- a/rebar.config.erl +++ b/rebar.config.erl @@ -185,7 +185,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", {tag, "0.1.1"}}}, %% emqx main project does not require port-compiler %% pin at root level for deterministic {pc, "v1.14.0"} @@ -393,9 +393,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/rel/relup/examples/5.6.0-to-5.6.1.relup b/rel/relup/examples/5.6.0-to-5.6.1.relup new file mode 100644 index 000000000..517e41fc8 --- /dev/null +++ b/rel/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/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 new file mode 100644 index 000000000..8e2170dda --- /dev/null +++ b/rel/relup/examples/5.6.1-to-5.6.1+patch.A.relup @@ -0,0 +1,44 @@ +%% 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. 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_ds_replication_shard_allocator} + , {update, emqx_ds_replication_layer_egress, {advanced, #{}}} + ], + post_upgrade_callbacks => + [ + %% 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)]} + ] +}. diff --git a/rel/relup/examples/upgrade_path.list b/rel/relup/examples/upgrade_path.list new file mode 100644 index 000000000..82b3d91d2 --- /dev/null +++ b/rel/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" +].