diff options
Diffstat (limited to 'apps')
5 files changed, 323 insertions, 68 deletions
diff --git a/apps/rabbitmq_prelaunch/src/rabbit_boot_state.erl b/apps/rabbitmq_prelaunch/src/rabbit_boot_state.erl new file mode 100644 index 0000000000..9adcc92e0e --- /dev/null +++ b/apps/rabbitmq_prelaunch/src/rabbit_boot_state.erl @@ -0,0 +1,84 @@ +%%%------------------------------------------------------------------- +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at https://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% Copyright (c) 2019-2020 Pivotal Software, Inc. All rights reserved. +%% + +-module(rabbit_boot_state). + +-include_lib("eunit/include/eunit.hrl"). + +-export([get/0, + set/1, + wait_for/2]). + +-define(PT_KEY_BOOT_STATE, {?MODULE, boot_state}). + +-type boot_state() :: 'stopped' | 'booting' | 'ready' | 'stopping'. + +-export_type([boot_state/0]). + +-spec get() -> boot_state(). +get() -> + persistent_term:get(?PT_KEY_BOOT_STATE, stopped). + +-spec set(boot_state()) -> ok. +set(BootState) -> + rabbit_log_prelaunch:debug("Change boot state to `~s`", [BootState]), + ?assert(is_valid(BootState)), + case BootState of + stopped -> persistent_term:erase(?PT_KEY_BOOT_STATE); + _ -> persistent_term:put(?PT_KEY_BOOT_STATE, BootState) + end, + rabbit_boot_state_sup:notify_boot_state_listeners(BootState). + +-spec wait_for(boot_state(), timeout()) -> ok | {error, timeout}. +wait_for(BootState, infinity) -> + case is_reached(BootState) of + true -> ok; + false -> Wait = 200, + timer:sleep(Wait), + wait_for(BootState, infinity) + end; +wait_for(BootState, Timeout) + when is_integer(Timeout) andalso Timeout >= 0 -> + case is_reached(BootState) of + true -> ok; + false -> Wait = 200, + timer:sleep(Wait), + wait_for(BootState, Timeout - Wait) + end; +wait_for(_, _) -> + {error, timeout}. + +boot_state_idx(stopped) -> 0; +boot_state_idx(booting) -> 1; +boot_state_idx(ready) -> 2; +boot_state_idx(stopping) -> 3. + +is_valid(BootState) -> + is_integer(boot_state_idx(BootState)). + +is_reached(TargetBootState) -> + is_reached(?MODULE:get(), TargetBootState). + +is_reached(CurrentBootState, CurrentBootState) -> + true; +is_reached(stopping, stopped) -> + false; +is_reached(_CurrentBootState, stopped) -> + true; +is_reached(stopped, _TargetBootState) -> + true; +is_reached(CurrentBootState, TargetBootState) -> + boot_state_idx(TargetBootState) =< boot_state_idx(CurrentBootState). diff --git a/apps/rabbitmq_prelaunch/src/rabbit_boot_state_sup.erl b/apps/rabbitmq_prelaunch/src/rabbit_boot_state_sup.erl new file mode 100644 index 0000000000..44f421e543 --- /dev/null +++ b/apps/rabbitmq_prelaunch/src/rabbit_boot_state_sup.erl @@ -0,0 +1,46 @@ +%%%------------------------------------------------------------------- +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at https://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% Copyright (c) 2020 Pivotal Software, Inc. All rights reserved. +%% + +-module(rabbit_boot_state_sup). +-behaviour(supervisor). + +-export([start_link/0, + init/1]). + +-export([notify_boot_state_listeners/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + SystemdSpec = #{id => rabbit_boot_state_systemd, + start => {rabbit_boot_state_systemd, start_link, []}, + restart => transient}, + {ok, {#{strategy => one_for_one, + intensity => 1, + period => 5}, + [SystemdSpec]}}. + +-spec notify_boot_state_listeners(rabbit_boot_state:boot_state()) -> ok. +notify_boot_state_listeners(BootState) -> + lists:foreach( + fun + ({_, Child, _, _}) when is_pid(Child) -> + gen_server:cast(Child, {notify_boot_state, BootState}); + (_) -> + ok + end, + supervisor:which_children(?MODULE)). diff --git a/apps/rabbitmq_prelaunch/src/rabbit_boot_state_systemd.erl b/apps/rabbitmq_prelaunch/src/rabbit_boot_state_systemd.erl new file mode 100644 index 0000000000..7a942745e7 --- /dev/null +++ b/apps/rabbitmq_prelaunch/src/rabbit_boot_state_systemd.erl @@ -0,0 +1,183 @@ +%%%------------------------------------------------------------------- +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License +%% at https://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and +%% limitations under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% Copyright (c) 2015-2020 Pivotal Software, Inc. All rights reserved. +%% + +-module(rabbit_boot_state_systemd). + +-behaviour(gen_server). + +-export([start_link/0]). + +-export([init/1, + handle_call/3, + handle_cast/2, + terminate/2, + code_change/3]). + +-record(state, {mechanism, + sd_notify_module, + socket}). + +-define(LOG_PREFIX, "Boot state/systemd: "). + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +init([]) -> + case os:type() of + {unix, _} -> + case code:load_file(sd_notify) of + {module, sd_notify} -> + {ok, #state{mechanism = legacy, + sd_notify_module = sd_notify}}; + {error, _} -> + case os:getenv("NOTIFY_SOCKET") of + false -> + ignore; + "" -> + ignore; + Socket -> + {ok, #state{mechanism = socat, + socket = Socket}} + end + end; + _ -> + ignore + end. + +handle_call(_Request, _From, State) -> + {noreply, State}. + +handle_cast({notify_boot_state, BootState}, State) -> + notify_boot_state(BootState, State), + {noreply, State}. + +terminate(normal, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%% Private + +notify_boot_state(ready = BootState, + #state{mechanism = legacy, sd_notify_module = SDNotify}) -> + rabbit_log_prelaunch:debug( + ?LOG_PREFIX "notifying of state `~s` (via native module)", + [BootState]), + sd_notify_legacy(SDNotify); +notify_boot_state(ready = BootState, + #state{mechanism = socat, socket = Socket}) -> + rabbit_log_prelaunch:debug( + ?LOG_PREFIX "notifying of state `~s` (via socat(1))", + [BootState]), + sd_notify_socat(Socket); +notify_boot_state(BootState, _) -> + rabbit_log_prelaunch:debug( + ?LOG_PREFIX "ignoring state `~s`", + [BootState]), + ok. + +sd_notify_message() -> + "READY=1\nSTATUS=Initialized\nMAINPID=" ++ os:getpid() ++ "\n". + +sd_notify_legacy(SDNotify) -> + SDNotify:sd_notify(0, sd_notify_message()). + +%% socat(1) is the most portable way the sd_notify could be +%% implemented in erlang, without introducing some NIF. Currently the +%% following issues prevent us from implementing it in a more +%% reasonable way: +%% - systemd-notify(1) is unstable for non-root users +%% - erlang doesn't support unix domain sockets. +%% +%% Some details on how we ended with such a solution: +%% https://github.com/rabbitmq/rabbitmq-server/issues/664 +sd_notify_socat(Socket) -> + case sd_current_unit() of + {ok, Unit} -> + rabbit_log_prelaunch:debug( + ?LOG_PREFIX "systemd unit for activation check: \"~s\"~n", + [Unit]), + sd_notify_socat(Socket, Unit); + _ -> + ok + end. + +sd_notify_socat(Socket, Unit) -> + try sd_open_port(Socket) of + Port -> + Port ! {self(), {command, sd_notify_message()}}, + Result = sd_wait_activation(Port, Unit), + port_close(Port), + Result + catch + Class:Reason -> + rabbit_log_prelaunch:debug( + ?LOG_PREFIX "Failed to start socat(1): ~p:~p~n", + [Class, Reason]), + false + end. + +sd_current_unit() -> + CmdOut = os:cmd("ps -o unit= -p " ++ os:getpid()), + Ret = (catch re:run(CmdOut, + "([-.@0-9a-zA-Z]+)", + [unicode, {capture, all_but_first, list}])), + case Ret of + {'EXIT', _} -> error; + {match, [Unit]} -> {ok, Unit}; + _ -> error + end. + +socat_socket_arg("@" ++ AbstractUnixSocket) -> + "abstract-sendto:" ++ AbstractUnixSocket; +socat_socket_arg(UnixSocket) -> + "unix-sendto:" ++ UnixSocket. + +sd_open_port(Socket) -> + open_port( + {spawn_executable, os:find_executable("socat")}, + [{args, [socat_socket_arg(Socket), "STDIO"]}, + use_stdio, out]). + +sd_wait_activation(Port, Unit) -> + case os:find_executable("systemctl") of + false -> + rabbit_log_prelaunch:debug( + ?LOG_PREFIX "systemctl(1) unavailable, falling back to sleep~n"), + timer:sleep(5000), + ok; + _ -> + sd_wait_activation(Port, Unit, 10) + end. + +sd_wait_activation(_, _, 0) -> + rabbit_log_prelaunch:debug( + ?LOG_PREFIX "service still in 'activating' state, bailing out~n"), + ok; +sd_wait_activation(Port, Unit, AttemptsLeft) -> + Ret = os:cmd("systemctl show --property=ActiveState -- '" ++ Unit ++ "'"), + case Ret of + "ActiveState=activating\n" -> + timer:sleep(1000), + sd_wait_activation(Port, Unit, AttemptsLeft - 1); + "ActiveState=" ++ _ -> + ok; + _ = Err -> + rabbit_log_prelaunch:debug( + ?LOG_PREFIX "unexpected status from systemd: ~p~n", [Err]), + ok + end. diff --git a/apps/rabbitmq_prelaunch/src/rabbit_prelaunch.erl b/apps/rabbitmq_prelaunch/src/rabbit_prelaunch.erl index 2acd1caf43..4091e28b0c 100644 --- a/apps/rabbitmq_prelaunch/src/rabbit_prelaunch.erl +++ b/apps/rabbitmq_prelaunch/src/rabbit_prelaunch.erl @@ -5,11 +5,6 @@ -export([run_prelaunch_first_phase/0, assert_mnesia_is_stopped/0, get_context/0, - get_boot_state/0, - set_boot_state/1, - is_boot_state_reached/1, - wait_for_boot_state/1, - wait_for_boot_state/2, get_stop_reason/0, set_stop_reason/1, clear_stop_reason/0, @@ -23,7 +18,6 @@ -endif. -define(PT_KEY_CONTEXT, {?MODULE, context}). --define(PT_KEY_BOOT_STATE, {?MODULE, boot_state}). -define(PT_KEY_INITIAL_PASS, {?MODULE, initial_pass_finished}). -define(PT_KEY_SHUTDOWN_FUNC, {?MODULE, chained_shutdown_func}). -define(PT_KEY_STOP_REASON, {?MODULE, stop_reason}). @@ -35,21 +29,21 @@ run_prelaunch_first_phase() -> throw:{error, _} = Error -> rabbit_prelaunch_errors:log_error(Error), set_stop_reason(Error), - set_boot_state(stopped), + rabbit_boot_state:set(stopped), Error; Class:Exception:Stacktrace -> rabbit_prelaunch_errors:log_exception( Class, Exception, Stacktrace), Error = {error, Exception}, set_stop_reason(Error), - set_boot_state(stopped), + rabbit_boot_state:set(stopped), Error end. do_run() -> %% Indicate RabbitMQ is booting. clear_stop_reason(), - set_boot_state(booting), + rabbit_boot_state:set(booting), %% Configure dbg if requested. rabbit_prelaunch_early_logging:enable_quick_dbg(rabbit_env:dbg_config()), @@ -135,63 +129,6 @@ clear_context_cache() -> persistent_term:erase(?PT_KEY_CONTEXT). -endif. -get_boot_state() -> - persistent_term:get(?PT_KEY_BOOT_STATE, stopped). - -set_boot_state(stopped) -> - rabbit_log_prelaunch:debug("Change boot state to `stopped`"), - persistent_term:erase(?PT_KEY_BOOT_STATE); -set_boot_state(BootState) -> - rabbit_log_prelaunch:debug("Change boot state to `~s`", [BootState]), - ?assert(is_boot_state_valid(BootState)), - persistent_term:put(?PT_KEY_BOOT_STATE, BootState). - -wait_for_boot_state(BootState) -> - wait_for_boot_state(BootState, infinity). - -wait_for_boot_state(BootState, Timeout) -> - ?assert(is_boot_state_valid(BootState)), - wait_for_boot_state1(BootState, Timeout). - -wait_for_boot_state1(BootState, infinity = Timeout) -> - case is_boot_state_reached(BootState) of - true -> ok; - false -> wait_for_boot_state1(BootState, Timeout) - end; -wait_for_boot_state1(BootState, Timeout) - when is_integer(Timeout) andalso Timeout >= 0 -> - case is_boot_state_reached(BootState) of - true -> ok; - false -> Wait = 200, - timer:sleep(Wait), - wait_for_boot_state1(BootState, Timeout - Wait) - end; -wait_for_boot_state1(_, _) -> - {error, timeout}. - -boot_state_idx(stopped) -> 0; -boot_state_idx(booting) -> 1; -boot_state_idx(ready) -> 2; -boot_state_idx(stopping) -> 3; -boot_state_idx(_) -> undefined. - -is_boot_state_valid(BootState) -> - is_integer(boot_state_idx(BootState)). - -is_boot_state_reached(TargetBootState) -> - is_boot_state_reached(get_boot_state(), TargetBootState). - -is_boot_state_reached(CurrentBootState, CurrentBootState) -> - true; -is_boot_state_reached(stopping, stopped) -> - false; -is_boot_state_reached(_CurrentBootState, stopped) -> - true; -is_boot_state_reached(stopped, _TargetBootState) -> - true; -is_boot_state_reached(CurrentBootState, TargetBootState) -> - boot_state_idx(TargetBootState) =< boot_state_idx(CurrentBootState). - get_stop_reason() -> persistent_term:get(?PT_KEY_STOP_REASON, undefined). diff --git a/apps/rabbitmq_prelaunch/src/rabbit_prelaunch_sup.erl b/apps/rabbitmq_prelaunch/src/rabbit_prelaunch_sup.erl index ec51989fb9..9fd117d9f3 100644 --- a/apps/rabbitmq_prelaunch/src/rabbit_prelaunch_sup.erl +++ b/apps/rabbitmq_prelaunch/src/rabbit_prelaunch_sup.erl @@ -8,10 +8,15 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> + BootStateSup = #{id => bootstate, + start => {rabbit_boot_state_sup, start_link, []}, + type => supervisor}, %% `rabbit_prelaunch` does not start a process, it only configures %% the node. Prelaunch = #{id => prelaunch, start => {rabbit_prelaunch, run_prelaunch_first_phase, []}, restart => transient}, - Procs = [Prelaunch], - {ok, {{one_for_one, 1, 5}, Procs}}. + Procs = [BootStateSup, Prelaunch], + {ok, {#{strategy => one_for_one, + intensity => 1, + period => 5}, Procs}}. |
