summaryrefslogtreecommitdiff
path: root/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_boot_state_systemd.erl
diff options
context:
space:
mode:
Diffstat (limited to 'deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_boot_state_systemd.erl')
-rw-r--r--deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_boot_state_systemd.erl174
1 files changed, 174 insertions, 0 deletions
diff --git a/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_boot_state_systemd.erl b/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_boot_state_systemd.erl
new file mode 100644
index 0000000000..f838535b6a
--- /dev/null
+++ b/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_boot_state_systemd.erl
@@ -0,0 +1,174 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2015-2020 VMware, Inc. or its affiliates. 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.