diff options
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.erl | 174 |
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. |