diff options
| author | Jean-Sébastien Pédron <jean-sebastien@rabbitmq.com> | 2019-12-11 18:01:40 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-12-11 18:01:40 +0100 |
| commit | 178757932e65704abd95a1bf64c460956a9391fa (patch) | |
| tree | 16732ab4722bc4a5e565c2158cc374477392d5de /src | |
| parent | 5e6043ac59abbafd62bae2d3721a01ed232fc5f3 (diff) | |
| parent | 4ca6d804765dde93508d3f475117faeea7e7c3dc (diff) | |
| download | rabbitmq-server-git-178757932e65704abd95a1bf64c460956a9391fa.tar.gz | |
Merge pull request #2142 from rabbitmq/rabbitmq-server-script-replacement
Move rabbitmq-server(8) scripts to Erlang
Diffstat (limited to 'src')
| -rw-r--r-- | src/rabbit.erl | 683 | ||||
| -rw-r--r-- | src/rabbit_config.erl | 291 | ||||
| -rw-r--r-- | src/rabbit_hipe.erl | 9 | ||||
| -rw-r--r-- | src/rabbit_lager.erl | 88 | ||||
| -rw-r--r-- | src/rabbit_plugins.erl | 48 | ||||
| -rw-r--r-- | src/rabbit_prelaunch.erl | 161 | ||||
| -rw-r--r-- | src/rabbit_prelaunch_cluster.erl | 22 | ||||
| -rw-r--r-- | src/rabbit_prelaunch_conf.erl | 534 | ||||
| -rw-r--r-- | src/rabbit_prelaunch_feature_flags.erl | 25 | ||||
| -rw-r--r-- | src/rabbit_prelaunch_hipe.erl | 10 | ||||
| -rw-r--r-- | src/rabbit_prelaunch_logging.erl | 68 | ||||
| -rw-r--r-- | src/rabbit_table.erl | 6 |
12 files changed, 1101 insertions, 844 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_lager.erl b/src/rabbit_lager.erl index 09a114b129..66e1451c45 100644 --- a/src/rabbit_lager.erl +++ b/src/rabbit_lager.erl @@ -243,6 +243,13 @@ configure_lager() -> end; _ -> ok end, + case application:get_env(lager, colored) of + undefined -> + UseColor = rabbit_prelaunch_early_logging:use_colored_logging(), + application:set_env(lager, colored, UseColor); + _ -> + ok + end, %% Set rabbit.log config variable based on environment. prepare_rabbit_log_config(), %% Configure syslog library. @@ -273,7 +280,7 @@ configure_lager() -> LogConfig = application:get_env(rabbit, log, []), LogLevels = application:get_env(rabbit, log_levels, []), Categories = proplists:get_value(categories, LogConfig, []), - CategoriesConfig = case {Categories, LogLevels} of + CategoriesConfig0 = case {Categories, LogLevels} of {[], []} -> []; {[], LogLevels} -> io:format("Using deprecated config parameter 'log_levels'. " @@ -283,13 +290,41 @@ configure_lager() -> LogLevels); {Categories, []} -> Categories; - {Categories, LogLevels} -> + {Categories, _} -> io:format("Using the deprecated config parameter 'rabbit.log_levels' together " "with a new parameter for log categories." " 'rabbit.log_levels' will be ignored. Please remove it from the config. More at " "https://rabbitmq.com/logging.html"), Categories end, + LogLevelsFromContext = case rabbit_prelaunch:get_context() of + #{log_levels := LL} -> LL; + undefined -> undefined + end, + Fun = fun + (global, _, CC) -> + CC; + (color, _, CC) -> + CC; + (CategoryS, LogLevel, CC) -> + Category = list_to_atom(CategoryS), + CCEntry = proplists:get_value( + Category, CC, []), + CCEntry1 = lists:ukeymerge( + 1, + [{level, LogLevel}], + lists:ukeysort(1, CCEntry)), + lists:keystore( + Category, 1, CC, {Category, CCEntry1}) + end, + CategoriesConfig = case LogLevelsFromContext of + undefined -> + CategoriesConfig0; + _ -> + maps:fold(Fun, + CategoriesConfig0, + LogLevelsFromContext) + end, SinkConfigs = lists:map( fun({Name, Config}) -> {rabbit_log:make_internal_sink_name(Name), Config} @@ -365,7 +400,10 @@ lager_backend(exchange) -> lager_exchange_backend. %% Syslog backend is using an old API for configuration and %% does not support proplists. generate_handler(syslog_lager_backend=Backend, HandlerConfig) -> - DefaultConfigVal = default_config_value(level), + %% The default log level is set to `debug` because the actual + %% filtering is made at the sink level. We want to accept all + %% messages here. + DefaultConfigVal = debug, Level = proplists:get_value(level, HandlerConfig, DefaultConfigVal), ok = configure_handler_backend(Backend), [{Backend, @@ -383,13 +421,25 @@ configure_handler_backend(_Backend) -> ok. default_handler_config(lager_console_backend) -> - [{level, default_config_value(level)}, + %% The default log level is set to `debug` because the actual + %% filtering is made at the sink level. We want to accept all + %% messages here. + DefaultConfigVal = debug, + [{level, DefaultConfigVal}, {formatter_config, default_config_value(formatter_config)}]; default_handler_config(lager_exchange_backend) -> - [{level, default_config_value(level)}, + %% The default log level is set to `debug` because the actual + %% filtering is made at the sink level. We want to accept all + %% messages here. + DefaultConfigVal = debug, + [{level, DefaultConfigVal}, {formatter_config, default_config_value(formatter_config)}]; default_handler_config(lager_file_backend) -> - [{level, default_config_value(level)}, + %% The default log level is set to `debug` because the actual + %% filtering is made at the sink level. We want to accept all + %% messages here. + DefaultConfigVal = debug, + [{level, DefaultConfigVal}, {formatter_config, default_config_value(formatter_config)}, {date, ""}, {size, 0}]. @@ -398,12 +448,18 @@ default_config_value(level) -> info; default_config_value(formatter_config) -> [date, " ", time, " ", color, "[", severity, "] ", {pid, ""}, - " ", message, "\n"]. + " ", message, eol()]. syslog_formatter_config() -> [color, "[", severity, "] ", {pid, ""}, - " ", message, "\n"]. + " ", message, eol()]. + +eol() -> + case application:get_env(lager, colored) of + {ok, true} -> "\e[0m\r\n"; + _ -> "\r\n" + end. prepare_rabbit_log_config() -> %% If RABBIT_LOGS is not set, we should ignore it. @@ -493,18 +549,30 @@ set_env_upgrade_log_file(FileName) -> ok = application:set_env(rabbit, log, NewLogConfig). generate_lager_sinks(SinkNames, SinkConfigs) -> + LogLevels = case rabbit_prelaunch:get_context() of + #{log_levels := LL} -> LL; + undefined -> undefined + end, + DefaultLogLevel = case LogLevels of + #{global := LogLevel} -> + LogLevel; + _ -> + default_config_value(level) + end, lists:map(fun(SinkName) -> SinkConfig = proplists:get_value(SinkName, SinkConfigs, []), SinkHandlers = case proplists:get_value(file, SinkConfig, false) of %% If no file defined - forward everything to the default backend false -> - ForwarderLevel = proplists:get_value(level, SinkConfig, inherit), + ForwarderLevel = proplists:get_value(level, + SinkConfig, + DefaultLogLevel), [{lager_forwarder_backend, [lager_util:make_internal_sink_name(lager), ForwarderLevel]}]; %% If a file defined - add a file backend to handlers and remove all default file backends. File -> %% Use `debug` as a default handler to not override a handler level - Level = proplists:get_value(level, SinkConfig, debug), + Level = proplists:get_value(level, SinkConfig, DefaultLogLevel), DefaultGeneratedHandlers = application:get_env(lager, rabbit_handlers, []), SinkFileHandlers = case proplists:get_value(lager_file_backend, DefaultGeneratedHandlers, undefined) of undefined -> 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} -> |
