summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/rabbitmq_prelaunch/src/rabbit_boot_state.erl84
-rw-r--r--apps/rabbitmq_prelaunch/src/rabbit_boot_state_sup.erl46
-rw-r--r--apps/rabbitmq_prelaunch/src/rabbit_boot_state_systemd.erl183
-rw-r--r--apps/rabbitmq_prelaunch/src/rabbit_prelaunch.erl69
-rw-r--r--apps/rabbitmq_prelaunch/src/rabbit_prelaunch_sup.erl9
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}}.