diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/rabbit.erl | 4 | ||||
| -rw-r--r-- | src/rabbit_alarm.erl | 55 | ||||
| -rw-r--r-- | src/rabbit_amqqueue.erl | 2 | ||||
| -rw-r--r-- | src/rabbit_control.erl | 155 | ||||
| -rw-r--r-- | src/rabbit_error_logger_file_h.erl | 4 | ||||
| -rw-r--r-- | src/rabbit_heartbeat.erl | 2 | ||||
| -rw-r--r-- | src/rabbit_load.erl | 6 | ||||
| -rw-r--r-- | src/rabbit_log.erl | 14 | ||||
| -rw-r--r-- | src/rabbit_memsup_linux.erl | 150 | ||||
| -rw-r--r-- | src/rabbit_misc.erl | 19 | ||||
| -rw-r--r-- | src/rabbit_mnesia.erl | 22 | ||||
| -rw-r--r-- | src/rabbit_multi.erl | 24 | ||||
| -rw-r--r-- | src/rabbit_networking.erl | 4 | ||||
| -rw-r--r-- | src/rabbit_router.erl | 13 | ||||
| -rw-r--r-- | src/rabbit_sasl_report_file_h.erl | 9 | ||||
| -rw-r--r-- | src/rabbit_tests.erl | 56 |
16 files changed, 425 insertions, 114 deletions
diff --git a/src/rabbit.erl b/src/rabbit.erl index a33c5b7bcb..195dd729bd 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -57,8 +57,8 @@ -spec(rotate_logs/1 :: (file_suffix()) -> 'ok' | {'error', any()}). -spec(status/0 :: () -> [{running_applications, [{atom(), string(), string()}]} | - {nodes, [node()]} | - {running_nodes, [node()]}]). + {nodes, [erlang_node()]} | + {running_nodes, [erlang_node()]}]). -spec(log_location/1 :: ('sasl' | 'kernel') -> log_location()). -endif. diff --git a/src/rabbit_alarm.erl b/src/rabbit_alarm.erl index d9c1c45042..6d65b3a407 100644 --- a/src/rabbit_alarm.erl +++ b/src/rabbit_alarm.erl @@ -50,22 +50,57 @@ %%---------------------------------------------------------------------------- start() -> + ok = alarm_handler:add_alarm_handler(?MODULE), + case whereis(memsup) of + undefined -> + Mod = case os:type() of + %% memsup doesn't take account of buffers or + %% cache when considering "free" memory - + %% therefore on Linux we can get memory alarms + %% very easily without any pressure existing on + %% memory at all. Therefore we need to use our + %% own simple memory monitor. + %% + {unix, linux} -> rabbit_memsup_linux; + + %% Start memsup programmatically rather than via + %% the rabbitmq-server script. This is not quite + %% the right thing to do as os_mon checks to see + %% if memsup is available before starting it, + %% but as memsup is available everywhere (even + %% on VXWorks) it should be ok. + %% + %% One benefit of the programmatic startup is + %% that we can add our alarm_handler before + %% memsup is running, thus ensuring that we + %% notice memory alarms that go off on startup. + %% + _ -> memsup + end, + %% This is based on os_mon:childspec(memsup, true) + {ok, _} = supervisor:start_child( + os_mon_sup, + {memsup, {Mod, start_link, []}, + permanent, 2000, worker, [Mod]}), + ok; + _ -> + ok + end, %% The default memsup check interval is 1 minute, which is way too - %% long - rabbit can gobble up all memory in a matter of - %% seconds. Unfortunately the memory_check_interval configuration - %% parameter and memsup:set_check_interval/1 function only provide - %% a granularity of minutes. So we have to peel off one layer of - %% the API to get to the underlying layer which operates at the + %% long - rabbit can gobble up all memory in a matter of seconds. + %% Unfortunately the memory_check_interval configuration parameter + %% and memsup:set_check_interval/1 function only provide a + %% granularity of minutes. So we have to peel off one layer of the + %% API to get to the underlying layer which operates at the %% granularity of milliseconds. %% %% Note that the new setting will only take effect after the first %% check has completed, i.e. after one minute. So if rabbit eats %% all the memory within the first minute after startup then we %% are out of luck. - ok = os_mon:call(memsup, {set_check_interval, ?MEMSUP_CHECK_INTERVAL}, - infinity), - - ok = alarm_handler:add_alarm_handler(?MODULE). + ok = os_mon:call(memsup, + {set_check_interval, ?MEMSUP_CHECK_INTERVAL}, + infinity). stop() -> ok = alarm_handler:delete_alarm_handler(?MODULE). @@ -118,7 +153,7 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %%---------------------------------------------------------------------------- - + alert(Alert, Alertees) -> dict:fold(fun (Pid, {M, F, A}, Acc) -> ok = erlang:apply(M, F, A ++ [Pid, Alert]), diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl index 4e524e3cb9..d142507df3 100644 --- a/src/rabbit_amqqueue.erl +++ b/src/rabbit_amqqueue.erl @@ -91,7 +91,7 @@ -spec(notify_sent/2 :: (pid(), pid()) -> 'ok'). -spec(unblock/2 :: (pid(), pid()) -> 'ok'). -spec(internal_delete/1 :: (queue_name()) -> 'ok' | not_found()). --spec(on_node_down/1 :: (node()) -> 'ok'). +-spec(on_node_down/1 :: (erlang_node()) -> 'ok'). -spec(pseudo_queue/2 :: (binary(), pid()) -> amqqueue()). -endif. diff --git a/src/rabbit_control.erl b/src/rabbit_control.erl index bc588279b4..b821fa0dfa 100644 --- a/src/rabbit_control.erl +++ b/src/rabbit_control.erl @@ -26,43 +26,67 @@ -module(rabbit_control). -include("rabbit.hrl"). --export([start/0, stop/0, action/3]). +-export([start/0, stop/0, action/4]). + +-record(params, {quiet, node, command, args}). -define(RPC_TIMEOUT, 30000). start() -> - case init:get_plain_arguments() of - [] -> + FullCommand = init:get_plain_arguments(), + #params{quiet = Quiet, node = Node, command = Command, args = Args} = + parse_args(FullCommand, #params{quiet = false, + node = rabbit_misc:localnode(rabbit)}), + Inform = case Quiet of + true -> fun(_Format, _Args1) -> ok end; + false -> fun(Format, Args1) -> + io:format(Format ++ " ...~n", Args1) + end + end, + %% The reason we don't use a try/catch here is that rpc:call turns + %% thrown errors into normal return values + case catch action(Command, Node, Args, Inform) of + ok -> + case Quiet of + true -> ok; + false -> io:format("...done.~n") + end, + init:stop(); + {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} -> + error("invalid command '~s'", + [lists:flatten( + rabbit_misc:intersperse( + " ", [atom_to_list(Command) | Args]))]), usage(); - FullCommand -> - {Node, Command, Args} = parse_args(FullCommand), - case catch action(Command, Node, Args) of - ok -> - io:format("done.~n"), - init:stop(); - {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} -> - io:format("Invalid command ~p~n", [FullCommand]), - usage(); - Other -> - io:format("~nrabbit_control action ~p failed:~n~p~n", [Command, Other]), - halt(2) - end + {error, Reason} -> + error("~p", [Reason]), + halt(2); + Other -> + error("~p", [Other]), + halt(2) end. -parse_args(["-n", NodeS, Command | Args]) -> +error(Format, Args) -> + rabbit_misc:format_stderr("Error: " ++ Format ++ "~n", Args). + +parse_args(["-n", NodeS | Args], Params) -> Node = case lists:member($@, NodeS) of true -> list_to_atom(NodeS); false -> rabbit_misc:localnode(list_to_atom(NodeS)) end, - {Node, list_to_atom(Command), Args}; -parse_args([Command | Args]) -> - {rabbit_misc:localnode(rabbit), list_to_atom(Command), Args}. + parse_args(Args, Params#params{node = Node}); +parse_args(["-q" | Args], Params) -> + parse_args(Args, Params#params{quiet = true}); +parse_args([Command | Args], Params) -> + Params#params{command = list_to_atom(Command), args = Args}; +parse_args([], _) -> + usage(). stop() -> ok. usage() -> - io:format("Usage: rabbitmqctl [-n <node>] <command> [<arg> ...] + io:format("Usage: rabbitmqctl [-q] [-n <node>] <command> [<arg> ...] Available commands: @@ -89,6 +113,9 @@ Available commands: list_user_vhosts <UserName> list_vhost_users <VHostPath> +Quiet output mode is selected with the \"-q\" flag. Informational messages +are suppressed when quiet mode is in effect. + <node> should be the name of the master node of the RabbitMQ cluster. It defaults to the node named \"rabbit\" on the local host. On a host named \"server.example.com\", the master node will usually be rabbit@server (unless @@ -98,95 +125,95 @@ output of hostname -s is usually the correct suffix to use after the \"@\" sign. "), halt(1). -action(stop, Node, []) -> - io:format("Stopping and halting node ~p ...", [Node]), +action(stop, Node, [], Inform) -> + Inform("Stopping and halting node ~p", [Node]), call(Node, {rabbit, stop_and_halt, []}); -action(stop_app, Node, []) -> - io:format("Stopping node ~p ...", [Node]), +action(stop_app, Node, [], Inform) -> + Inform("Stopping node ~p", [Node]), call(Node, {rabbit, stop, []}); -action(start_app, Node, []) -> - io:format("Starting node ~p ...", [Node]), +action(start_app, Node, [], Inform) -> + Inform("Starting node ~p", [Node]), call(Node, {rabbit, start, []}); -action(reset, Node, []) -> - io:format("Resetting node ~p ...", [Node]), +action(reset, Node, [], Inform) -> + Inform("Resetting node ~p", [Node]), call(Node, {rabbit_mnesia, reset, []}); -action(force_reset, Node, []) -> - io:format("Forcefully resetting node ~p ...", [Node]), +action(force_reset, Node, [], Inform) -> + Inform("Forcefully resetting node ~p", [Node]), call(Node, {rabbit_mnesia, force_reset, []}); -action(cluster, Node, ClusterNodeSs) -> +action(cluster, Node, ClusterNodeSs, Inform) -> ClusterNodes = lists:map(fun list_to_atom/1, ClusterNodeSs), - io:format("Clustering node ~p with ~p ...", + Inform("Clustering node ~p with ~p", [Node, ClusterNodes]), rpc_call(Node, rabbit_mnesia, cluster, [ClusterNodes]); -action(status, Node, []) -> - io:format("Status of node ~p ...", [Node]), +action(status, Node, [], Inform) -> + Inform("Status of node ~p", [Node]), Res = call(Node, {rabbit, status, []}), - io:format("~n~p~n", [Res]), + io:format("~p~n", [Res]), ok; -action(rotate_logs, Node, []) -> - io:format("Reopening logs for node ~p ...", [Node]), +action(rotate_logs, Node, [], Inform) -> + Inform("Reopening logs for node ~p", [Node]), call(Node, {rabbit, rotate_logs, [""]}); -action(rotate_logs, Node, Args = [Suffix]) -> - io:format("Rotating logs to files with suffix ~p ...", [Suffix]), +action(rotate_logs, Node, Args = [Suffix], Inform) -> + Inform("Rotating logs to files with suffix ~p", [Suffix]), call(Node, {rabbit, rotate_logs, Args}); -action(add_user, Node, Args = [Username, _Password]) -> - io:format("Creating user ~p ...", [Username]), +action(add_user, Node, Args = [Username, _Password], Inform) -> + Inform("Creating user ~p", [Username]), call(Node, {rabbit_access_control, add_user, Args}); -action(delete_user, Node, Args = [_Username]) -> - io:format("Deleting user ~p ...", Args), +action(delete_user, Node, Args = [_Username], Inform) -> + Inform("Deleting user ~p", Args), call(Node, {rabbit_access_control, delete_user, Args}); -action(change_password, Node, Args = [Username, _Newpassword]) -> - io:format("Changing password for user ~p ...", [Username]), +action(change_password, Node, Args = [Username, _Newpassword], Inform) -> + Inform("Changing password for user ~p", [Username]), call(Node, {rabbit_access_control, change_password, Args}); -action(list_users, Node, []) -> - io:format("Listing users ..."), +action(list_users, Node, [], Inform) -> + Inform("Listing users", []), display_list(call(Node, {rabbit_access_control, list_users, []})); -action(add_vhost, Node, Args = [_VHostPath]) -> - io:format("Creating vhost ~p ...", Args), +action(add_vhost, Node, Args = [_VHostPath], Inform) -> + Inform("Creating vhost ~p", Args), call(Node, {rabbit_access_control, add_vhost, Args}); -action(delete_vhost, Node, Args = [_VHostPath]) -> - io:format("Deleting vhost ~p ...", Args), +action(delete_vhost, Node, Args = [_VHostPath], Inform) -> + Inform("Deleting vhost ~p", Args), call(Node, {rabbit_access_control, delete_vhost, Args}); -action(list_vhosts, Node, []) -> - io:format("Listing vhosts ..."), +action(list_vhosts, Node, [], Inform) -> + Inform("Listing vhosts", []), display_list(call(Node, {rabbit_access_control, list_vhosts, []})); -action(map_user_vhost, Node, Args = [_Username, _VHostPath]) -> - io:format("Mapping user ~p to vhost ~p ...", Args), +action(map_user_vhost, Node, Args = [_Username, _VHostPath], Inform) -> + Inform("Mapping user ~p to vhost ~p", Args), call(Node, {rabbit_access_control, map_user_vhost, Args}); -action(unmap_user_vhost, Node, Args = [_Username, _VHostPath]) -> - io:format("Unmapping user ~p from vhost ~p ...", Args), +action(unmap_user_vhost, Node, Args = [_Username, _VHostPath], Inform) -> + Inform("Unmapping user ~p from vhost ~p", Args), call(Node, {rabbit_access_control, unmap_user_vhost, Args}); -action(list_user_vhosts, Node, Args = [_Username]) -> - io:format("Listing vhosts for user ~p...", Args), +action(list_user_vhosts, Node, Args = [_Username], Inform) -> + Inform("Listing vhosts for user ~p", Args), display_list(call(Node, {rabbit_access_control, list_user_vhosts, Args})); -action(list_vhost_users, Node, Args = [_VHostPath]) -> - io:format("Listing users for vhosts ~p...", Args), +action(list_vhost_users, Node, Args = [_VHostPath], Inform) -> + Inform("Listing users for vhosts ~p", Args), display_list(call(Node, {rabbit_access_control, list_vhost_users, Args})). display_list(L) when is_list(L) -> lists:foreach(fun (I) -> - io:format("~n~s", [binary_to_list(I)]) + io:format("~s~n", [binary_to_list(I)]) end, lists:sort(L)), - io:nl(); + ok; display_list(Other) -> Other. call(Node, {Mod, Fun, Args}) -> diff --git a/src/rabbit_error_logger_file_h.erl b/src/rabbit_error_logger_file_h.erl index d67b02ef42..7f7f77f5e1 100644 --- a/src/rabbit_error_logger_file_h.erl +++ b/src/rabbit_error_logger_file_h.erl @@ -53,9 +53,11 @@ init({{File, _}, error}) -> %% log rotation init({File, []}) -> init(File); -init({_File, _Type} = FileInfo) -> +init({File, _Type} = FileInfo) -> + rabbit_misc:ensure_parent_dirs_exist(File), error_logger_file_h:init(FileInfo); init(File) -> + rabbit_misc:ensure_parent_dirs_exist(File), error_logger_file_h:init(File). handle_event(Event, State) -> diff --git a/src/rabbit_heartbeat.erl b/src/rabbit_heartbeat.erl index 6e825578d3..b05a4655e5 100644 --- a/src/rabbit_heartbeat.erl +++ b/src/rabbit_heartbeat.erl @@ -67,7 +67,7 @@ heartbeater(Sock, TimeoutMillisec, StatName, Threshold, Handler, MonitorRef) -> {'DOWN', MonitorRef, process, _Object, _Info} -> ok; Other -> exit({unexpected_message, Other}) after TimeoutMillisec -> - case prim_inet:getstat(Sock, [StatName]) of + case inet:getstat(Sock, [StatName]) of {ok, [{StatName, NewStatVal}]} -> if NewStatVal =/= StatVal -> F({NewStatVal, 0}); diff --git a/src/rabbit_load.erl b/src/rabbit_load.erl index 8deec8ebee..2154bf72ae 100644 --- a/src/rabbit_load.erl +++ b/src/rabbit_load.erl @@ -34,11 +34,11 @@ -ifdef(use_specs). --type(node() :: atom()). --type(load() :: {{non_neg_integer(), float()}, node()}). +-type(erlang_node() :: atom()). +-type(load() :: {{non_neg_integer(), float()}, erlang_node()}). -spec(local_load/0 :: () -> load()). -spec(remote_loads/0 :: () -> [load()]). --spec(pick/0 :: () -> node()). +-spec(pick/0 :: () -> erlang_node()). -endif. diff --git a/src/rabbit_log.erl b/src/rabbit_log.erl index a8f839f0a6..b4729230e6 100644 --- a/src/rabbit_log.erl +++ b/src/rabbit_log.erl @@ -32,7 +32,7 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([debug/1, debug/2, info/1, info/2, +-export([debug/1, debug/2, message/4, info/1, info/2, warning/1, warning/2, error/1, error/2]). -import(io). @@ -67,6 +67,10 @@ debug(Fmt) -> debug(Fmt, Args) when is_list(Args) -> gen_server:cast(?SERVER, {debug, Fmt, Args}). +message(Direction, Channel, MethodRecord, Content) -> + gen_server:cast(?SERVER, + {message, Direction, Channel, MethodRecord, Content}). + info(Fmt) -> gen_server:cast(?SERVER, {info, Fmt}). @@ -100,6 +104,14 @@ handle_cast({debug, Fmt, Args}, State) -> io:format("debug:: "), io:format(Fmt, Args), error_logger:info_msg("debug:: " ++ Fmt, Args), {noreply, State}; +handle_cast({message, Direction, Channel, MethodRecord, Content}, State) -> + io:format("~s ch~p ~p~n", + [case Direction of + in -> "-->"; + out -> "<--" end, + Channel, + {MethodRecord, Content}]), + {noreply, State}; handle_cast({info, Fmt}, State) -> error_logger:info_msg(Fmt), {noreply, State}; diff --git a/src/rabbit_memsup_linux.erl b/src/rabbit_memsup_linux.erl new file mode 100644 index 0000000000..b77ffcabb4 --- /dev/null +++ b/src/rabbit_memsup_linux.erl @@ -0,0 +1,150 @@ +%% 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 +%% http://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 Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_memsup_linux). + +-behaviour(gen_server). + +-export([start_link/0]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-export([update/0]). + +-define(SERVER, memsup). %% must be the same as the standard memsup + +-define(DEFAULT_MEMORY_CHECK_INTERVAL, 1000). + +-record(state, {memory_fraction, alarmed, timeout, timer}). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-spec(start_link/0 :: () -> {'ok', pid()} | 'ignore' | {'error', any()}). +-spec(update/0 :: () -> 'ok'). + +-endif. + +%%---------------------------------------------------------------------------- + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + + +update() -> + gen_server:cast(?SERVER, update). + +%%---------------------------------------------------------------------------- + +init(_Args) -> + Fraction = os_mon:get_env(memsup, system_memory_high_watermark), + TRef = start_timer(?DEFAULT_MEMORY_CHECK_INTERVAL), + {ok, #state{alarmed = false, + memory_fraction = Fraction, + timeout = ?DEFAULT_MEMORY_CHECK_INTERVAL, + timer = TRef}}. + +start_timer(Timeout) -> + {ok, TRef} = timer:apply_interval(Timeout, ?MODULE, update, []), + TRef. + +%% Export the same API as the real memsup. Note that +%% get_sysmem_high_watermark gives an int in the range 0 - 100, while +%% set_sysmem_high_watermark takes a float in the range 0.0 - 1.0. +handle_call(get_sysmem_high_watermark, _From, State) -> + {reply, trunc(100 * State#state.memory_fraction), State}; + +handle_call({set_sysmem_high_watermark, Float}, _From, State) -> + {reply, ok, State#state{memory_fraction = Float}}; + +handle_call(get_check_interval, _From, State) -> + {reply, State#state.timeout, State}; + +handle_call({set_check_interval, Timeout}, _From, State) -> + {ok, cancel} = timer:cancel(State#state.timer), + {reply, ok, State#state{timeout = Timeout, timer = start_timer(Timeout)}}; + +handle_call(_Request, _From, State) -> + {noreply, State}. + +handle_cast(update, State = #state{alarmed = Alarmed, + memory_fraction = MemoryFraction}) -> + File = read_proc_file("/proc/meminfo"), + Lines = string:tokens(File, "\n"), + Dict = dict:from_list(lists:map(fun parse_line/1, Lines)), + MemTotal = dict:fetch('MemTotal', Dict), + MemUsed = MemTotal + - dict:fetch('MemFree', Dict) + - dict:fetch('Buffers', Dict) + - dict:fetch('Cached', Dict), + NewAlarmed = MemUsed / MemTotal > MemoryFraction, + case {Alarmed, NewAlarmed} of + {false, true} -> + alarm_handler:set_alarm({system_memory_high_watermark, []}); + {true, false} -> + alarm_handler:clear_alarm(system_memory_high_watermark); + _ -> + ok + end, + {noreply, State#state{alarmed = NewAlarmed}}; + +handle_cast(_Request, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%---------------------------------------------------------------------------- + +-define(BUFFER_SIZE, 1024). + +%% file:read_file does not work on files in /proc as it seems to get +%% the size of the file first and then read that many bytes. But files +%% in /proc always have length 0, we just have to read until we get +%% eof. +read_proc_file(File) -> + {ok, IoDevice} = file:open(File, [read, raw]), + Res = read_proc_file(IoDevice, []), + file:close(IoDevice), + lists:flatten(lists:reverse(Res)). + +read_proc_file(IoDevice, Acc) -> + case file:read(IoDevice, ?BUFFER_SIZE) of + {ok, Res} -> read_proc_file(IoDevice, [Res | Acc]); + eof -> Acc + end. + +%% A line looks like "FooBar: 123456 kB" +parse_line(Line) -> + [Name, Value | _] = string:tokens(Line, ": "), + {list_to_atom(Name), list_to_integer(Value)}. diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index 7638af582d..6be73e43a1 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -42,7 +42,8 @@ -export([intersperse/2, upmap/2, map_in_order/2]). -export([guid/0, string_guid/1, binstring_guid/1]). -export([dirty_read_all/1, dirty_foreach_key/2, dirty_dump_log/1]). --export([append_file/2]). +-export([append_file/2, ensure_parent_dirs_exist/1]). +-export([format_stderr/2]). -import(mnesia). -import(lists). @@ -85,7 +86,7 @@ -spec(with_user_and_vhost/3 :: (username(), vhost(), thunk(A)) -> A). -spec(execute_mnesia_transaction/1 :: (thunk(A)) -> A). -spec(ensure_ok/2 :: ('ok' | {'error', any()}, atom()) -> 'ok'). --spec(localnode/1 :: (atom()) -> node()). +-spec(localnode/1 :: (atom()) -> erlang_node()). -spec(tcp_name/3 :: (atom(), ip_address(), ip_port()) -> atom()). -spec(intersperse/2 :: (A, [A]) -> [A]). -spec(upmap/2 :: (fun ((A) -> B), [A]) -> [B]). @@ -98,6 +99,8 @@ 'ok' | 'aborted'). -spec(dirty_dump_log/1 :: (string()) -> 'ok' | {'error', any()}). -spec(append_file/2 :: (string(), string()) -> 'ok' | {'error', any()}). +-spec(ensure_parent_dirs_exist/1 :: (string()) -> 'ok'). +-spec(format_stderr/2 :: (string(), [any()]) -> 'true'). -endif. @@ -369,3 +372,15 @@ append_file(File, _, Suffix) -> {ok, Data} -> file:write_file([File, Suffix], Data, [append]); Error -> Error end. + +ensure_parent_dirs_exist(Filename) -> + case filelib:ensure_dir(Filename) of + ok -> ok; + {error, Reason} -> + throw({error, {cannot_create_parent_dirs, Filename, Reason}}) + end. + +format_stderr(Fmt, Args) -> + Port = open_port({fd, 0, 2}, [out]), + port_command(Port, io_lib:format(Fmt, Args)), + port_close(Port). diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl index 9b67135def..ca8b587846 100644 --- a/src/rabbit_mnesia.erl +++ b/src/rabbit_mnesia.erl @@ -30,6 +30,9 @@ -export([table_names/0]). +%% Called by rabbitmq-mnesia-current script +-export([schema_current/0]). + %% create_tables/0 exported for helping embed RabbitMQ in or alongside %% other mnesia-using Erlang applications, such as ejabberd -export([create_tables/0]). @@ -40,14 +43,15 @@ -ifdef(use_specs). --spec(status/0 :: () -> [{'nodes' | 'running_nodes', [node()]}]). +-spec(status/0 :: () -> [{'nodes' | 'running_nodes', [erlang_node()]}]). -spec(ensure_mnesia_dir/0 :: () -> 'ok'). -spec(init/0 :: () -> 'ok'). -spec(is_db_empty/0 :: () -> bool()). --spec(cluster/1 :: ([node()]) -> 'ok'). +-spec(cluster/1 :: ([erlang_node()]) -> 'ok'). -spec(reset/0 :: () -> 'ok'). -spec(force_reset/0 :: () -> 'ok'). -spec(create_tables/0 :: () -> 'ok'). +-spec(schema_current/0 :: () -> bool()). -endif. @@ -91,6 +95,20 @@ cluster(ClusterNodes) -> reset() -> reset(false). force_reset() -> reset(true). +%% This is invoked by rabbitmq-mnesia-current. +schema_current() -> + application:start(mnesia), + ok = ensure_mnesia_running(), + ok = ensure_mnesia_dir(), + ok = init_db(read_cluster_nodes_config()), + try + ensure_schema_integrity(), + true + catch + {error, {schema_integrity_check_failed, _Reason}} -> + false + end. + %%-------------------------------------------------------------------- table_definitions() -> diff --git a/src/rabbit_multi.erl b/src/rabbit_multi.erl index c6a7e9200d..b99dfbc1c1 100644 --- a/src/rabbit_multi.erl +++ b/src/rabbit_multi.erl @@ -46,18 +46,22 @@ start() -> io:format("done.~n"), init:stop(); {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} -> - io:format("Invalid command ~p~n", [FullCommand]), + error("invalid command '~s'", + [lists:flatten( + rabbit_misc:intersperse(" ", FullCommand))]), usage(); timeout -> - io:format("timeout starting some nodes.~n"), + error("timeout starting some nodes.", []), halt(1); Other -> - io:format("~nrabbit_multi action ~p failed:~n~p~n", - [Command, Other]), + error("~p", [Other]), halt(2) end end. +error(Format, Args) -> + rabbit_misc:format_stderr("Error: " ++ Format ++ "~n", Args). + parse_args([Command | Args]) -> {list_to_atom(Command), Args}. @@ -223,8 +227,7 @@ write_pids_file(Pids) -> {ok, Device} -> Device; {error, Reason} -> - throw({error, {cannot_create_pids_file, - FileName, Reason}}) + throw({cannot_create_pids_file, FileName, Reason}) end, try ok = io:write(Handle, Pids), @@ -233,8 +236,7 @@ write_pids_file(Pids) -> case file:close(Handle) of ok -> ok; {error, Reason1} -> - throw({error, {cannot_create_pids_file, - FileName, Reason1}}) + throw({cannot_create_pids_file, FileName, Reason1}) end end, ok. @@ -244,8 +246,7 @@ delete_pids_file() -> case file:delete(FileName) of ok -> ok; {error, enoent} -> ok; - {error, Reason} -> throw({error, {cannot_delete_pids_file, - FileName, Reason}}) + {error, Reason} -> throw({cannot_delete_pids_file, FileName, Reason}) end. read_pids_file() -> @@ -253,8 +254,7 @@ read_pids_file() -> case file:consult(FileName) of {ok, [Pids]} -> Pids; {error, enoent} -> []; - {error, Reason} -> throw({error, {cannot_read_pids_file, - FileName, Reason}}) + {error, Reason} -> throw({cannot_read_pids_file, FileName, Reason}) end. kill_wait(Pid, TimeLeft, Forceful) when TimeLeft < 0 -> diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl index 79c927cb9e..a91602abb2 100644 --- a/src/rabbit_networking.erl +++ b/src/rabbit_networking.erl @@ -45,8 +45,8 @@ -spec(start_tcp_listener/2 :: (host(), ip_port()) -> 'ok'). -spec(stop_tcp_listener/2 :: (host(), ip_port()) -> 'ok'). -spec(active_listeners/0 :: () -> [listener()]). --spec(node_listeners/1 :: (node()) -> [listener()]). --spec(on_node_down/1 :: (node()) -> 'ok'). +-spec(node_listeners/1 :: (erlang_node()) -> [listener()]). +-spec(on_node_down/1 :: (erlang_node()) -> 'ok'). -spec(check_tcp_listener_address/3 :: (atom(), host(), ip_port()) -> {ip_address(), atom()}). diff --git a/src/rabbit_router.erl b/src/rabbit_router.erl index a233764766..58eb5b54f6 100644 --- a/src/rabbit_router.erl +++ b/src/rabbit_router.erl @@ -36,6 +36,9 @@ -define(SERVER, ?MODULE). +%% cross-node routing optimisation is disabled because of bug 19758. +-define(BUG19758, true). + %%---------------------------------------------------------------------------- -ifdef(use_specs). @@ -51,6 +54,14 @@ start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). +-ifdef(BUG19758). + +deliver(QPids, Mandatory, Immediate, Txn, Message) -> + check_delivery(Mandatory, Immediate, + run_bindings(QPids, Mandatory, Immediate, Txn, Message)). + +-else. + deliver(QPids, Mandatory, Immediate, Txn, Message) -> %% we reduce inter-node traffic by grouping the qpids by node and %% only delivering one copy of the message to each node involved, @@ -114,6 +125,8 @@ deliver_per_node(NodeQPids, Mandatory, Immediate, R), check_delivery(Mandatory, Immediate, {Routed, lists:append(Handled)}). +-endif. + %%-------------------------------------------------------------------- init([]) -> diff --git a/src/rabbit_sasl_report_file_h.erl b/src/rabbit_sasl_report_file_h.erl index 3374d63d0b..e2385d83ba 100644 --- a/src/rabbit_sasl_report_file_h.erl +++ b/src/rabbit_sasl_report_file_h.erl @@ -54,9 +54,11 @@ init({{File, _}, error}) -> %% doing any log rotation init({File, []}) -> init(File); -init({_File, _Type} = FileInfo) -> +init({File, _Type} = FileInfo) -> + rabbit_misc:ensure_parent_dirs_exist(File), sasl_report_file_h:init(FileInfo); init(File) -> + rabbit_misc:ensure_parent_dirs_exist(File), sasl_report_file_h:init({File, sasl_error_logger_type()}). handle_event(Event, State) -> @@ -71,8 +73,9 @@ handle_call(Event, State) -> terminate(Reason, State) -> sasl_report_file_h:terminate(Reason, State). -code_change(OldVsn, State, Extra) -> - sasl_report_file_h:code_change(OldVsn, State, Extra). +code_change(_OldVsn, State, _Extra) -> + %% There is no sasl_report_file_h:code_change/3 + {ok, State}. %%---------------------------------------------------------------------- diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl index fff02d7363..d7c8bddf03 100644 --- a/src/rabbit_tests.erl +++ b/src/rabbit_tests.erl @@ -131,6 +131,8 @@ test_topic_matching() -> passed. test_app_management() -> + true = rabbit_mnesia:schema_current(), + %% starting, stopping, status ok = control_action(stop_app, []), ok = control_action(stop_app, []), @@ -236,25 +238,51 @@ test_log_management_during_startup() -> ok = error_logger:tty(false), ok = delete_log_handlers([sasl_report_tty_h]), ok = case catch control_action(start_app, []) of - ok -> exit(got_success_but_expected_failure); - {error, {cannot_log_to_tty, _, _}} -> ok - end, + ok -> exit({got_success_but_expected_failure, + log_rotation_tty_no_handlers_test}); + {error, {cannot_log_to_tty, _, _}} -> ok + end, %% fix sasl logging ok = application:set_env(sasl, sasl_error_logger, {file, SaslLog}), - %% start application with logging to invalid directory + %% start application with logging to non-existing directory TmpLog = "/tmp/rabbit-tests/test.log", - file:delete(TmpLog), + delete_file(TmpLog), ok = application:set_env(kernel, error_logger, {file, TmpLog}), ok = delete_log_handlers([rabbit_error_logger_file_h]), ok = add_log_handlers([{error_logger_file_h, MainLog}]), - ok = case catch control_action(start_app, []) of - ok -> exit(got_success_but_expected_failure); - {error, {cannot_log_to_file, _, _}} -> ok - end, + ok = control_action(start_app, []), + + %% start application with logging to directory with no + %% write permissions + TmpDir = "/tmp/rabbit-tests", + ok = set_permissions(TmpDir, 8#00400), + ok = delete_log_handlers([rabbit_error_logger_file_h]), + ok = add_log_handlers([{error_logger_file_h, MainLog}]), + ok = case control_action(start_app, []) of + ok -> exit({got_success_but_expected_failure, + log_rotation_no_write_permission_dir_test}); + {error, {cannot_log_to_file, _, _}} -> ok + end, + + %% start application with logging to a subdirectory which + %% parent directory has no write permissions + TmpTestDir = "/tmp/rabbit-tests/no-permission/test/log", + ok = application:set_env(kernel, error_logger, {file, TmpTestDir}), + ok = add_log_handlers([{error_logger_file_h, MainLog}]), + ok = case control_action(start_app, []) of + ok -> exit({got_success_but_expected_failure, + log_rotatation_parent_dirs_test}); + {error, {cannot_log_to_file, _, + {error, {cannot_create_parent_dirs, _, eacces}}}} -> ok + end, + ok = set_permissions(TmpDir, 8#00700), + ok = set_permissions(TmpLog, 8#00600), + ok = delete_file(TmpLog), + ok = file:del_dir(TmpDir), %% start application with standard error_logger_file_h %% handler not installed @@ -465,7 +493,7 @@ test_user_management() -> control_action(Command, Args) -> control_action(Command, node(), Args). control_action(Command, Node, Args) -> - case catch rabbit_control:action(Command, Node, Args) of + case catch rabbit_control:action(Command, Node, Args, fun io:format/2) of ok -> io:format("done.~n"), ok; @@ -494,6 +522,14 @@ test_logs_working(MainLogFile, SaslLogFile) -> [true, true] = non_empty_files([MainLogFile, SaslLogFile]), ok. +set_permissions(Path, Mode) -> + case file:read_file_info(Path) of + {ok, FInfo} -> file:write_file_info( + Path, + FInfo#file_info{mode=Mode}); + Error -> Error + end. + clean_logs(Files, Suffix) -> [begin ok = delete_file(File), |
