summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/rabbit.erl683
-rw-r--r--src/rabbit_config.erl291
-rw-r--r--src/rabbit_hipe.erl9
-rw-r--r--src/rabbit_plugins.erl48
-rw-r--r--src/rabbit_prelaunch.erl161
-rw-r--r--src/rabbit_prelaunch_cluster.erl22
-rw-r--r--src/rabbit_prelaunch_conf.erl534
-rw-r--r--src/rabbit_prelaunch_feature_flags.erl25
-rw-r--r--src/rabbit_prelaunch_hipe.erl10
-rw-r--r--src/rabbit_prelaunch_logging.erl68
-rw-r--r--src/rabbit_table.erl6
11 files changed, 1023 insertions, 834 deletions
diff --git a/src/rabbit.erl b/src/rabbit.erl
index e4d2ee9808..0b002618e5 100644
--- a/src/rabbit.erl
+++ b/src/rabbit.erl
@@ -30,15 +30,9 @@
-export([start/2, stop/1, prep_stop/1]).
-export([start_apps/1, start_apps/2, stop_apps/1]).
--export([log_locations/0, config_files/0, decrypt_config/2]). %% for testing and mgmt-agent
+-export([log_locations/0, config_files/0]). %% for testing and mgmt-agent
-export([is_booted/1, is_booted/0, is_booting/1, is_booting/0]).
--ifdef(TEST).
-
--export([start_logger/0]).
-
--endif.
-
%%---------------------------------------------------------------------------
%% Boot steps.
-export([maybe_insert_default_data/0, boot_delegate/0, recover/0]).
@@ -262,7 +256,7 @@
-include("rabbit_framing.hrl").
-include("rabbit.hrl").
--define(APPS, [os_mon, mnesia, rabbit_common, ra, sysmon_handler, rabbit]).
+-define(APPS, [os_mon, mnesia, rabbit_common, rabbitmq_prelaunch, ra, sysmon_handler, rabbit]).
-define(ASYNC_THREADS_WARNING_THRESHOLD, 8).
@@ -282,75 +276,93 @@
%%----------------------------------------------------------------------------
-ensure_application_loaded() ->
- %% We end up looking at the rabbit app's env for HiPE and log
- %% handling, so it needs to be loaded. But during the tests, it
- %% may end up getting loaded twice, so guard against that.
- case application:load(rabbit) of
- ok -> ok;
- {error, {already_loaded, rabbit}} -> ok
- end.
-
-spec start() -> 'ok'.
start() ->
- start_it(fun() ->
- %% We do not want to upgrade mnesia after just
- %% restarting the app.
- ok = ensure_application_loaded(),
- HipeResult = rabbit_hipe:maybe_hipe_compile(),
- ok = start_logger(),
- rabbit_hipe:log_hipe_result(HipeResult),
- Apps = load_all_apps(),
- rabbit_feature_flags:initialize_registry(),
- rabbit_node_monitor:prepare_cluster_status_files(),
- rabbit_mnesia:check_cluster_consistency(),
- broker_start(Apps)
- end).
+ %% start() vs. boot(): we want to throw an error in start().
+ start_it(temporary).
-spec boot() -> 'ok'.
boot() ->
- start_it(fun() ->
- ensure_config(),
- ok = ensure_application_loaded(),
- HipeResult = rabbit_hipe:maybe_hipe_compile(),
- ok = start_logger(),
- rabbit_hipe:log_hipe_result(HipeResult),
- Apps = load_all_apps(),
- rabbit_feature_flags:initialize_registry(),
- rabbit_node_monitor:prepare_cluster_status_files(),
- ok = rabbit_upgrade:maybe_upgrade_mnesia(),
- %% It's important that the consistency check happens after
- %% the upgrade, since if we are a secondary node the
- %% primary node will have forgotten us
- rabbit_mnesia:check_cluster_consistency(),
- broker_start(Apps)
- end).
-
-ensure_config() ->
- case rabbit_config:validate_config_files() of
- ok -> ok;
- {error, {ErrFmt, ErrArgs}} ->
- throw({error, {check_config_file, ErrFmt, ErrArgs}})
+ %% start() vs. boot(): we want the node to exit in boot(). Because
+ %% applications are started with `transient`, any error during their
+ %% startup will abort the node.
+ start_it(transient).
+
+run_prelaunch_second_phase() ->
+ %% Finish the prelaunch phase started by the `rabbitmq_prelaunch`
+ %% application.
+ %%
+ %% The first phase was handled by the `rabbitmq_prelaunch`
+ %% application. It was started in one of the following way:
+ %% - from an Erlang release boot script;
+ %% - from the rabbit:boot/0 or rabbit:start/0 functions.
+ %%
+ %% The `rabbitmq_prelaunch` application creates the context map from
+ %% the environment and the configuration files early during Erlang
+ %% VM startup. Once it is done, all application environments are
+ %% configured (in particular `mnesia` and `ra`).
+ %%
+ %% This second phase depends on other modules & facilities of
+ %% RabbitMQ core. That's why we need to run it now, from the
+ %% `rabbit` application start function.
+
+ %% We assert Mnesia is stopped before we run the prelaunch
+ %% phases. See `rabbit_prelaunch` for an explanation.
+ %%
+ %% This is the second assertion, just in case Mnesia is started
+ %% between the two prelaunch phases.
+ rabbit_prelaunch:assert_mnesia_is_stopped(),
+
+ %% Get the context created by `rabbitmq_prelaunch` then proceed
+ %% with all steps in this phase.
+ #{initial_pass := IsInitialPass} =
+ Context = rabbit_prelaunch:get_context(),
+
+ case IsInitialPass of
+ true ->
+ rabbit_log_prelaunch:debug(""),
+ rabbit_log_prelaunch:debug(
+ "== Prelaunch phase [2/2] (initial pass) ==");
+ false ->
+ rabbit_log_prelaunch:debug(""),
+ rabbit_log_prelaunch:debug("== Prelaunch phase [2/2] =="),
+ ok
+ end,
+
+ %% 1. Feature flags registry.
+ ok = rabbit_prelaunch_feature_flags:setup(Context),
+
+ %% 2. Configuration check + loading.
+ ok = rabbit_prelaunch_conf:setup(Context),
+
+ %% 3. Logging.
+ ok = rabbit_prelaunch_logging:setup(Context),
+
+ case IsInitialPass of
+ true ->
+ %% 4. HiPE compilation.
+ ok = rabbit_prelaunch_hipe:setup(Context);
+ false ->
+ ok
end,
- case rabbit_config:prepare_and_use_config() of
- {error, {generation_error, Error}} ->
- throw({error, {generate_config_file, Error}});
- ok -> ok
- end.
-load_all_apps() ->
- Plugins = rabbit_plugins:setup(),
- ToBeLoaded = Plugins ++ ?APPS,
- app_utils:load_applications(ToBeLoaded),
- ToBeLoaded.
+ %% 5. Clustering.
+ ok = rabbit_prelaunch_cluster:setup(Context),
-broker_start(Apps) ->
- start_loaded_apps(Apps),
- maybe_sd_notify(),
- ok = rabbit_lager:broker_is_started(),
- ok = log_broker_started(rabbit_plugins:strictly_plugins(rabbit_plugins:active())).
+ %% Start Mnesia now that everything is ready.
+ rabbit_log_prelaunch:debug("Starting Mnesia"),
+ ok = mnesia:start(),
+
+ rabbit_log_prelaunch:debug(""),
+ rabbit_log_prelaunch:debug("== Prelaunch DONE =="),
+
+ case IsInitialPass of
+ true -> rabbit_prelaunch:initial_pass_finished();
+ false -> ok
+ end,
+ ok.
%% Try to send systemd ready notification if it makes sense in the
%% current environment. standard_error is used intentionally in all
@@ -465,41 +477,89 @@ sd_wait_activation(Port, Unit, AttemptsLeft) ->
false
end.
-start_it(StartFun) ->
+start_it(StartType) ->
+ case spawn_boot_marker() of
+ {ok, Marker} ->
+ T0 = erlang:timestamp(),
+ rabbit_log:info("RabbitMQ is asked to start...", []),
+ try
+ {ok, _} = application:ensure_all_started(rabbitmq_prelaunch,
+ StartType),
+ {ok, _} = application:ensure_all_started(rabbit,
+ StartType),
+ ok = wait_for_ready_or_stopped(),
+
+ T1 = erlang:timestamp(),
+ rabbit_log_prelaunch:debug(
+ "Time to start RabbitMQ: ~p µs",
+ [timer:now_diff(T1, T0)]),
+ stop_boot_marker(Marker),
+ ok
+ catch
+ error:{badmatch, Error}:_ ->
+ stop_boot_marker(Marker),
+ case StartType of
+ temporary -> throw(Error);
+ _ -> exit(Error)
+ end
+ end;
+ {already_booting, Marker} ->
+ stop_boot_marker(Marker),
+ ok
+ end.
+
+wait_for_ready_or_stopped() ->
+ ok = rabbit_prelaunch:wait_for_boot_state(ready),
+ case rabbit_prelaunch:get_boot_state() of
+ ready ->
+ ok;
+ _ ->
+ ok = rabbit_prelaunch:wait_for_boot_state(stopped),
+ rabbit_prelaunch:get_stop_reason()
+ end.
+
+spawn_boot_marker() ->
+ %% Compatibility with older RabbitMQ versions:
+ %% We register a process doing nothing to indicate that RabbitMQ is
+ %% booting. This is checked by `is_booting(Node)` on a remote node.
Marker = spawn_link(fun() -> receive stop -> ok end end),
case catch register(rabbit_boot, Marker) of
- true -> try
- case is_running() of
- true -> ok;
- false -> StartFun()
- end
- catch Class:Reason ->
- boot_error(Class, Reason)
- after
- unlink(Marker),
- Marker ! stop,
- %% give the error loggers some time to catch up
- timer:sleep(100)
- end;
- _ -> unlink(Marker),
- Marker ! stop
+ true -> {ok, Marker};
+ _ -> {already_booting, Marker}
end.
+stop_boot_marker(Marker) ->
+ unlink(Marker),
+ Marker ! stop,
+ ok.
+
-spec stop() -> 'ok'.
stop() ->
- case whereis(rabbit_boot) of
- undefined -> ok;
- _ ->
- rabbit_log:info("RabbitMQ hasn't finished starting yet. Waiting for startup to finish before stopping..."),
- ok = wait_for_boot_to_finish(node())
- end,
- rabbit_log:info("RabbitMQ is asked to stop...~n", []),
- Apps = ?APPS ++ rabbit_plugins:active(),
+ case wait_for_ready_or_stopped() of
+ ok ->
+ case rabbit_prelaunch:get_boot_state() of
+ ready ->
+ rabbit_log:info("RabbitMQ is asked to stop..."),
+ do_stop(),
+ rabbit_log:info(
+ "Successfully stopped RabbitMQ and its dependencies"),
+ ok;
+ stopped ->
+ ok
+ end;
+ _ ->
+ ok
+ end.
+
+do_stop() ->
+ Apps0 = ?APPS ++ rabbit_plugins:active(),
+ %% We ensure that Mnesia is stopped last (or more exactly, after rabbit).
+ Apps1 = app_utils:app_dependency_order(Apps0, true) -- [mnesia],
+ Apps = [mnesia | Apps1],
%% this will also perform unregistration with the peer discovery backend
%% as needed
- stop_apps(app_utils:app_dependency_order(Apps, true)),
- rabbit_log:info("Successfully stopped RabbitMQ and its dependencies~n", []).
+ stop_apps(Apps).
-spec stop_and_halt() -> no_return().
@@ -541,57 +601,8 @@ start_apps(Apps, RestartTypes) ->
ok = rabbit_feature_flags:refresh_feature_flags_after_app_load(Apps),
start_loaded_apps(Apps, RestartTypes).
-start_loaded_apps(Apps) ->
- start_loaded_apps(Apps, #{}).
-
start_loaded_apps(Apps, RestartTypes) ->
- ensure_sysmon_handler_app_config(),
- %% make Ra use a custom logger that dispatches to lager instead of the
- %% default OTP logger
- application:set_env(ra, logger_module, rabbit_log_ra_shim),
- %% use a larger segments size for queues
- case application:get_env(ra, segment_max_entries) of
- undefined ->
- application:set_env(ra, segment_max_entries, 32768);
- _ ->
- ok
- end,
- case application:get_env(ra, wal_max_size_bytes) of
- undefined ->
- application:set_env(ra, wal_max_size_bytes, 536870912); %% 5 * 2 ^ 20
- _ ->
- ok
- end,
- ConfigEntryDecoder = case application:get_env(rabbit, config_entry_decoder) of
- undefined ->
- [];
- {ok, Val} ->
- Val
- end,
- PassPhrase = case proplists:get_value(passphrase, ConfigEntryDecoder) of
- prompt ->
- IoDevice = get_input_iodevice(),
- io:setopts(IoDevice, [{echo, false}]),
- PP = lists:droplast(io:get_line(IoDevice,
- "\nPlease enter the passphrase to unlock encrypted "
- "configuration entries.\n\nPassphrase: ")),
- io:setopts(IoDevice, [{echo, true}]),
- io:format(IoDevice, "~n", []),
- PP;
- {file, Filename} ->
- {ok, File} = file:read_file(Filename),
- [PP|_] = binary:split(File, [<<"\r\n">>, <<"\n">>]),
- PP;
- PP ->
- PP
- end,
- Algo = {
- proplists:get_value(cipher, ConfigEntryDecoder, rabbit_pbe:default_cipher()),
- proplists:get_value(hash, ConfigEntryDecoder, rabbit_pbe:default_hash()),
- proplists:get_value(iterations, ConfigEntryDecoder, rabbit_pbe:default_iterations()),
- PassPhrase
- },
- decrypt_config(Apps, Algo),
+ rabbit_prelaunch_conf:decrypt_config(Apps),
OrderedApps = app_utils:app_dependency_order(Apps, false),
case lists:member(rabbit, Apps) of
false -> rabbit_boot_steps:run_boot_steps(Apps); %% plugin activation
@@ -601,102 +612,6 @@ start_loaded_apps(Apps, RestartTypes) ->
handle_app_error(could_not_start),
RestartTypes).
-%% rabbitmq/rabbitmq-server#952
-%% This function is to be called after configuration has been optionally generated
-%% and the sysmon_handler application loaded, but not started. It will ensure that
-%% sane defaults are used for configuration settings that haven't been set by the
-%% user
-ensure_sysmon_handler_app_config() ->
- Defaults = [
- {process_limit, 100},
- {port_limit, 100},
- {gc_ms_limit, 0},
- {schedule_ms_limit, 0},
- {heap_word_limit, 0},
- {busy_port, false},
- {busy_dist_port, true}
- ],
- lists:foreach(fun({K, V}) ->
- case application:get_env(sysmon_handler, K) of
- undefined ->
- application:set_env(sysmon_handler, K, V);
- _ ->
- ok
- end
- end, Defaults).
-
-%% This function retrieves the correct IoDevice for requesting
-%% input. The problem with using the default IoDevice is that
-%% the Erlang shell prevents us from getting the input.
-%%
-%% Instead we therefore look for the io process used by the
-%% shell and if it can't be found (because the shell is not
-%% started e.g with -noshell) we use the 'user' process.
-%%
-%% This function will not work when either -oldshell or -noinput
-%% options are passed to erl.
-get_input_iodevice() ->
- case whereis(user) of
- undefined -> user;
- User ->
- case group:interfaces(User) of
- [] ->
- user;
- [{user_drv, Drv}] ->
- case user_drv:interfaces(Drv) of
- [] ->
- user;
- [{current_group, IoDevice}] ->
- IoDevice
- end
- end
- end.
-
-decrypt_config([], _) ->
- ok;
-decrypt_config([App|Apps], Algo) ->
- decrypt_app(App, application:get_all_env(App), Algo),
- decrypt_config(Apps, Algo).
-
-decrypt_app(_, [], _) ->
- ok;
-decrypt_app(App, [{Key, Value}|Tail], Algo) ->
- try begin
- case decrypt(Value, Algo) of
- Value ->
- ok;
- NewValue ->
- application:set_env(App, Key, NewValue)
- end
- end
- catch
- exit:{bad_configuration, config_entry_decoder} ->
- exit({bad_configuration, config_entry_decoder});
- _:Msg ->
- rabbit_log:info("Error while decrypting key '~p'. Please check encrypted value, passphrase, and encryption configuration~n", [Key]),
- exit({decryption_error, {key, Key}, Msg})
- end,
- decrypt_app(App, Tail, Algo).
-
-decrypt({encrypted, _}, {_, _, _, undefined}) ->
- exit({bad_configuration, config_entry_decoder});
-decrypt({encrypted, EncValue}, {Cipher, Hash, Iterations, Password}) ->
- rabbit_pbe:decrypt_term(Cipher, Hash, Iterations, Password, EncValue);
-decrypt(List, Algo) when is_list(List) ->
- decrypt_list(List, Algo, []);
-decrypt(Value, _) ->
- Value.
-
-%% We make no distinction between strings and other lists.
-%% When we receive a string, we loop through each element
-%% and ultimately return the string unmodified, as intended.
-decrypt_list([], _, Acc) ->
- lists:reverse(Acc);
-decrypt_list([{Key, Value}|Tail], Algo, Acc) when Key =/= encrypted ->
- decrypt_list(Tail, Algo, [{Key, decrypt(Value, Algo)}|Acc]);
-decrypt_list([Value|Tail], Algo, Acc) ->
- decrypt_list(Tail, Algo, [decrypt(Value, Algo)|Acc]).
-
-spec stop_apps([app_name()]) -> 'ok'.
stop_apps([]) ->
@@ -725,11 +640,15 @@ handle_app_error(Term) ->
is_booting() -> is_booting(node()).
+is_booting(Node) when Node =:= node() ->
+ case rabbit_prelaunch:get_boot_state() of
+ booting -> true;
+ _ -> false
+ end;
is_booting(Node) ->
- case rpc:call(Node, erlang, whereis, [rabbit_boot]) of
+ case rpc:call(Node, rabbit, is_booting, []) of
{badrpc, _} = Err -> Err;
- undefined -> false;
- P when is_pid(P) -> true
+ Ret -> Ret
end.
@@ -795,9 +714,6 @@ do_wait_for_boot_to_start(Node, IterationsLeft) ->
ok
end.
-wait_for_boot_to_finish(Node) ->
- wait_for_boot_to_finish(Node, false, ?BOOT_FINISH_TIMEOUT).
-
wait_for_boot_to_finish(Node, PrintProgressReports) ->
wait_for_boot_to_finish(Node, PrintProgressReports, ?BOOT_FINISH_TIMEOUT).
@@ -917,17 +833,22 @@ total_queue_count() ->
end,
0, rabbit_vhost:list_names()).
-%% TODO this only determines if the rabbit application has started,
-%% not if it is running, never mind plugins. It would be nice to have
-%% more nuance here.
-
-spec is_running() -> boolean().
is_running() -> is_running(node()).
-spec is_running(node()) -> boolean().
-is_running(Node) -> rabbit_nodes:is_process_running(Node, rabbit).
+is_running(Node) when Node =:= node() ->
+ case rabbit_prelaunch:get_boot_state() of
+ ready -> true;
+ _ -> false
+ end;
+is_running(Node) ->
+ case rpc:call(Node, rabbit, is_running, []) of
+ true -> true;
+ _ -> false
+ end.
is_booted() -> is_booted(node()).
@@ -983,105 +904,136 @@ rotate_logs() ->
{'ok',pid()}.
start(normal, []) ->
- case erts_version_check() of
- ok ->
- rabbit_log:info("~n Starting RabbitMQ ~s on Erlang ~s~n ~s~n ~s~n",
- [rabbit_misc:version(), rabbit_misc:otp_release(),
- ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]),
- {ok, SupPid} = rabbit_sup:start_link(),
- true = register(rabbit, self()),
- print_banner(),
- log_banner(),
- warn_if_kernel_config_dubious(),
- warn_if_disc_io_options_dubious(),
- rabbit_boot_steps:run_boot_steps(),
- {ok, SupPid};
- {error, {erlang_version_too_old,
- {found, OTPRel, ERTSVer},
- {required, ?OTP_MINIMUM, ?ERTS_MINIMUM}}} ->
- Msg = "This RabbitMQ version cannot run on Erlang ~s (erts ~s): "
- "minimum required version is ~s (erts ~s)",
- Args = [OTPRel, ERTSVer, ?OTP_MINIMUM, ?ERTS_MINIMUM],
- rabbit_log:error(Msg, Args),
- %% also print to stderr to make this more visible
- io:format(standard_error, "Error: " ++ Msg ++ "~n", Args),
- {error, {erlang_version_too_old, rabbit_misc:format("Erlang ~s or later is required, started on ~s", [?OTP_MINIMUM, OTPRel])}};
- Error ->
+ %% Reset boot state and clear the stop reason again (it was already
+ %% made in rabbitmq_prelaunch).
+ %%
+ %% This is important if the previous startup attempt failed after
+ %% rabbitmq_prelaunch was started and the application is still
+ %% running.
+ rabbit_prelaunch:set_boot_state(booting),
+ rabbit_prelaunch:clear_stop_reason(),
+
+ try
+ run_prelaunch_second_phase(),
+
+ rabbit_log:info("~n Starting RabbitMQ ~s on Erlang ~s~n ~s~n ~s~n",
+ [rabbit_misc:version(), rabbit_misc:otp_release(),
+ ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]),
+ {ok, SupPid} = rabbit_sup:start_link(),
+
+ %% Compatibility with older RabbitMQ versions + required by
+ %% rabbit_node_monitor:notify_node_up/0:
+ %%
+ %% We register the app process under the name `rabbit`. This is
+ %% checked by `is_running(Node)` on a remote node. The process
+ %% is also monitord by rabbit_node_monitor.
+ %%
+ %% The process name must be registered *before* running the boot
+ %% steps: that's when rabbit_node_monitor will set the process
+ %% monitor up.
+ %%
+ %% Note that plugins were not taken care of at this point
+ %% either.
+ rabbit_log_prelaunch:debug(
+ "Register `rabbit` process (~p) for rabbit_node_monitor",
+ [self()]),
+ true = register(rabbit, self()),
+
+ print_banner(),
+ log_banner(),
+ warn_if_kernel_config_dubious(),
+ warn_if_disc_io_options_dubious(),
+ %% We run `rabbit` boot steps only for now. Plugins boot steps
+ %% will be executed as part of the postlaunch phase after they
+ %% are started.
+ rabbit_boot_steps:run_boot_steps([rabbit]),
+ run_postlaunch_phase(),
+ {ok, SupPid}
+ catch
+ throw:{error, _} = Error ->
+ mnesia:stop(),
+ rabbit_prelaunch_errors:log_error(Error),
+ rabbit_prelaunch:set_stop_reason(Error),
+ rabbit_prelaunch:set_boot_state(stopped),
+ Error;
+ Class:Exception:Stacktrace ->
+ mnesia:stop(),
+ rabbit_prelaunch_errors:log_exception(
+ Class, Exception, Stacktrace),
+ Error = {error, Exception},
+ rabbit_prelaunch:set_stop_reason(Error),
+ rabbit_prelaunch:set_boot_state(stopped),
Error
end.
+run_postlaunch_phase() ->
+ spawn(fun() -> do_run_postlaunch_phase() end).
+
+do_run_postlaunch_phase() ->
+ %% Once RabbitMQ itself is started, we need to run a few more steps,
+ %% in particular start plugins.
+ rabbit_log_prelaunch:debug(""),
+ rabbit_log_prelaunch:debug("== Postlaunch phase =="),
+
+ try
+ rabbit_log_prelaunch:debug(""),
+ rabbit_log_prelaunch:debug("== Plugins =="),
+
+ rabbit_log_prelaunch:debug("Setting plugins up"),
+ Plugins = rabbit_plugins:setup(),
+ rabbit_log_prelaunch:debug(
+ "Starting the following plugins: ~p", [Plugins]),
+ app_utils:load_applications(Plugins),
+ ok = rabbit_feature_flags:refresh_feature_flags_after_app_load(
+ Plugins),
+ lists:foreach(
+ fun(Plugin) ->
+ case application:ensure_all_started(Plugin) of
+ {ok, _} -> rabbit_boot_steps:run_boot_steps([Plugin]);
+ Error -> throw(Error)
+ end
+ end, Plugins),
+
+ maybe_sd_notify(),
+
+ rabbit_log_prelaunch:debug("Marking RabbitMQ as running"),
+ rabbit_prelaunch:set_boot_state(ready),
+ ok = rabbit_lager:broker_is_started(),
+ ok = log_broker_started(
+ rabbit_plugins:strictly_plugins(rabbit_plugins:active()))
+ catch
+ throw:{error, _} = Error ->
+ rabbit_prelaunch_errors:log_error(Error),
+ rabbit_prelaunch:set_stop_reason(Error),
+ do_stop();
+ Class:Exception:Stacktrace ->
+ rabbit_prelaunch_errors:log_exception(
+ Class, Exception, Stacktrace),
+ Error = {error, Exception},
+ rabbit_prelaunch:set_stop_reason(Error),
+ do_stop()
+ end.
+
prep_stop(State) ->
rabbit_peer_discovery:maybe_unregister(),
State.
-spec stop(_) -> 'ok'.
-stop(_State) ->
+stop(State) ->
+ rabbit_prelaunch:set_boot_state(stopping),
ok = rabbit_alarm:stop(),
ok = case rabbit_mnesia:is_clustered() of
true -> ok;
false -> rabbit_table:clear_ram_only_tables()
end,
+ case State of
+ [] -> rabbit_prelaunch:set_stop_reason(normal);
+ _ -> rabbit_prelaunch:set_stop_reason(State)
+ end,
+ rabbit_prelaunch:set_boot_state(stopped),
ok.
--spec boot_error(term(), not_available | [tuple()]) -> no_return().
-
-boot_error(_, {could_not_start, rabbit, {{timeout_waiting_for_tables, _}, _}}) ->
- AllNodes = rabbit_mnesia:cluster_nodes(all),
- Suffix = "~nBACKGROUND~n==========~n~n"
- "This cluster node was shut down while other nodes were still running.~n"
- "To avoid losing data, you should start the other nodes first, then~n"
- "start this one. To force this node to start, first invoke~n"
- "\"rabbitmqctl force_boot\". If you do so, any changes made on other~n"
- "cluster nodes after this one was shut down may be lost.~n",
- {Err, Nodes} =
- case AllNodes -- [node()] of
- [] -> {"Timeout contacting cluster nodes. Since RabbitMQ was"
- " shut down forcefully~nit cannot determine which nodes"
- " are timing out.~n" ++ Suffix, []};
- Ns -> {rabbit_misc:format(
- "Timeout contacting cluster nodes: ~p.~n" ++ Suffix, [Ns]),
- Ns}
- end,
- log_boot_error_and_exit(
- timeout_waiting_for_tables,
- "~n" ++ Err ++ rabbit_nodes:diagnostics(Nodes), []);
-boot_error(_, {error, {cannot_log_to_file, unknown, Reason}}) ->
- log_boot_error_and_exit(could_not_initialise_logger,
- "failed to initialised logger: ~p~n",
- [Reason]);
-boot_error(_, {error, {cannot_log_to_file, LogFile,
- {cannot_create_parent_dirs, _, Reason}}}) ->
- log_boot_error_and_exit(could_not_initialise_logger,
- "failed to create parent directory for log file at '~s', reason: ~p~n",
- [LogFile, Reason]);
-boot_error(_, {error, {cannot_log_to_file, LogFile, Reason}}) ->
- log_boot_error_and_exit(could_not_initialise_logger,
- "failed to open log file at '~s', reason: ~p~n",
- [LogFile, Reason]);
-boot_error(_, {error, {generate_config_file, Error}}) ->
- log_boot_error_and_exit(generate_config_file,
- "~nConfig file generation failed:~n~s"
- "In case the setting comes from a plugin, make sure that the plugin is enabled.~n"
- "Alternatively remove the setting from the config.~n",
- [Error]);
-boot_error(Class, Reason) ->
- LogLocations = log_locations(),
- log_boot_error_and_exit(
- Reason,
- "~nError description:~s"
- "~nLog file(s) (may contain more information):~n" ++
- lists:flatten([" ~s~n" || _ <- lists:seq(1, length(LogLocations))]),
- [lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason})] ++
- LogLocations).
-
--spec log_boot_error_and_exit(_, _, _) -> no_return().
-log_boot_error_and_exit(Reason, Format, Args) ->
- rabbit_log:error(Format, Args),
- io:format(standard_error, "~nBOOT FAILED~n===========~n" ++ Format ++ "~n", Args),
- timer:sleep(1000),
- exit(Reason).
-
%%---------------------------------------------------------------------------
%% boot step functions
@@ -1141,10 +1093,6 @@ insert_default_data() ->
%%---------------------------------------------------------------------------
%% logging
-start_logger() ->
- rabbit_lager:start_logger(),
- ok.
-
-spec log_locations() -> [rabbit_lager:log_location()].
log_locations() ->
rabbit_lager:log_locations().
@@ -1176,25 +1124,29 @@ log_broker_started(Plugins) ->
rabbit_log:info(Message),
io:format(" completed with ~p plugins.~n", [length(Plugins)]).
-erts_version_check() ->
- ERTSVer = erlang:system_info(version),
- OTPRel = rabbit_misc:otp_release(),
- case rabbit_misc:version_compare(?ERTS_MINIMUM, ERTSVer, lte) of
- true when ?ERTS_MINIMUM =/= ERTSVer ->
- ok;
- true when ?ERTS_MINIMUM =:= ERTSVer andalso ?OTP_MINIMUM =< OTPRel ->
- %% When a critical regression or bug is found, a new OTP
- %% release can be published without changing the ERTS
- %% version. For instance, this is the case with R16B03 and
- %% R16B03-1.
- %%
- %% In this case, we compare the release versions
- %% alphabetically.
- ok;
- _ -> {error, {erlang_version_too_old,
- {found, OTPRel, ERTSVer},
- {required, ?OTP_MINIMUM, ?ERTS_MINIMUM}}}
- end.
+-define(RABBIT_TEXT_LOGO,
+ "~n ## ## ~s ~s"
+ "~n ## ##"
+ "~n ########## ~s"
+ "~n ###### ##"
+ "~n ########## ~s").
+-define(FG8_START, "\033[38;5;202m").
+-define(BG8_START, "\033[48;5;202m").
+-define(FG32_START, "\033[38;2;255;102;0m").
+-define(BG32_START, "\033[48;2;255;102;0m").
+-define(C_END, "\033[0m").
+-define(RABBIT_8BITCOLOR_LOGO,
+ "~n " ?BG8_START " " ?C_END " " ?BG8_START " " ?C_END " \033[1m" ?FG8_START "~s" ?C_END " ~s"
+ "~n " ?BG8_START " " ?C_END " " ?BG8_START " " ?C_END
+ "~n " ?BG8_START " " ?C_END " ~s"
+ "~n " ?BG8_START " " ?C_END " " ?BG8_START " " ?C_END
+ "~n " ?BG8_START " " ?C_END " ~s").
+-define(RABBIT_32BITCOLOR_LOGO,
+ "~n " ?BG32_START " " ?C_END " " ?BG32_START " " ?C_END " \033[1m" ?FG32_START "~s" ?C_END " ~s"
+ "~n " ?BG32_START " " ?C_END " " ?BG32_START " " ?C_END
+ "~n " ?BG32_START " " ?C_END " ~s"
+ "~n " ?BG32_START " " ?C_END " " ?BG32_START " " ?C_END
+ "~n " ?BG32_START " " ?C_END " ~s").
print_banner() ->
{ok, Product} = application:get_key(description),
@@ -1205,14 +1157,23 @@ print_banner() ->
(_, []) ->
{"", ["(none)"]}
end,
+ Logo = case rabbit_prelaunch:get_context() of
+ %% We use the colored logo only when running the
+ %% interactive shell and when colors are supported.
+ %%
+ %% Basically it means it will be used on Unix when
+ %% running "make run-broker" and that's about it.
+ #{os_type := {unix, darwin},
+ interactive_shell := true,
+ output_supports_colors := true} -> ?RABBIT_8BITCOLOR_LOGO;
+ #{interactive_shell := true,
+ output_supports_colors := true} -> ?RABBIT_32BITCOLOR_LOGO;
+ _ -> ?RABBIT_TEXT_LOGO
+ end,
%% padded list lines
{LogFmt, LogLocations} = LineListFormatter("~n ~ts", log_locations()),
{CfgFmt, CfgLocations} = LineListFormatter("~n ~ts", config_locations()),
- io:format("~n ## ## ~s ~s"
- "~n ## ##"
- "~n ########## ~s"
- "~n ###### ##"
- "~n ########## ~s"
+ io:format(Logo ++
"~n"
"~n Doc guides: https://rabbitmq.com/documentation.html"
"~n Support: https://rabbitmq.com/contact.html"
diff --git a/src/rabbit_config.erl b/src/rabbit_config.erl
index 6a2cc5c663..573a972c68 100644
--- a/src/rabbit_config.erl
+++ b/src/rabbit_config.erl
@@ -1,164 +1,33 @@
-module(rabbit_config).
-export([
- generate_config_file/5,
- prepare_and_use_config/0,
- prepare_config/1,
- update_app_config/1,
schema_dir/0,
config_files/0,
- get_advanced_config/0,
- validate_config_files/0
+ get_advanced_config/0
]).
-export_type([config_location/0]).
-type config_location() :: string().
-prepare_and_use_config() ->
- case legacy_erlang_term_config_used() of
- true ->
- %% Use .config file
- ok;
- false ->
- case prepare_config(get_confs()) of
- ok ->
- %% No .conf to generate from
- ok;
- {ok, GeneratedConfigFile} ->
- %% Generated config file
- update_app_config(GeneratedConfigFile);
- {error, Err} ->
- {error, Err}
- end
- end.
-
%% we support both the classic Erlang term
%% config file (rabbitmq.config) as well as rabbitmq.conf
legacy_erlang_term_config_used() ->
- case init:get_argument(config) of
- error -> false;
- {ok, [Config | _]} ->
- ConfigFile = Config ++ ".config",
- rabbit_file:is_file(ConfigFile)
- andalso
- get_advanced_config() == none
+ case get_prelaunch_config_state() of
+ #{config_type := erlang,
+ config_advanced_file := undefined} ->
+ true;
+ _ ->
+ false
end.
get_confs() ->
- case init:get_argument(conf) of
- {ok, Confs} -> [ filename:rootname(Conf, ".conf") ++ ".conf"
- || Conf <- Confs ];
- _ -> []
- end.
-
-prepare_config(Confs) ->
- case {init:get_argument(conf_dir), init:get_argument(conf_script_dir)} of
- {{ok, ConfDir}, {ok, ScriptDir}} ->
- ConfFiles = [Conf || Conf <- Confs,
- rabbit_file:is_file(Conf)],
- case ConfFiles of
- [] -> ok;
- _ ->
- case generate_config_file(ConfFiles, ConfDir, ScriptDir) of
- {ok, GeneratedConfigFile} ->
- {ok, GeneratedConfigFile};
- {error, Reason} ->
- {error, Reason}
- end
- end;
- _ -> ok
- end.
-
-update_app_config(ConfigFile) ->
- RunningApps = [ App || {App, _, _} <- application:which_applications() ],
- LoadedApps = [ App || {App, _, _} <- application:loaded_applications() ],
- {ok, [Config]} = file:consult(ConfigFile),
- %% For application config to be updated, applications should
- %% be unloaded first.
- %% If an application is already running, print an error.
- lists:foreach(fun({App, AppConfig}) ->
- case lists:member(App, RunningApps) of
- true ->
- maybe_print_warning_for_running_app(App, AppConfig);
- false ->
- case lists:member(App, LoadedApps) of
- true -> application:unload(App);
- false -> ok
- end
- end
- end,
- Config),
- maybe_set_net_ticktime(proplists:get_value(kernel, Config)),
- ok = application_controller:change_application_data([], [ConfigFile]),
- %% Make sure to load all the applications we're unloaded
- lists:foreach(fun(App) -> application:load(App) end, LoadedApps),
- ok.
-
-maybe_print_warning_for_running_app(kernel, Config) ->
- ConfigWithoutSupportedEntry = proplists:delete(net_ticktime, Config),
- case length(ConfigWithoutSupportedEntry) > 0 of
- true -> io:format(standard_error,
- "~nUnable to update config for app ~p from a .conf file."
- " The app is already running. Use advanced.config instead.~n", [kernel]);
- false -> ok
- end;
-maybe_print_warning_for_running_app(App, _Config) ->
- io:format(standard_error,
- "~nUnable to update config for app ~p from a .conf file: "
- " The app is already running.~n",
- [App]).
-
-maybe_set_net_ticktime(undefined) ->
- ok;
-maybe_set_net_ticktime(KernelConfig) ->
- case proplists:get_value(net_ticktime, KernelConfig) of
- undefined ->
- ok;
- NetTickTime ->
- case net_kernel:set_net_ticktime(NetTickTime, 0) of
- unchanged ->
- ok;
- change_initiated ->
- ok;
- {ongoing_change_to, NewNetTicktime} ->
- io:format(standard_error,
- "~nCouldn't set net_ticktime to ~p "
- "as net_kernel is busy changing net_ticktime to ~p seconds ~n",
- [NetTickTime, NewNetTicktime])
- end
- end.
-
-generate_config_file(ConfFiles, ConfDir, ScriptDir) ->
- generate_config_file(ConfFiles, ConfDir, ScriptDir,
- schema_dir(), get_advanced_config()).
-
-
-generate_config_file(ConfFiles, ConfDir, ScriptDir, SchemaDir, Advanced) ->
- prepare_plugin_schemas(SchemaDir),
- Cuttlefish = filename:join([ScriptDir, "cuttlefish"]),
- GeneratedDir = filename:join([ConfDir, "generated"]),
-
- AdvancedConfigArg = case check_advanced_config(Advanced) of
- {ok, FileName} -> [" -a ", FileName];
- none -> []
- end,
- rabbit_file:recursive_delete([GeneratedDir]),
- Command = lists:concat(["escript ", "\"", Cuttlefish, "\"",
- " -f rabbitmq -s ", "\"", SchemaDir, "\"",
- " -e ", "\"", ConfDir, "\"",
- [[" -c \"", ConfFile, "\""] || ConfFile <- ConfFiles],
- AdvancedConfigArg]),
- rabbit_log:debug("Generating config file using '~s'", [Command]),
- Result = rabbit_misc:os_cmd(Command),
- case string:str(Result, " -config ") of
- 0 -> {error, {generation_error, Result}};
+ case get_prelaunch_config_state() of
+ #{config_files := Confs} ->
+ [ filename:rootname(Conf, ".conf") ++ ".conf"
+ || Conf <- Confs ];
_ ->
- [OutFile] = rabbit_file:wildcard("rabbitmq.*.config", GeneratedDir),
- ResultFile = filename:join([GeneratedDir, "rabbitmq.config"]),
- rabbit_file:rename(filename:join([GeneratedDir, OutFile]),
- ResultFile),
- {ok, ResultFile}
+ []
end.
schema_dir() ->
@@ -171,17 +40,10 @@ schema_dir() ->
end
end.
-check_advanced_config(none) -> none;
-check_advanced_config(ConfigName) ->
- case rabbit_file:is_file(ConfigName) of
- true -> {ok, ConfigName};
- false -> none
- end.
-
get_advanced_config() ->
- case init:get_argument(conf_advanced) of
+ case get_prelaunch_config_state() of
%% There can be only one advanced.config
- {ok, [FileName | _]} ->
+ #{config_advanced_file := FileName} ->
case rabbit_file:is_file(FileName) of
true -> FileName;
false -> none
@@ -189,26 +51,21 @@ get_advanced_config() ->
_ -> none
end.
-
-prepare_plugin_schemas(SchemaDir) ->
- case rabbit_file:is_dir(SchemaDir) of
- true -> rabbit_plugins:extract_schemas(SchemaDir);
- false -> ok
- end.
-
-spec config_files() -> [config_location()].
config_files() ->
case legacy_erlang_term_config_used() of
true ->
- case init:get_argument(config) of
- {ok, Files} -> [ filename:absname(filename:rootname(File) ++ ".config")
- || [File] <- Files];
- error -> case config_setting() of
- none -> [];
- File -> [filename:absname(filename:rootname(File, ".config") ++ ".config")
- ++
- " (not found)"]
- end
+ case get_prelaunch_config_state() of
+ #{config_files := Files} ->
+ [ filename:absname(filename:rootname(File) ++ ".config")
+ || File <- Files];
+ _ ->
+ case config_setting() of
+ none -> [];
+ File -> [filename:absname(filename:rootname(File, ".config") ++ ".config")
+ ++
+ " (not found)"]
+ end
end;
false ->
ConfFiles = [filename:absname(File) || File <- get_confs(),
@@ -221,6 +78,8 @@ config_files() ->
end.
+get_prelaunch_config_state() ->
+ rabbit_prelaunch_conf:get_config_state().
%% This is a pain. We want to know where the config file is. But we
%% can't specify it on the command line if it is missing or the VM
@@ -232,95 +91,9 @@ config_files() ->
config_setting() ->
case application:get_env(rabbit, windows_service_config) of
{ok, File1} -> File1;
- undefined -> case os:getenv("RABBITMQ_CONFIG_FILE") of
- false -> none;
- File2 -> File2
- end
- end.
-
--spec validate_config_files() -> ok | {error, {Fmt :: string(), Args :: list()}}.
-validate_config_files() ->
- ConfigFile = os:getenv("RABBITMQ_CONFIG_FILE"),
- AdvancedConfigFile = get_advanced_config(),
- AssertConfig = case filename:extension(ConfigFile) of
- ".config" -> assert_config(ConfigFile, "RABBITMQ_CONFIG_FILE");
- ".conf" -> assert_conf(ConfigFile, "RABBITMQ_CONFIG_FILE");
- _ -> ok
- end,
- case AssertConfig of
- ok ->
- assert_config(AdvancedConfigFile, "RABBITMQ_ADVANCED_CONFIG_FILE");
- {error, Err} ->
- {error, Err}
+ undefined ->
+ case application:get_env(rabbitmq_prelaunch, context) of
+ #{main_config_file := File2} -> File2;
+ _ -> none
+ end
end.
-
-assert_config("", _) -> ok;
-assert_config(none, _) -> ok;
-assert_config(Filename, Env) ->
- assert_config(filename:extension(Filename), Filename, Env).
-
--define(ERRMSG_INDENT, " ").
-
-assert_config(".config", Filename, Env) ->
- case filelib:is_regular(Filename) of
- true ->
- case file:consult(Filename) of
- {ok, []} -> {error,
- {"Config file ~s should not be empty: ~s",
- [Env, Filename]}};
- {ok, [_]} -> ok;
- {ok, [_|_]} -> {error,
- {"Config file ~s must contain ONE list ended by <dot>: ~s",
- [Env, Filename]}};
- {error, {1, erl_parse, Err}} ->
- % Note: the sequence of spaces is to indent from the [error] prefix, like this:
- %
- % 2018-09-06 07:05:40.225 [error] Unable to parse erlang terms from RABBITMQ_ADVANCED_CONFIG_FILE...
- % Reason: ["syntax error before: ",[]]
- {error, {"Unable to parse erlang terms from ~s file: ~s~n"
- ?ERRMSG_INDENT
- "Reason: ~p~n"
- ?ERRMSG_INDENT
- "Check that the file is in erlang term format. " ++
- case Env of
- "RABBITMQ_CONFIG_FILE" ->
- "If you are using the new ini-style format, the file extension should be '.conf'~n";
- _ -> ""
- end,
- [Env, Filename, Err]}};
- {error, Err} ->
- {error, {"Unable to parse erlang terms from ~s file: ~s~n"
- ?ERRMSG_INDENT
- "Error: ~p~n",
- [Env, Filename, Err]}}
- end;
- false ->
- ok
- end;
-assert_config(BadExt, Filename, Env) ->
- {error, {"'~s': Expected extension '.config', got extension '~s' for file '~s'~n", [Env, BadExt, Filename]}}.
-
-assert_conf("", _) -> ok;
-assert_conf(Filename, Env) ->
- assert_conf(filename:extension(Filename), Filename, Env).
-
-assert_conf(".conf", Filename, Env) ->
- case filelib:is_regular(Filename) of
- true ->
- case file:consult(Filename) of
- {ok, []} -> ok;
- {ok, _} ->
- {error, {"Wrong format of the config file ~s: ~s~n"
- ?ERRMSG_INDENT
- "Check that the file is in the new ini-style config format. "
- "If you are using the old format the file extension should "
- "be .config~n",
- [Env, Filename]}};
- _ ->
- ok
- end;
- false ->
- ok
- end;
-assert_conf(BadExt, Filename, Env) ->
- {error, {"'~s': Expected extension '.config', got extension '~s' for file '~s'~n", [Env, BadExt, Filename]}}.
diff --git a/src/rabbit_hipe.erl b/src/rabbit_hipe.erl
index bb13a3baf4..a180e2f1c9 100644
--- a/src/rabbit_hipe.erl
+++ b/src/rabbit_hipe.erl
@@ -20,17 +20,18 @@ maybe_hipe_compile() ->
end.
log_hipe_result({ok, disabled}) ->
- ok;
+ rabbit_log_prelaunch:info(
+ "HiPE disabled: no modules were natively recompiled.~n", []);
log_hipe_result({ok, already_compiled}) ->
- rabbit_log:info(
+ rabbit_log_prelaunch:info(
"HiPE in use: modules already natively compiled.~n", []);
log_hipe_result({ok, Count, Duration}) ->
- rabbit_log:info(
+ rabbit_log_prelaunch:info(
"HiPE in use: compiled ~B modules in ~Bs.~n", [Count, Duration]);
log_hipe_result(false) ->
io:format(
"~nNot HiPE compiling: HiPE not found in this Erlang installation.~n"),
- rabbit_log:warning(
+ rabbit_log_prelaunch:warning(
"Not HiPE compiling: HiPE not found in this Erlang installation.~n").
hipe_compile() ->
diff --git a/src/rabbit_plugins.erl b/src/rabbit_plugins.erl
index 7757b5229e..02e3e0d8de 100644
--- a/src/rabbit_plugins.erl
+++ b/src/rabbit_plugins.erl
@@ -20,10 +20,9 @@
-export([setup/0, active/0, read_enabled/1, list/1, list/2, dependencies/3, running_plugins/0]).
-export([ensure/1]).
--export([extract_schemas/1]).
-export([validate_plugins/1, format_invalid_plugins/1]).
-export([is_strictly_plugin/1, strictly_plugins/2, strictly_plugins/1]).
--export([plugins_dir/0, plugins_expand_dir/0, enabled_plugins_file/0]).
+-export([plugins_dir/0, plugin_names/1, plugins_expand_dir/0, enabled_plugins_file/0]).
% Export for testing purpose.
-export([is_version_supported/2, validate_plugins/2]).
@@ -49,7 +48,6 @@ ensure1(FileJustChanged0) ->
FileJustChanged ->
Enabled = read_enabled(OurFile),
Wanted = prepare_plugins(Enabled),
- rabbit_config:prepare_and_use_config(),
Current = active(),
Start = Wanted -- Current,
Stop = Current -- Wanted,
@@ -132,50 +130,6 @@ setup() ->
Enabled = enabled_plugins(),
prepare_plugins(Enabled).
-extract_schemas(SchemaDir) ->
- application:load(rabbit),
- {ok, EnabledFile} = application:get_env(rabbit, enabled_plugins_file),
- Enabled = read_enabled(EnabledFile),
-
- {ok, PluginsDistDir} = application:get_env(rabbit, plugins_dir),
-
- AllPlugins = list(PluginsDistDir),
- Wanted = dependencies(false, Enabled, AllPlugins),
- WantedPlugins = lookup_plugins(Wanted, AllPlugins),
- [ extract_schema(Plugin, SchemaDir) || Plugin <- WantedPlugins ],
- application:unload(rabbit),
- ok.
-
-extract_schema(#plugin{type = ez, location = Location}, SchemaDir) ->
- {ok, Files} = zip:extract(Location,
- [memory, {file_filter,
- fun(#zip_file{name = Name}) ->
- string:str(Name, "priv/schema") > 0
- end}]),
- lists:foreach(
- fun({FileName, Content}) ->
- ok = file:write_file(filename:join([SchemaDir,
- filename:basename(FileName)]),
- Content)
- end,
- Files),
- ok;
-extract_schema(#plugin{type = dir, location = Location}, SchemaDir) ->
- PluginSchema = filename:join([Location,
- "priv",
- "schema"]),
- case rabbit_file:is_dir(PluginSchema) of
- false -> ok;
- true ->
- PluginSchemaFiles =
- [ filename:join(PluginSchema, FileName)
- || FileName <- rabbit_file:wildcard(".*\\.schema",
- PluginSchema) ],
- [ file:copy(SchemaFile, SchemaDir)
- || SchemaFile <- PluginSchemaFiles ]
- end.
-
-
%% @doc Lists the plugins which are currently running.
-spec active() -> [plugin_name()].
diff --git a/src/rabbit_prelaunch.erl b/src/rabbit_prelaunch.erl
deleted file mode 100644
index 51deb0f0ea..0000000000
--- a/src/rabbit_prelaunch.erl
+++ /dev/null
@@ -1,161 +0,0 @@
-%% 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.
-%%
-%% The Initial Developer of the Original Code is GoPivotal, Inc.
-%% Copyright (c) 2007-2019 Pivotal Software, Inc. All rights reserved.
-%%
-
--module(rabbit_prelaunch).
-
--export([start/0, stop/0]).
--export([config_file_check/0]).
-
--import(rabbit_misc, [pget/2, pget/3]).
-
--include("rabbit.hrl").
-
--define(SET_DIST_PORT, 0).
--define(ERROR_CODE, 1).
--define(DO_NOT_SET_DIST_PORT, 2).
--define(EX_USAGE, 64).
-
-%%----------------------------------------------------------------------------
-
--spec start() -> no_return().
-
-start() ->
- case init:get_plain_arguments() of
- [NodeStr] ->
- Node = rabbit_nodes:make(NodeStr),
- {NodeName, NodeHost} = rabbit_nodes:parts(Node),
- ok = duplicate_node_check(NodeName, NodeHost),
- ok = dist_port_set_check(),
- ok = dist_port_range_check(),
- ok = dist_port_use_check(NodeHost),
- ok = config_file_check();
- [] ->
- %% Ignore running node while installing windows service
- ok = dist_port_set_check(),
- ok
- end,
- rabbit_misc:quit(?SET_DIST_PORT),
- ok.
-
--spec stop() -> 'ok'.
-
-stop() ->
- ok.
-
-%%----------------------------------------------------------------------------
-
-config_file_check() ->
- case rabbit_config:validate_config_files() of
- ok -> ok;
- {error, {ErrFmt, ErrArgs}} ->
- ErrMsg = io_lib:format(ErrFmt, ErrArgs),
- {{Year, Month, Day}, {Hour, Minute, Second, Milli}} = lager_util:localtime_ms(),
- io:format(standard_error, "~b-~2..0b-~2..0b ~2..0b:~2..0b:~2..0b.~b [error] ~s",
- [Year, Month, Day, Hour, Minute, Second, Milli, ErrMsg]),
- rabbit_misc:quit(?EX_USAGE)
- end.
-
-%% Check whether a node with the same name is already running
-duplicate_node_check(NodeName, NodeHost) ->
- case rabbit_nodes:names(NodeHost) of
- {ok, NamePorts} ->
- case proplists:is_defined(NodeName, NamePorts) of
- true -> io:format(
- "ERROR: node with name ~p already running on ~p~n",
- [NodeName, NodeHost]),
- rabbit_misc:quit(?ERROR_CODE);
- false -> ok
- end;
- {error, EpmdReason} ->
- io:format("ERROR: epmd error for host ~s: ~s~n",
- [NodeHost, rabbit_misc:format_inet_error(EpmdReason)]),
- rabbit_misc:quit(?ERROR_CODE)
- end.
-
-dist_port_set_check() ->
- case get_config(os:getenv("RABBITMQ_CONFIG_ARG_FILE")) of
- {ok, [Config]} ->
- Kernel = pget(kernel, Config, []),
- case {pget(inet_dist_listen_min, Kernel, none),
- pget(inet_dist_listen_max, Kernel, none)} of
- {none, none} -> ok;
- _ -> rabbit_misc:quit(?DO_NOT_SET_DIST_PORT)
- end;
- {ok, _} ->
- ok;
- {error, _} ->
- ok
- end.
-
-get_config("") -> {error, nofile};
-get_config(File) ->
- case consult_file(File) of
- {ok, Contents} -> {ok, Contents};
- {error, _} = E -> E
- end.
-
-consult_file(false) -> {error, nofile};
-consult_file(File) ->
- FileName = case filename:extension(File) of
- "" -> File ++ ".config";
- ".config" -> File;
- _ -> ""
- end,
- file:consult(FileName).
-
-dist_port_range_check() ->
- case os:getenv("RABBITMQ_DIST_PORT") of
- false -> ok;
- PortStr -> case catch list_to_integer(PortStr) of
- Port when is_integer(Port) andalso Port > 65535 ->
- rabbit_misc:quit(?DO_NOT_SET_DIST_PORT);
- _ ->
- ok
- end
- end.
-
-dist_port_use_check(NodeHost) ->
- case os:getenv("RABBITMQ_DIST_PORT") of
- false -> ok;
- PortStr -> Port = list_to_integer(PortStr),
- dist_port_use_check_ipv4(NodeHost, Port)
- end.
-
-dist_port_use_check_ipv4(NodeHost, Port) ->
- case gen_tcp:listen(Port, [inet, {reuseaddr, true}]) of
- {ok, Sock} -> gen_tcp:close(Sock);
- {error, einval} -> dist_port_use_check_ipv6(NodeHost, Port);
- {error, _} -> dist_port_use_check_fail(Port, NodeHost)
- end.
-
-dist_port_use_check_ipv6(NodeHost, Port) ->
- case gen_tcp:listen(Port, [inet6, {reuseaddr, true}]) of
- {ok, Sock} -> gen_tcp:close(Sock);
- {error, _} -> dist_port_use_check_fail(Port, NodeHost)
- end.
-
--spec dist_port_use_check_fail(non_neg_integer(), string()) ->
- no_return().
-
-dist_port_use_check_fail(Port, Host) ->
- {ok, Names} = rabbit_nodes:names(Host),
- case [N || {N, P} <- Names, P =:= Port] of
- [] -> io:format("ERROR: distribution port ~b in use on ~s "
- "(by non-Erlang process?)~n", [Port, Host]);
- [Name] -> io:format("ERROR: distribution port ~b in use by ~s@~s~n",
- [Port, Name, Host])
- end,
- rabbit_misc:quit(?ERROR_CODE).
diff --git a/src/rabbit_prelaunch_cluster.erl b/src/rabbit_prelaunch_cluster.erl
new file mode 100644
index 0000000000..9d3cda99e3
--- /dev/null
+++ b/src/rabbit_prelaunch_cluster.erl
@@ -0,0 +1,22 @@
+-module(rabbit_prelaunch_cluster).
+
+-export([setup/1]).
+
+setup(Context) ->
+ rabbit_log_prelaunch:debug(""),
+ rabbit_log_prelaunch:debug("== Clustering =="),
+ rabbit_log_prelaunch:debug("Preparing cluster status files"),
+ rabbit_node_monitor:prepare_cluster_status_files(),
+ case Context of
+ #{initial_pass := true} ->
+ rabbit_log_prelaunch:debug("Upgrading Mnesia schema"),
+ ok = rabbit_upgrade:maybe_upgrade_mnesia();
+ _ ->
+ ok
+ end,
+ %% It's important that the consistency check happens after
+ %% the upgrade, since if we are a secondary node the
+ %% primary node will have forgotten us
+ rabbit_log_prelaunch:debug("Checking cluster consistency"),
+ rabbit_mnesia:check_cluster_consistency(),
+ ok.
diff --git a/src/rabbit_prelaunch_conf.erl b/src/rabbit_prelaunch_conf.erl
new file mode 100644
index 0000000000..23d0f68f82
--- /dev/null
+++ b/src/rabbit_prelaunch_conf.erl
@@ -0,0 +1,534 @@
+-module(rabbit_prelaunch_conf).
+
+-include_lib("kernel/include/file.hrl").
+-include_lib("stdlib/include/zip.hrl").
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-export([setup/1,
+ get_config_state/0,
+ generate_config_from_cuttlefish_files/3,
+ decrypt_config/1]).
+
+-ifdef(TEST).
+-export([decrypt_config/2]).
+-endif.
+
+setup(Context) ->
+ rabbit_log_prelaunch:debug(""),
+ rabbit_log_prelaunch:debug("== Configuration =="),
+
+ %% TODO: Check if directories/files are inside Mnesia dir.
+
+ %% TODO: Support glob patterns & directories in RABBITMQ_CONFIG_FILE.
+ %% TODO: Always try parsing of both erlang and cuttlefish formats.
+
+ update_enabled_plugins_file(Context),
+
+ set_default_config(),
+
+ AdvancedConfigFile = find_actual_advanced_config_file(Context),
+ State = case find_actual_main_config_file(Context) of
+ {MainConfigFile, erlang} ->
+ Config = load_erlang_term_based_config_file(
+ MainConfigFile),
+ Apps = [App || {App, _} <- Config],
+ decrypt_config(Apps),
+ #{config_type => erlang,
+ config_files => [MainConfigFile],
+ config_advanced_file => undefined};
+ {MainConfigFile, cuttlefish} ->
+ ConfigFiles = [MainConfigFile],
+ Config = load_cuttlefish_config_file(Context,
+ ConfigFiles,
+ AdvancedConfigFile),
+ Apps = [App || {App, _} <- Config],
+ decrypt_config(Apps),
+ #{config_type => cuttlefish,
+ config_files => ConfigFiles,
+ config_advanced_file => AdvancedConfigFile};
+ undefined when AdvancedConfigFile =/= undefined ->
+ rabbit_log_prelaunch:warning(
+ "Using RABBITMQ_ADVANCED_CONFIG_FILE: ~s",
+ [AdvancedConfigFile]),
+ Config = load_erlang_term_based_config_file(
+ AdvancedConfigFile),
+ Apps = [App || {App, _} <- Config],
+ decrypt_config(Apps),
+ #{config_type => erlang,
+ config_files => [AdvancedConfigFile],
+ config_advanced_file => AdvancedConfigFile};
+ undefined ->
+ #{config_type => undefined,
+ config_files => [],
+ config_advanced_file => undefined}
+ end,
+ override_with_hard_coded_critical_config(),
+ rabbit_log_prelaunch:debug(
+ "Saving config state to application env: ~p", [State]),
+ store_config_state(State).
+
+store_config_state(ConfigState) ->
+ persistent_term:put({rabbitmq_prelaunch, config_state}, ConfigState).
+
+get_config_state() ->
+ persistent_term:get({rabbitmq_prelaunch, config_state}, undefined).
+
+%% -------------------------------------------------------------------
+%% `enabled_plugins` file content initialization.
+%% -------------------------------------------------------------------
+
+update_enabled_plugins_file(Context) ->
+ %% We only do this on startup, not when the configuration is
+ %% reloaded.
+ case get_config_state() of
+ undefined -> update_enabled_plugins_file1(Context);
+ _ -> ok
+ end.
+
+update_enabled_plugins_file1(#{enabled_plugins := undefined}) ->
+ ok;
+update_enabled_plugins_file1(#{enabled_plugins := all,
+ plugins_path := Path} = Context) ->
+ List = [P#plugin.name || P <- rabbit_plugins:list(Path)],
+ do_update_enabled_plugins_file(Context, List);
+update_enabled_plugins_file1(#{enabled_plugins := List} = Context) ->
+ do_update_enabled_plugins_file(Context, List).
+
+do_update_enabled_plugins_file(#{enabled_plugins_file := File}, List) ->
+ SortedList = lists:usort(List),
+ case SortedList of
+ [] ->
+ rabbit_log_prelaunch:debug("Marking all plugins as disabled");
+ _ ->
+ rabbit_log_prelaunch:debug(
+ "Marking the following plugins as enabled:"),
+ [rabbit_log_prelaunch:debug(" - ~s", [P]) || P <- SortedList]
+ end,
+ Content = io_lib:format("~p.~n", [SortedList]),
+ case file:write_file(File, Content) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ rabbit_log_prelaunch:error(
+ "Failed to update enabled plugins file \"~ts\" "
+ "from $RABBITMQ_ENABLED_PLUGINS: ~ts",
+ [File, file:format_error(Reason)]),
+ throw({error, failed_to_update_enabled_plugins_file})
+ end.
+
+%% -------------------------------------------------------------------
+%% Configuration loading.
+%% -------------------------------------------------------------------
+
+set_default_config() ->
+ rabbit_log_prelaunch:debug("Setting default config"),
+ Config = [
+ {ra,
+ [
+ %% Use a larger segments size for queues.
+ {segment_max_entries, 32768},
+ {wal_max_size_bytes, 536870912} %% 5 * 2 ^ 20
+ ]},
+ {sysmon_handler,
+ [{process_limit, 100},
+ {port_limit, 100},
+ {gc_ms_limit, 0},
+ {schedule_ms_limit, 0},
+ {heap_word_limit, 0},
+ {busy_port, false},
+ {busy_dist_port, true}]}
+ ],
+ apply_erlang_term_based_config(Config).
+
+find_actual_main_config_file(#{main_config_file := File}) ->
+ case filelib:is_regular(File) of
+ true ->
+ Format = case filename:extension(File) of
+ ".conf" -> cuttlefish;
+ ".config" -> erlang;
+ _ -> determine_config_format(File)
+ end,
+ {File, Format};
+ false ->
+ OldFormatFile = File ++ ".config",
+ NewFormatFile = File ++ ".conf",
+ case filelib:is_regular(OldFormatFile) of
+ true ->
+ case filelib:is_regular(NewFormatFile) of
+ true ->
+ rabbit_log_prelaunch:warning(
+ "Both old (.config) and new (.conf) format config "
+ "files exist."),
+ rabbit_log_prelaunch:warning(
+ "Using the old format config file: ~s",
+ [OldFormatFile]),
+ rabbit_log_prelaunch:warning(
+ "Please update your config files to the new format "
+ "and remove the old file."),
+ ok;
+ false ->
+ ok
+ end,
+ {OldFormatFile, erlang};
+ false ->
+ case filelib:is_regular(NewFormatFile) of
+ true -> {NewFormatFile, cuttlefish};
+ false -> undefined
+ end
+ end
+ end.
+
+find_actual_advanced_config_file(#{advanced_config_file := File}) ->
+ case filelib:is_regular(File) of
+ true -> File;
+ false -> undefined
+ end.
+
+determine_config_format(File) ->
+ case filelib:file_size(File) of
+ 0 ->
+ cuttlefish;
+ _ ->
+ case file:consult(File) of
+ {ok, _} -> erlang;
+ _ -> cuttlefish
+ end
+ end.
+
+load_erlang_term_based_config_file(ConfigFile) ->
+ rabbit_log_prelaunch:debug(
+ "Loading configuration file \"~ts\" (Erlang terms based)", [ConfigFile]),
+ case file:consult(ConfigFile) of
+ {ok, [Config]} when is_list(Config) ->
+ apply_erlang_term_based_config(Config),
+ Config;
+ {ok, OtherTerms} ->
+ rabbit_log_prelaunch:error(
+ "Failed to load configuration file \"~ts\", "
+ "incorrect format: ~p",
+ [ConfigFile, OtherTerms]),
+ throw({error, failed_to_parse_configuration_file});
+ {error, Reason} ->
+ rabbit_log_prelaunch:error(
+ "Failed to load configuration file \"~ts\": ~ts",
+ [ConfigFile, file:format_error(Reason)]),
+ throw({error, failed_to_read_configuration_file})
+ end.
+
+load_cuttlefish_config_file(Context,
+ ConfigFiles,
+ AdvancedConfigFile) ->
+ Config = generate_config_from_cuttlefish_files(
+ Context, ConfigFiles, AdvancedConfigFile),
+ apply_erlang_term_based_config(Config),
+ Config.
+
+generate_config_from_cuttlefish_files(Context,
+ ConfigFiles,
+ AdvancedConfigFile) ->
+ %% Load schemas.
+ SchemaFiles = find_cuttlefish_schemas(Context),
+ case SchemaFiles of
+ [] ->
+ rabbit_log_prelaunch:error(
+ "No configuration schema found~n", []),
+ throw({error, no_configuration_schema_found});
+ _ ->
+ rabbit_log_prelaunch:debug(
+ "Configuration schemas found:~n", []),
+ [rabbit_log_prelaunch:debug(" - ~ts", [SchemaFile])
+ || SchemaFile <- SchemaFiles],
+ ok
+ end,
+ Schema = cuttlefish_schema:files(SchemaFiles),
+
+ %% Load configuration.
+ rabbit_log_prelaunch:debug(
+ "Loading configuration files (Cuttlefish based):"),
+ [rabbit_log_prelaunch:debug(
+ " - ~ts", [ConfigFile]) || ConfigFile <- ConfigFiles],
+ case cuttlefish_conf:files(ConfigFiles) of
+ {errorlist, Errors} ->
+ rabbit_log_prelaunch:error("Error generating configuration:", []),
+ [rabbit_log_prelaunch:error(
+ " - ~ts",
+ [cuttlefish_error:xlate(Error)])
+ || Error <- Errors],
+ throw({error, failed_to_generate_configuration_file});
+ Config0 ->
+ %% Finalize configuration, based on the schema.
+ Config = case cuttlefish_generator:map(Schema, Config0) of
+ {error, Phase, {errorlist, Errors}} ->
+ %% TODO
+ rabbit_log_prelaunch:error(
+ "Error generating configuration in phase ~ts:",
+ [Phase]),
+ [rabbit_log_prelaunch:error(
+ " - ~ts",
+ [cuttlefish_error:xlate(Error)])
+ || Error <- Errors],
+ throw(
+ {error, failed_to_generate_configuration_file});
+ ValidConfig ->
+ proplists:delete(vm_args, ValidConfig)
+ end,
+
+ %% Apply advanced configuration overrides, if any.
+ override_with_advanced_config(Config, AdvancedConfigFile)
+ end.
+
+find_cuttlefish_schemas(Context) ->
+ Apps = list_apps(Context),
+ rabbit_log_prelaunch:debug(
+ "Looking up configuration schemas in the following applications:"),
+ find_cuttlefish_schemas(Apps, []).
+
+find_cuttlefish_schemas([App | Rest], AllSchemas) ->
+ Schemas = list_schemas_in_app(App),
+ find_cuttlefish_schemas(Rest, AllSchemas ++ Schemas);
+find_cuttlefish_schemas([], AllSchemas) ->
+ lists:sort(fun(A,B) -> A < B end, AllSchemas).
+
+list_apps(#{os_type := {win32, _}, plugins_path := PluginsPath}) ->
+ PluginsDirs = string:lexemes(PluginsPath, ";"),
+ list_apps1(PluginsDirs, []);
+list_apps(#{plugins_path := PluginsPath}) ->
+ PluginsDirs = string:lexemes(PluginsPath, ":"),
+ list_apps1(PluginsDirs, []).
+
+
+list_apps1([Dir | Rest], Apps) ->
+ case file:list_dir(Dir) of
+ {ok, Filenames} ->
+ NewApps = [list_to_atom(
+ hd(
+ string:split(filename:basename(F, ".ex"), "-")))
+ || F <- Filenames],
+ Apps1 = lists:umerge(Apps, lists:sort(NewApps)),
+ list_apps1(Rest, Apps1);
+ {error, Reason} ->
+ rabbit_log_prelaunch:debug(
+ "Failed to list directory \"~ts\" content: ~ts",
+ [Dir, file:format_error(Reason)]),
+ list_apps1(Rest, Apps)
+ end;
+list_apps1([], AppInfos) ->
+ AppInfos.
+
+list_schemas_in_app(App) ->
+ {Loaded, Unload} = case application:load(App) of
+ ok -> {true, true};
+ {error, {already_loaded, _}} -> {true, false};
+ {error, _} -> {false, false}
+ end,
+ List = case Loaded of
+ true ->
+ case code:priv_dir(App) of
+ {error, bad_name} ->
+ rabbit_log_prelaunch:debug(
+ " [ ] ~s (no readable priv dir)", [App]),
+ [];
+ PrivDir ->
+ SchemaDir = filename:join([PrivDir, "schema"]),
+ do_list_schemas_in_app(App, SchemaDir)
+ end;
+ false ->
+ rabbit_log_prelaunch:debug(
+ " [ ] ~s (failed to load application)", [App]),
+ []
+ end,
+ case Unload of
+ true -> application:unload(App);
+ false -> ok
+ end,
+ List.
+
+do_list_schemas_in_app(App, SchemaDir) ->
+ case erl_prim_loader:list_dir(SchemaDir) of
+ {ok, Files} ->
+ rabbit_log_prelaunch:debug(" [x] ~s", [App]),
+ [filename:join(SchemaDir, File)
+ || [C | _] = File <- Files,
+ C =/= $.];
+ error ->
+ rabbit_log_prelaunch:debug(
+ " [ ] ~s (no readable schema dir)", [App]),
+ []
+ end.
+
+override_with_advanced_config(Config, undefined) ->
+ Config;
+override_with_advanced_config(Config, AdvancedConfigFile) ->
+ rabbit_log_prelaunch:debug(
+ "Override with advanced configuration file \"~ts\"",
+ [AdvancedConfigFile]),
+ case file:consult(AdvancedConfigFile) of
+ {ok, [AdvancedConfig]} ->
+ cuttlefish_advanced:overlay(Config, AdvancedConfig);
+ {ok, OtherTerms} ->
+ rabbit_log_prelaunch:error(
+ "Failed to load advanced configuration file \"~ts\", "
+ "incorrect format: ~p",
+ [AdvancedConfigFile, OtherTerms]),
+ throw({error, failed_to_parse_advanced_configuration_file});
+ {error, Reason} ->
+ rabbit_log_prelaunch:error(
+ "Failed to load advanced configuration file \"~ts\": ~ts",
+ [AdvancedConfigFile, file:format_error(Reason)]),
+ throw({error, failed_to_read_advanced_configuration_file})
+ end.
+
+override_with_hard_coded_critical_config() ->
+ rabbit_log_prelaunch:debug("Override with hard-coded critical config"),
+ Config = [
+ {ra,
+ %% Make Ra use a custom logger that dispatches to lager
+ %% instead of the default OTP logger
+ [{logger_module, rabbit_log_ra_shim}]}
+ ],
+ apply_erlang_term_based_config(Config).
+
+apply_erlang_term_based_config([{_, []} | Rest]) ->
+ apply_erlang_term_based_config(Rest);
+apply_erlang_term_based_config([{App, Vars} | Rest]) ->
+ rabbit_log_prelaunch:debug(" Applying configuration for '~s':", [App]),
+ apply_app_env_vars(App, Vars),
+ apply_erlang_term_based_config(Rest);
+apply_erlang_term_based_config([]) ->
+ ok.
+
+apply_app_env_vars(App, [{Var, Value} | Rest]) ->
+ rabbit_log_prelaunch:debug(
+ " - ~s = ~p",
+ [Var, Value]),
+ ok = application:set_env(App, Var, Value, [{persistent, true}]),
+ apply_app_env_vars(App, Rest);
+apply_app_env_vars(_, []) ->
+ ok.
+
+%% -------------------------------------------------------------------
+%% Config decryption.
+%% -------------------------------------------------------------------
+
+decrypt_config(Apps) ->
+ rabbit_log_prelaunch:debug("Decoding encrypted config values (if any)"),
+ ConfigEntryDecoder = application:get_env(rabbit, config_entry_decoder, []),
+ decrypt_config(Apps, ConfigEntryDecoder).
+
+decrypt_config([], _) ->
+ ok;
+decrypt_config([App | Apps], Algo) ->
+ Algo1 = decrypt_app(App, application:get_all_env(App), Algo),
+ decrypt_config(Apps, Algo1).
+
+decrypt_app(_, [], Algo) ->
+ Algo;
+decrypt_app(App, [{Key, Value} | Tail], Algo) ->
+ Algo2 = try
+ case decrypt(Value, Algo) of
+ {Value, Algo1} ->
+ Algo1;
+ {NewValue, Algo1} ->
+ rabbit_log_prelaunch:debug(
+ "Value of `~s` decrypted", [Key]),
+ ok = application:set_env(App, Key, NewValue,
+ [{persistent, true}]),
+ Algo1
+ end
+ catch
+ throw:{bad_config_entry_decoder, _} = Error ->
+ throw(Error);
+ _:Msg ->
+ throw({config_decryption_error, {key, Key}, Msg})
+ end,
+ decrypt_app(App, Tail, Algo2).
+
+decrypt({encrypted, EncValue},
+ {Cipher, Hash, Iterations, PassPhrase} = Algo) ->
+ {rabbit_pbe:decrypt_term(Cipher, Hash, Iterations, PassPhrase, EncValue),
+ Algo};
+decrypt({encrypted, _} = Value,
+ ConfigEntryDecoder)
+ when is_list(ConfigEntryDecoder) ->
+ Algo = config_entry_decoder_to_algo(ConfigEntryDecoder),
+ decrypt(Value, Algo);
+decrypt(List, Algo) when is_list(List) ->
+ decrypt_list(List, Algo, []);
+decrypt(Value, Algo) ->
+ {Value, Algo}.
+
+%% We make no distinction between strings and other lists.
+%% When we receive a string, we loop through each element
+%% and ultimately return the string unmodified, as intended.
+decrypt_list([], Algo, Acc) ->
+ {lists:reverse(Acc), Algo};
+decrypt_list([{Key, Value} | Tail], Algo, Acc)
+ when Key =/= encrypted ->
+ {Value1, Algo1} = decrypt(Value, Algo),
+ decrypt_list(Tail, Algo1, [{Key, Value1} | Acc]);
+decrypt_list([Value | Tail], Algo, Acc) ->
+ {Value1, Algo1} = decrypt(Value, Algo),
+ decrypt_list(Tail, Algo1, [Value1 | Acc]).
+
+config_entry_decoder_to_algo(ConfigEntryDecoder) ->
+ case get_passphrase(ConfigEntryDecoder) of
+ undefined ->
+ throw({bad_config_entry_decoder, missing_passphrase});
+ PassPhrase ->
+ {
+ proplists:get_value(
+ cipher, ConfigEntryDecoder, rabbit_pbe:default_cipher()),
+ proplists:get_value(
+ hash, ConfigEntryDecoder, rabbit_pbe:default_hash()),
+ proplists:get_value(
+ iterations, ConfigEntryDecoder, rabbit_pbe:default_iterations()),
+ PassPhrase
+ }
+ end.
+
+get_passphrase(ConfigEntryDecoder) ->
+ rabbit_log_prelaunch:debug("Getting encrypted config passphrase"),
+ case proplists:get_value(passphrase, ConfigEntryDecoder) of
+ prompt ->
+ IoDevice = get_input_iodevice(),
+ io:setopts(IoDevice, [{echo, false}]),
+ PP = lists:droplast(io:get_line(IoDevice,
+ "\nPlease enter the passphrase to unlock encrypted "
+ "configuration entries.\n\nPassphrase: ")),
+ io:setopts(IoDevice, [{echo, true}]),
+ io:format(IoDevice, "~n", []),
+ PP;
+ {file, Filename} ->
+ {ok, File} = file:read_file(Filename),
+ [PP|_] = binary:split(File, [<<"\r\n">>, <<"\n">>]),
+ PP;
+ PP ->
+ PP
+ end.
+
+%% This function retrieves the correct IoDevice for requesting
+%% input. The problem with using the default IoDevice is that
+%% the Erlang shell prevents us from getting the input.
+%%
+%% Instead we therefore look for the io process used by the
+%% shell and if it can't be found (because the shell is not
+%% started e.g with -noshell) we use the 'user' process.
+%%
+%% This function will not work when either -oldshell or -noinput
+%% options are passed to erl.
+get_input_iodevice() ->
+ case whereis(user) of
+ undefined ->
+ user;
+ User ->
+ case group:interfaces(User) of
+ [] ->
+ user;
+ [{user_drv, Drv}] ->
+ case user_drv:interfaces(Drv) of
+ [] -> user;
+ [{current_group, IoDevice}] -> IoDevice
+ end
+ end
+ end.
diff --git a/src/rabbit_prelaunch_feature_flags.erl b/src/rabbit_prelaunch_feature_flags.erl
new file mode 100644
index 0000000000..cef656078a
--- /dev/null
+++ b/src/rabbit_prelaunch_feature_flags.erl
@@ -0,0 +1,25 @@
+-module(rabbit_prelaunch_feature_flags).
+
+-export([setup/1]).
+
+setup(#{feature_flags_file := FFFile}) ->
+ rabbit_log_prelaunch:debug(""),
+ rabbit_log_prelaunch:debug("== Feature flags =="),
+ case filelib:ensure_dir(FFFile) of
+ ok ->
+ rabbit_log_prelaunch:debug("Initializing feature flags registry"),
+ case rabbit_feature_flags:initialize_registry() of
+ ok ->
+ ok;
+ {error, Reason} ->
+ rabbit_log_prelaunch:error(
+ "Failed to initialize feature flags registry: ~p",
+ [Reason]),
+ throw({error, failed_to_initialize_feature_flags_registry})
+ end;
+ {error, Reason} ->
+ rabbit_log_prelaunch:error(
+ "Failed to create feature flags file \"~ts\" directory: ~ts",
+ [FFFile, file:format_error(Reason)]),
+ throw({error, failed_to_create_feature_flags_file_directory})
+ end.
diff --git a/src/rabbit_prelaunch_hipe.erl b/src/rabbit_prelaunch_hipe.erl
new file mode 100644
index 0000000000..16e9b90869
--- /dev/null
+++ b/src/rabbit_prelaunch_hipe.erl
@@ -0,0 +1,10 @@
+-module(rabbit_prelaunch_hipe).
+
+-export([setup/1]).
+
+setup(_Context) ->
+ rabbit_log_prelaunch:debug(""),
+ rabbit_log_prelaunch:debug("== HiPE compitation =="),
+ HipeResult = rabbit_hipe:maybe_hipe_compile(),
+ rabbit_hipe:log_hipe_result(HipeResult),
+ ok.
diff --git a/src/rabbit_prelaunch_logging.erl b/src/rabbit_prelaunch_logging.erl
new file mode 100644
index 0000000000..5d70d46f86
--- /dev/null
+++ b/src/rabbit_prelaunch_logging.erl
@@ -0,0 +1,68 @@
+-module(rabbit_prelaunch_logging).
+
+-export([setup/1]).
+
+setup(Context) ->
+ rabbit_log_prelaunch:debug(""),
+ rabbit_log_prelaunch:debug("== Logging =="),
+ ok = set_ERL_CRASH_DUMP_envvar(Context),
+ ok = configure_lager(Context).
+
+set_ERL_CRASH_DUMP_envvar(#{log_base_dir := LogBaseDir}) ->
+ case os:getenv("ERL_CRASH_DUMP") of
+ false ->
+ ErlCrashDump = filename:join(LogBaseDir, "erl_crash.dump"),
+ rabbit_log_prelaunch:debug(
+ "Setting $ERL_CRASH_DUMP environment variable to \"~ts\"",
+ [ErlCrashDump]),
+ os:putenv("ERL_CRASH_DUMP", ErlCrashDump),
+ ok;
+ ErlCrashDump ->
+ rabbit_log_prelaunch:debug(
+ "$ERL_CRASH_DUMP environment variable already set to \"~ts\"",
+ [ErlCrashDump]),
+ ok
+ end.
+
+configure_lager(#{log_base_dir := LogBaseDir,
+ main_log_file := MainLog,
+ upgrade_log_file := UpgradeLog} = Context) ->
+ {SaslErrorLogger,
+ MainLagerHandler,
+ UpgradeLagerHandler} = case MainLog of
+ "-" ->
+ %% Log to STDOUT.
+ rabbit_log_prelaunch:debug(
+ "Logging to stdout"),
+ {tty,
+ tty,
+ tty};
+ _ ->
+ rabbit_log_prelaunch:debug(
+ "Logging to:"),
+ [rabbit_log_prelaunch:debug(
+ " - ~ts", [Log])
+ || Log <- [MainLog, UpgradeLog]],
+ %% Log to file.
+ {false,
+ MainLog,
+ UpgradeLog}
+ end,
+
+ ok = application:set_env(lager, crash_log, "log/crash.log"),
+
+ Fun = fun({App, Var, Value}) ->
+ case application:get_env(App, Var) of
+ undefined -> ok = application:set_env(App, Var, Value);
+ _ -> ok
+ end
+ end,
+ Vars = [{sasl, sasl_error_logger, SaslErrorLogger},
+ {rabbit, lager_log_root, LogBaseDir},
+ {rabbit, lager_default_file, MainLagerHandler},
+ {rabbit, lager_upgrade_file, UpgradeLagerHandler}],
+ lists:foreach(Fun, Vars),
+
+ ok = rabbit_lager:start_logger(),
+
+ ok = rabbit_prelaunch_early_logging:setup_early_logging(Context, false).
diff --git a/src/rabbit_table.erl b/src/rabbit_table.erl
index 8326666edd..c14b829841 100644
--- a/src/rabbit_table.erl
+++ b/src/rabbit_table.erl
@@ -106,9 +106,11 @@ wait(TableNames, Timeout, Retries) ->
ok ->
ok;
{timeout, BadTabs} ->
- {error, {timeout_waiting_for_tables, BadTabs}};
+ AllNodes = rabbit_mnesia:cluster_nodes(all),
+ {error, {timeout_waiting_for_tables, AllNodes, BadTabs}};
{error, Reason} ->
- {error, {failed_waiting_for_tables, Reason}}
+ AllNodes = rabbit_mnesia:cluster_nodes(all),
+ {error, {failed_waiting_for_tables, AllNodes, Reason}}
end,
case {Retries, Result} of
{_, ok} ->