diff options
author | Tim Watson <tim@rabbitmq.com> | 2014-04-16 12:28:03 +0100 |
---|---|---|
committer | Tim Watson <tim@rabbitmq.com> | 2014-04-16 12:28:03 +0100 |
commit | d7bb98936706a03c5960f31500e0b2b4c0e303fe (patch) | |
tree | e6ccd9ff1fe795ea66f960c3e3bf4663b3731e9d | |
parent | c1a0b20ba7771e092612880d0e3f3b42287006a7 (diff) | |
parent | 8fc94c42d3cf8b6da16649875efafe54f1272ec3 (diff) | |
download | rabbitmq-server-d7bb98936706a03c5960f31500e0b2b4c0e303fe.tar.gz |
Merge default into bug24926
-rw-r--r-- | docs/rabbitmq-plugins.1.xml | 14 | ||||
-rwxr-xr-x | scripts/rabbitmq-plugins | 2 | ||||
-rwxr-xr-x | scripts/rabbitmq-plugins.bat | 6 | ||||
-rw-r--r-- | src/app_utils.erl | 7 | ||||
-rw-r--r-- | src/rabbit.erl | 117 | ||||
-rw-r--r-- | src/rabbit_event.erl | 24 | ||||
-rw-r--r-- | src/rabbit_misc.erl | 25 | ||||
-rw-r--r-- | src/rabbit_plugins.erl | 44 | ||||
-rw-r--r-- | src/rabbit_plugins_main.erl | 194 | ||||
-rw-r--r-- | src/rabbit_version.erl | 9 |
10 files changed, 318 insertions, 124 deletions
diff --git a/docs/rabbitmq-plugins.1.xml b/docs/rabbitmq-plugins.1.xml index 8ecb4fc8..3b67d0e6 100644 --- a/docs/rabbitmq-plugins.1.xml +++ b/docs/rabbitmq-plugins.1.xml @@ -97,12 +97,14 @@ </variablelist> <para> Lists all plugins, their versions, dependencies and - descriptions. Each plugin is prefixed with a status - indicator - [ ] to indicate that the plugin is not - enabled, [E] to indicate that it is explicitly enabled, - [e] to indicate that it is implicitly enabled, and [!] to - indicate that it is enabled but missing and thus not - operational. + descriptions. Each plugin is prefixed with two status + indicator characters inside [ ]. The first indicator can + be " " to indicate that the plugin is not enabled, "E" to + indicate that it is explicitly enabled, "e" to indicate + that it is implicitly enabled, or "!" to indicate that it + is enabled but missing and thus not operational. The + second indicator can be " " to show that the plugin is not + running, or "*" to show that it is. </para> <para> If the optional pattern is given, only plugins whose diff --git a/scripts/rabbitmq-plugins b/scripts/rabbitmq-plugins index 2ec45be0..2213e20b 100755 --- a/scripts/rabbitmq-plugins +++ b/scripts/rabbitmq-plugins @@ -21,6 +21,7 @@ ##--- Set environment vars RABBITMQ_<var_name> to defaults if not set +[ "x" = "x$RABBITMQ_NODENAME" ] && RABBITMQ_NODENAME=${NODENAME} [ "x" = "x$RABBITMQ_ENABLED_PLUGINS_FILE" ] && RABBITMQ_ENABLED_PLUGINS_FILE=${ENABLED_PLUGINS_FILE} [ "x" = "x$RABBITMQ_PLUGINS_DIR" ] && RABBITMQ_PLUGINS_DIR=${PLUGINS_DIR} @@ -35,4 +36,5 @@ exec ${ERL_DIR}erl \ -s rabbit_plugins_main \ -enabled_plugins_file "$RABBITMQ_ENABLED_PLUGINS_FILE" \ -plugins_dist_dir "$RABBITMQ_PLUGINS_DIR" \ + -nodename $RABBITMQ_NODENAME \ -extra "$@" diff --git a/scripts/rabbitmq-plugins.bat b/scripts/rabbitmq-plugins.bat index a535ebad..61e39e38 100755 --- a/scripts/rabbitmq-plugins.bat +++ b/scripts/rabbitmq-plugins.bat @@ -31,6 +31,10 @@ if "!RABBITMQ_BASE!"=="" ( set RABBITMQ_BASE=!APPDATA!\!RABBITMQ_SERVICENAME!
)
+if "!RABBITMQ_NODENAME!"=="" (
+ set RABBITMQ_NODENAME=rabbit@!COMPUTERNAME!
+)
+
if not exist "!ERLANG_HOME!\bin\erl.exe" (
echo.
echo ******************************
@@ -51,7 +55,7 @@ if "!RABBITMQ_PLUGINS_DIR!"=="" ( set RABBITMQ_PLUGINS_DIR=!TDP0!..\plugins
)
-"!ERLANG_HOME!\bin\erl.exe" -pa "!TDP0!..\ebin" -noinput -hidden -sname rabbitmq-plugins!RANDOM!!TIME:~9! -s rabbit_plugins_main -enabled_plugins_file "!RABBITMQ_ENABLED_PLUGINS_FILE!" -plugins_dist_dir "!RABBITMQ_PLUGINS_DIR:\=/!" -extra !STAR!
+"!ERLANG_HOME!\bin\erl.exe" -pa "!TDP0!..\ebin" -noinput -hidden -sname rabbitmq-plugins!RANDOM!!TIME:~9! -s rabbit_plugins_main -enabled_plugins_file "!RABBITMQ_ENABLED_PLUGINS_FILE!" -plugins_dist_dir "!RABBITMQ_PLUGINS_DIR:\=/!" -nodename !RABBITMQ_NODENAME! -extra !STAR!
endlocal
endlocal
diff --git a/src/app_utils.erl b/src/app_utils.erl index 0479ce66..e321888d 100644 --- a/src/app_utils.erl +++ b/src/app_utils.erl @@ -17,7 +17,7 @@ -export([load_applications/1, start_applications/1, start_applications/2, stop_applications/1, stop_applications/2, app_dependency_order/2, - wait_for_applications/1]). + wait_for_applications/1, app_dependencies/1]). -ifdef(use_specs). @@ -30,6 +30,7 @@ -spec stop_applications([atom()], error_handler()) -> 'ok'. -spec wait_for_applications([atom()]) -> 'ok'. -spec app_dependency_order([atom()], boolean()) -> [digraph:vertex()]. +-spec app_dependencies(atom()) -> [atom()]. -endif. @@ -74,8 +75,8 @@ wait_for_applications(Apps) -> app_dependency_order(RootApps, StripUnreachable) -> {ok, G} = rabbit_misc:build_acyclic_graph( - fun (App, _Deps) -> [{App, App}] end, - fun (App, Deps) -> [{Dep, App} || Dep <- Deps] end, + fun ({App, _Deps}) -> [{App, App}] end, + fun ({App, Deps}) -> [{Dep, App} || Dep <- Deps] end, [{App, app_dependencies(App)} || {App, _Desc, _Vsn} <- application:loaded_applications()]), try diff --git a/src/rabbit.erl b/src/rabbit.erl index c2d7e29d..8a682616 100644 --- a/src/rabbit.erl +++ b/src/rabbit.erl @@ -22,9 +22,8 @@ stop_and_halt/0, await_startup/0, status/0, is_running/0, is_running/1, environment/0, rotate_logs/1, force_event_refresh/1, start_fhc/0]). - -export([start/2, stop/1]). - +-export([start_apps/1, stop_apps/1]). -export([log_location/1]). %% for testing %%--------------------------------------------------------------------------- @@ -211,6 +210,7 @@ %% this really should be an abstract type -type(log_location() :: 'tty' | 'undefined' | file:filename()). -type(param() :: atom()). +-type(app_name() :: atom()). -spec(start/0 :: () -> 'ok'). -spec(boot/0 :: () -> 'ok'). @@ -242,6 +242,8 @@ -spec(maybe_insert_default_data/0 :: () -> 'ok'). -spec(boot_delegate/0 :: () -> 'ok'). -spec(recover/0 :: () -> 'ok'). +-spec(start_apps/1 :: ([app_name()]) -> 'ok'). +-spec(stop_apps/1 :: ([app_name()]) -> 'ok'). -endif. @@ -312,9 +314,7 @@ start() -> ok = ensure_working_log_handlers(), rabbit_node_monitor:prepare_cluster_status_files(), rabbit_mnesia:check_cluster_consistency(), - ok = app_utils:start_applications( - app_startup_order(), fun handle_app_error/2), - ok = log_broker_started(rabbit_plugins:active()) + broker_start() end). boot() -> @@ -329,21 +329,31 @@ boot() -> %% the upgrade, since if we are a secondary node the %% primary node will have forgotten us rabbit_mnesia:check_cluster_consistency(), - Plugins = rabbit_plugins:setup(), - ToBeLoaded = Plugins ++ ?APPS, - ok = app_utils:load_applications(ToBeLoaded), - StartupApps = app_utils:app_dependency_order(ToBeLoaded, - false), - ok = app_utils:start_applications( - StartupApps, fun handle_app_error/2), - ok = log_broker_started(Plugins) + broker_start() end). -handle_app_error(App, {bad_return, {_MFA, {'EXIT', {Reason, _}}}}) -> - throw({could_not_start, App, Reason}); +broker_start() -> + Plugins = rabbit_plugins:setup(), + ToBeLoaded = Plugins ++ ?APPS, + start_apps(ToBeLoaded), + ok = log_broker_started(rabbit_plugins:active()). + +handle_app_error(Term) -> + fun(App, {bad_return, {_MFA, {'EXIT', {ExitReason, _}}}}) -> + throw({Term, App, ExitReason}); + (App, Reason) -> + throw({Term, App, Reason}) + end. -handle_app_error(App, Reason) -> - throw({could_not_start, App, Reason}). +start_apps(Apps) -> + app_utils:load_applications(Apps), + StartupApps = app_utils:app_dependency_order(Apps, false), + case whereis(rabbit_boot) of + undefined -> run_boot_steps(Apps); + _ -> ok + end, + ok = app_utils:start_applications(StartupApps, + handle_app_error(could_not_start)). start_it(StartFun) -> Marker = spawn_link(fun() -> receive stop -> ok end end), @@ -369,12 +379,13 @@ start_it(StartFun) -> end. stop() -> + Apps = app_shutdown_order(), case whereis(rabbit_boot) of undefined -> ok; - _ -> await_startup() + _ -> app_utils:wait_for_applications(Apps) end, rabbit_log:info("Stopping RabbitMQ~n"), - ok = app_utils:stop_applications(app_shutdown_order()). + ok = app_utils:stop_applications(Apps). stop_and_halt() -> try @@ -385,6 +396,29 @@ stop_and_halt() -> end, ok. +stop_apps(Apps) -> + try + ok = app_utils:stop_applications( + Apps, handle_app_error(error_during_shutdown)) + after + run_cleanup_steps(Apps), + [begin + {ok, Mods} = application:get_key(App, modules), + [begin + code:soft_purge(Mod), + code:delete(Mod), + false = code:is_loaded(Mod) + end || Mod <- Mods], + application:unload(App) + end || App <- Apps] + end. + +run_cleanup_steps(Apps) -> + [run_step(Name, Attributes, cleanup) || + {App, Name, Attributes} <- find_steps(Apps), + lists:member(App, Apps)], + ok. + await_startup() -> app_utils:wait_for_applications(app_startup_order()). @@ -468,7 +502,7 @@ start(normal, []) -> true = register(rabbit, self()), print_banner(), log_banner(), - [ok = run_boot_step(Step) || Step <- boot_steps()], + run_boot_steps(), {ok, SupPid}; Error -> Error @@ -496,29 +530,44 @@ app_shutdown_order() -> %%--------------------------------------------------------------------------- %% boot step logic -run_boot_step({_StepName, Attributes}) -> - case [MFA || {mfa, MFA} <- Attributes] of +run_boot_steps() -> + run_boot_steps([App || {App, _, _} <- application:loaded_applications()]). + +run_boot_steps(Apps) -> + Steps = find_steps(Apps), + [ok = run_step(StepName, Attributes, mfa) || + {_, StepName, Attributes} <- Steps], + ok. + +find_steps(BaseApps) -> + Apps = BaseApps -- [App || {App, _, _} <- rabbit_misc:which_applications()], + FullBoot = sort_boot_steps( + rabbit_misc:all_module_attributes(rabbit_boot_step)), + [Step || {App, _, _} = Step <- FullBoot, lists:member(App, Apps)]. + +run_step(StepName, Attributes, AttributeName) -> + case [MFA || {Key, MFA} <- Attributes, + Key =:= AttributeName] of [] -> ok; MFAs -> [try apply(M,F,A) of - ok -> ok; - {error, Reason} -> boot_error(Reason, not_available) + ok -> ok; + {error, Reason} -> boot_error({boot_step, StepName, Reason}, + not_available) catch - _:Reason -> boot_error(Reason, erlang:get_stacktrace()) + _:Reason -> boot_error({boot_step, StepName, Reason}, + erlang:get_stacktrace()) end || {M,F,A} <- MFAs], ok end. -boot_steps() -> - sort_boot_steps(rabbit_misc:all_module_attributes(rabbit_boot_step)). - -vertices(_Module, Steps) -> - [{StepName, {StepName, Atts}} || {StepName, Atts} <- Steps]. +vertices({AppName, _Module, Steps}) -> + [{StepName, {AppName, StepName, Atts}} || {StepName, Atts} <- Steps]. -edges(_Module, Steps) -> +edges({_AppName, _Module, Steps}) -> [case Key of requires -> {StepName, OtherStep}; enables -> {OtherStep, StepName} @@ -527,7 +576,7 @@ edges(_Module, Steps) -> Key =:= requires orelse Key =:= enables]. sort_boot_steps(UnsortedSteps) -> - case rabbit_misc:build_acyclic_graph(fun vertices/2, fun edges/2, + case rabbit_misc:build_acyclic_graph(fun vertices/1, fun edges/1, UnsortedSteps) of {ok, G} -> %% Use topological sort to find a consistent ordering (if @@ -541,8 +590,8 @@ sort_boot_steps(UnsortedSteps) -> digraph:delete(G), %% Check that all mentioned {M,F,A} triples are exported. case [{StepName, {M,F,A}} || - {StepName, Attributes} <- SortedSteps, - {mfa, {M,F,A}} <- Attributes, + {_App, StepName, Attributes} <- SortedSteps, + {mfa, {M,F,A}} <- Attributes, not erlang:function_exported(M, F, length(A))] of [] -> SortedSteps; MissingFunctions -> basic_boot_error( diff --git a/src/rabbit_event.erl b/src/rabbit_event.erl index b867223b..d3cd1a63 100644 --- a/src/rabbit_event.erl +++ b/src/rabbit_event.erl @@ -23,6 +23,7 @@ ensure_stats_timer/3, stop_stats_timer/2, reset_stats_timer/2]). -export([stats_level/2, if_enabled/3]). -export([notify/2, notify/3, notify_if/3]). +-export([sync_notify/2, sync_notify/3]). %%---------------------------------------------------------------------------- @@ -61,6 +62,9 @@ -spec(notify/2 :: (event_type(), event_props()) -> 'ok'). -spec(notify/3 :: (event_type(), event_props(), reference() | 'none') -> 'ok'). -spec(notify_if/3 :: (boolean(), event_type(), event_props()) -> 'ok'). +-spec(sync_notify/2 :: (event_type(), event_props()) -> 'ok'). +-spec(sync_notify/3 :: (event_type(), event_props(), + reference() | 'none') -> 'ok'). -endif. @@ -145,7 +149,19 @@ notify_if(false, _Type, _Props) -> ok. notify(Type, Props) -> notify(Type, Props, none). notify(Type, Props, Ref) -> - gen_event:notify(?MODULE, #event{type = Type, - props = Props, - reference = Ref, - timestamp = os:timestamp()}). + do_notify(notify, #event{type = Type, + props = Props, + reference = Ref, + timestamp = os:timestamp()}). + +sync_notify(Type, Props) -> sync_notify(Type, Props, none). + +sync_notify(Type, Props, Ref) -> + do_notify(sync_notify, #event{type = Type, + props = Props, + reference = Ref, + timestamp = os:timestamp()}). + +do_notify(F, Event) -> + apply(gen_event, F, [?MODULE, Event]). + diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl index 58e93a3f..2484a31a 100644 --- a/src/rabbit_misc.erl +++ b/src/rabbit_misc.erl @@ -209,7 +209,8 @@ [string()]) -> {'ok', {atom(), [{string(), string()}], [string()]}} | 'no_command'). --spec(all_module_attributes/1 :: (atom()) -> [{atom(), [term()]}]). +-spec(all_module_attributes/1 :: + (atom()) -> [{atom(), atom(), [term()]}]). -spec(build_acyclic_graph/3 :: (graph_vertex_fun(), graph_edge_fun(), [{atom(), [term()]}]) -> rabbit_types:ok_or_error2(digraph(), @@ -849,20 +850,20 @@ module_attributes(Module) -> end. all_module_attributes(Name) -> - Modules = + Targets = lists:usort( lists:append( - [Modules || {App, _, _} <- application:loaded_applications(), - {ok, Modules} <- [application:get_key(App, modules)]])), + [[{App, Module} || Module <- Modules] || + {App, _, _} <- application:loaded_applications(), + {ok, Modules} <- [application:get_key(App, modules)]])), lists:foldl( - fun (Module, Acc) -> + fun ({App, Module}, Acc) -> case lists:append([Atts || {N, Atts} <- module_attributes(Module), N =:= Name]) of [] -> Acc; - Atts -> [{Module, Atts} | Acc] + Atts -> [{App, Module, Atts} | Acc] end - end, [], Modules). - + end, [], Targets). build_acyclic_graph(VertexFun, EdgeFun, Graph) -> G = digraph:new([acyclic]), @@ -870,13 +871,13 @@ build_acyclic_graph(VertexFun, EdgeFun, Graph) -> [case digraph:vertex(G, Vertex) of false -> digraph:add_vertex(G, Vertex, Label); _ -> ok = throw({graph_error, {vertex, duplicate, Vertex}}) - end || {Module, Atts} <- Graph, - {Vertex, Label} <- VertexFun(Module, Atts)], + end || GraphElem <- Graph, + {Vertex, Label} <- VertexFun(GraphElem)], [case digraph:add_edge(G, From, To) of {error, E} -> throw({graph_error, {edge, E, From, To}}); _ -> ok - end || {Module, Atts} <- Graph, - {From, To} <- EdgeFun(Module, Atts)], + end || GraphElem <- Graph, + {From, To} <- EdgeFun(GraphElem)], {ok, G} catch {graph_error, Reason} -> true = digraph:delete(G), diff --git a/src/rabbit_plugins.erl b/src/rabbit_plugins.erl index c0fb05e2..2f10e0a0 100644 --- a/src/rabbit_plugins.erl +++ b/src/rabbit_plugins.erl @@ -18,6 +18,7 @@ -include("rabbit.hrl"). -export([setup/0, active/0, read_enabled/1, list/1, dependencies/3]). +-export([enable/1, disable/1]). %%---------------------------------------------------------------------------- @@ -31,17 +32,38 @@ -spec(read_enabled/1 :: (file:filename()) -> [plugin_name()]). -spec(dependencies/3 :: (boolean(), [plugin_name()], [#plugin{}]) -> [plugin_name()]). - +-spec(enable/1 :: ([plugin_name()]) -> 'ok'). +-spec(disable/1 :: ([plugin_name()]) -> 'ok'). -endif. %%---------------------------------------------------------------------------- +enable(Plugins) -> + prepare_plugins(Plugins), + rabbit:start_apps(Plugins), + ok = rabbit_event:notify(plugins_changed, [{enabled, Plugins}]). + +disable(Plugins) -> + RunningApps = rabbit_misc:which_applications(), + ToDisable = [P || P <- Plugins, + proplists:is_defined(P, RunningApps)], + ok = rabbit_event:sync_notify(plugins_changed, [{disabled, ToDisable}]), + rabbit:stop_apps(ToDisable). + %% @doc Prepares the file system and installs all enabled plugins. setup() -> - {ok, PluginDir} = application:get_env(rabbit, plugins_dir), {ok, ExpandDir} = application:get_env(rabbit, plugins_expand_dir), + + %% Eliminate the contents of the destination directory + case delete_recursively(ExpandDir) of + ok -> ok; + {error, E1} -> throw({error, {cannot_delete_plugins_expand_dir, + [ExpandDir, E1]}}) + end, + {ok, EnabledFile} = application:get_env(rabbit, enabled_plugins_file), - prepare_plugins(EnabledFile, PluginDir, ExpandDir). + Enabled = read_enabled(EnabledFile), + prepare_plugins(Enabled). %% @doc Lists the plugins which are currently running. active() -> @@ -86,8 +108,8 @@ read_enabled(PluginsFile) -> %% the resulting list, otherwise they're skipped. dependencies(Reverse, Sources, AllPlugins) -> {ok, G} = rabbit_misc:build_acyclic_graph( - fun (App, _Deps) -> [{App, App}] end, - fun (App, Deps) -> [{App, Dep} || Dep <- Deps] end, + fun ({App, _Deps}) -> [{App, App}] end, + fun ({App, Deps}) -> [{App, Dep} || Dep <- Deps] end, lists:ukeysort( 1, [{Name, Deps} || #plugin{name = Name, @@ -104,9 +126,11 @@ dependencies(Reverse, Sources, AllPlugins) -> %%---------------------------------------------------------------------------- -prepare_plugins(EnabledFile, PluginsDistDir, ExpandDir) -> +prepare_plugins(Enabled) -> + {ok, PluginsDistDir} = application:get_env(rabbit, plugins_dir), + {ok, ExpandDir} = application:get_env(rabbit, plugins_expand_dir), + AllPlugins = list(PluginsDistDir), - Enabled = read_enabled(EnabledFile), ToUnpack = dependencies(false, Enabled, AllPlugins), ToUnpackPlugins = lookup_plugins(ToUnpack, AllPlugins), @@ -117,12 +141,6 @@ prepare_plugins(EnabledFile, PluginsDistDir, ExpandDir) -> [Missing]) end, - %% Eliminate the contents of the destination directory - case delete_recursively(ExpandDir) of - ok -> ok; - {error, E1} -> throw({error, {cannot_delete_plugins_expand_dir, - [ExpandDir, E1]}}) - end, case filelib:ensure_dir(ExpandDir ++ "/") of ok -> ok; {error, E2} -> throw({error, {cannot_create_plugins_expand_dir, diff --git a/src/rabbit_plugins_main.erl b/src/rabbit_plugins_main.erl index 89e16f14..3b9d30f2 100644 --- a/src/rabbit_plugins_main.erl +++ b/src/rabbit_plugins_main.erl @@ -18,23 +18,30 @@ -include("rabbit.hrl"). -export([start/0, stop/0]). +-export([action/6]). +-define(NODE_OPT, "-n"). -define(VERBOSE_OPT, "-v"). -define(MINIMAL_OPT, "-m"). -define(ENABLED_OPT, "-E"). -define(ENABLED_ALL_OPT, "-e"). +-define(OFFLINE_OPT, "--offline"). +-define(NODE_DEF(Node), {?NODE_OPT, {option, Node}}). -define(VERBOSE_DEF, {?VERBOSE_OPT, flag}). -define(MINIMAL_DEF, {?MINIMAL_OPT, flag}). -define(ENABLED_DEF, {?ENABLED_OPT, flag}). -define(ENABLED_ALL_DEF, {?ENABLED_ALL_OPT, flag}). +-define(OFFLINE_DEF, {?OFFLINE_OPT, flag}). --define(GLOBAL_DEFS, []). +-define(RPC_TIMEOUT, infinity). + +-define(GLOBAL_DEFS(Node), [?NODE_DEF(Node)]). -define(COMMANDS, [{list, [?VERBOSE_DEF, ?MINIMAL_DEF, ?ENABLED_DEF, ?ENABLED_ALL_DEF]}, - enable, - disable]). + {enable, [?OFFLINE_DEF]}, + {disable, [?OFFLINE_DEF]}]). %%---------------------------------------------------------------------------- @@ -51,11 +58,10 @@ start() -> {ok, [[PluginsFile|_]|_]} = init:get_argument(enabled_plugins_file), + {ok, [[NodeStr|_]|_]} = init:get_argument(nodename), {ok, [[PluginsDir|_]|_]} = init:get_argument(plugins_dist_dir), {Command, Opts, Args} = - case rabbit_misc:parse_arguments(?COMMANDS, ?GLOBAL_DEFS, - init:get_plain_arguments()) - of + case parse_arguments(init:get_plain_arguments(), NodeStr) of {ok, Res} -> Res; no_command -> print_error("could not recognise command", []), usage() @@ -67,7 +73,8 @@ start() -> [string:join([atom_to_list(Command) | Args], " ")]) end, - case catch action(Command, Args, Opts, PluginsFile, PluginsDir) of + Node = proplists:get_value(?NODE_OPT, Opts), + case catch action(Command, Node, Args, Opts, PluginsFile, PluginsDir) of ok -> rabbit_misc:quit(0); {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} -> @@ -92,12 +99,25 @@ stop() -> %%---------------------------------------------------------------------------- -action(list, [], Opts, PluginsFile, PluginsDir) -> - action(list, [".*"], Opts, PluginsFile, PluginsDir); -action(list, [Pat], Opts, PluginsFile, PluginsDir) -> - format_plugins(Pat, Opts, PluginsFile, PluginsDir); +parse_arguments(CmdLine, NodeStr) -> + case rabbit_misc:parse_arguments( + ?COMMANDS, ?GLOBAL_DEFS(NodeStr), CmdLine) of + {ok, {Cmd, Opts0, Args}} -> + Opts = [case K of + ?NODE_OPT -> {?NODE_OPT, rabbit_nodes:make(V)}; + _ -> {K, V} + end || {K, V} <- Opts0], + {ok, {Cmd, Opts, Args}}; + E -> + E + end. + +action(list, Node, [], Opts, PluginsFile, PluginsDir) -> + action(list, Node, [".*"], Opts, PluginsFile, PluginsDir); +action(list, Node, [Pat], Opts, PluginsFile, PluginsDir) -> + format_plugins(Node, Pat, Opts, PluginsFile, PluginsDir); -action(enable, ToEnable0, _Opts, PluginsFile, PluginsDir) -> +action(enable, Node, ToEnable0, Opts, PluginsFile, PluginsDir) -> case ToEnable0 of [] -> throw({error_string, "Not enough arguments for 'enable'"}); _ -> ok @@ -108,7 +128,22 @@ action(enable, ToEnable0, _Opts, PluginsFile, PluginsDir) -> Enabled, AllPlugins), ToEnable = [list_to_atom(Name) || Name <- ToEnable0], Missing = ToEnable -- plugin_names(AllPlugins), - NewEnabled = lists:usort(Enabled ++ ToEnable), + ExplicitlyEnabled = lists:usort(Enabled ++ ToEnable), + OfflineOnly = proplists:get_bool(?OFFLINE_OPT, Opts), + NewEnabled = + case OfflineOnly of + true -> ToEnable -- Enabled; + false -> + case rpc:call(Node, rabbit_plugins, active, [], ?RPC_TIMEOUT) of + {badrpc, _} -> rpc_failure(Node); + [] -> ExplicitlyEnabled; + ActiveList -> + EnabledSet = sets:from_list(ExplicitlyEnabled), + ActiveSet = sets:from_list(ActiveList), + Intersect = sets:intersection(EnabledSet, ActiveSet), + sets:to_list(sets:subtract(EnabledSet, Intersect)) + end + end, NewImplicitlyEnabled = rabbit_plugins:dependencies(false, NewEnabled, AllPlugins), MissingDeps = (NewImplicitlyEnabled -- plugin_names(AllPlugins)) -- Missing, @@ -120,15 +155,15 @@ action(enable, ToEnable0, _Opts, PluginsFile, PluginsDir) -> fmt_missing("plugins", Missing) ++ fmt_missing("dependencies", MissingDeps)}) end, - write_enabled_plugins(PluginsFile, NewEnabled), - case NewEnabled -- ImplicitlyEnabled of + write_enabled_plugins(PluginsFile, ExplicitlyEnabled), + case NewEnabled -- (ImplicitlyEnabled -- ExplicitlyEnabled) of [] -> io:format("Plugin configuration unchanged.~n"); _ -> print_list("The following plugins have been enabled:", - NewImplicitlyEnabled -- ImplicitlyEnabled), - report_change() + NewEnabled), + action_change(OfflineOnly, Node, enable, NewEnabled) end; -action(disable, ToDisable0, _Opts, PluginsFile, PluginsDir) -> +action(disable, Node, ToDisable0, Opts, PluginsFile, PluginsDir) -> case ToDisable0 of [] -> throw({error_string, "Not enough arguments for 'disable'"}); _ -> ok @@ -143,22 +178,42 @@ action(disable, ToDisable0, _Opts, PluginsFile, PluginsDir) -> Missing) end, ToDisableDeps = rabbit_plugins:dependencies(true, ToDisable, AllPlugins), + OfflineOnly = proplists:get_bool(?OFFLINE_OPT, Opts), + Active = + case OfflineOnly of + true -> Enabled; + false -> case rpc:call(Node, rabbit_plugins, active, + [], ?RPC_TIMEOUT) of + {badrpc, _} -> rpc_failure(Node); + [] -> Enabled; + ActiveList -> ActiveList + end + end, NewEnabled = Enabled -- ToDisableDeps, - case length(Enabled) =:= length(NewEnabled) of + case length(Active) =:= length(NewEnabled) of true -> io:format("Plugin configuration unchanged.~n"); false -> ImplicitlyEnabled = - rabbit_plugins:dependencies(false, Enabled, AllPlugins), + rabbit_plugins:dependencies(false, Active, AllPlugins), NewImplicitlyEnabled = rabbit_plugins:dependencies(false, NewEnabled, AllPlugins), + Disabled = ImplicitlyEnabled -- NewImplicitlyEnabled, print_list("The following plugins have been disabled:", - ImplicitlyEnabled -- NewImplicitlyEnabled), + Disabled), write_enabled_plugins(PluginsFile, NewEnabled), - report_change() + action_change(OfflineOnly, Node, disable, Disabled) end. %%---------------------------------------------------------------------------- +rpc_failure(Node) -> + RpcMsg = rabbit_nodes:diagnostics([Node]), + Msg = io_lib:format("Unable to contact ~p~n" + "To apply these changes anyway, " + "try again with --offline~n" + "~s", [Node, RpcMsg]), + throw({error_string, Msg}). + print_error(Format, Args) -> rabbit_misc:format_stderr("Error: " ++ Format ++ "~n", Args). @@ -167,7 +222,7 @@ usage() -> rabbit_misc:quit(1). %% Pretty print a list of plugins. -format_plugins(Pattern, Opts, PluginsFile, PluginsDir) -> +format_plugins(Node, Pattern, Opts, PluginsFile, PluginsDir) -> Verbose = proplists:get_bool(?VERBOSE_OPT, Opts), Minimal = proplists:get_bool(?MINIMAL_OPT, Opts), Format = case {Verbose, Minimal} of @@ -182,9 +237,14 @@ format_plugins(Pattern, Opts, PluginsFile, PluginsDir) -> AvailablePlugins = rabbit_plugins:list(PluginsDir), EnabledExplicitly = rabbit_plugins:read_enabled(PluginsFile), - EnabledImplicitly = - rabbit_plugins:dependencies(false, EnabledExplicitly, - AvailablePlugins) -- EnabledExplicitly, + AllEnabled = rabbit_plugins:dependencies(false, EnabledExplicitly, + AvailablePlugins), + EnabledImplicitly = AllEnabled -- EnabledExplicitly, + Running = case rpc:call(Node, rabbit_plugins, active, + [], ?RPC_TIMEOUT) of + {badrpc, _} -> AllEnabled; + Active -> Active + end, Missing = [#plugin{name = Name, dependencies = []} || Name <- ((EnabledExplicitly ++ EnabledImplicitly) -- plugin_names(AvailablePlugins))], @@ -192,31 +252,42 @@ format_plugins(Pattern, Opts, PluginsFile, PluginsDir) -> Plugins = [ Plugin || Plugin = #plugin{name = Name} <- AvailablePlugins ++ Missing, re:run(atom_to_list(Name), RE, [{capture, none}]) =:= match, - if OnlyEnabled -> lists:member(Name, EnabledExplicitly); - OnlyEnabledAll -> (lists:member(Name, - EnabledExplicitly) or - lists:member(Name, EnabledImplicitly)); + if OnlyEnabled -> lists:member(Name, EnabledExplicitly); + OnlyEnabledAll -> lists:member(Name, EnabledExplicitly) or + lists:member(Name,EnabledImplicitly); true -> true end], Plugins1 = usort_plugins(Plugins), MaxWidth = lists:max([length(atom_to_list(Name)) || #plugin{name = Name} <- Plugins1] ++ [0]), - [format_plugin(P, EnabledExplicitly, EnabledImplicitly, + case Format of + minimal -> ok; + _ -> io:format(" Configured: E = explicitly enabled; " + "e = implicitly enabled; ! = missing~n" + " | Status: * = running on ~s~n" + " |/~n", [Node]) + end, + [format_plugin(P, EnabledExplicitly, EnabledImplicitly, Running, plugin_names(Missing), Format, MaxWidth) || P <- Plugins1], ok. format_plugin(#plugin{name = Name, version = Version, description = Description, dependencies = Deps}, - EnabledExplicitly, EnabledImplicitly, Missing, - Format, MaxWidth) -> - Glyph = case {lists:member(Name, EnabledExplicitly), - lists:member(Name, EnabledImplicitly), - lists:member(Name, Missing)} of - {true, false, false} -> "[E]"; - {false, true, false} -> "[e]"; - {_, _, true} -> "[!]"; - _ -> "[ ]" - end, + EnabledExplicitly, EnabledImplicitly, Running, + Missing, Format, MaxWidth) -> + EnabledGlyph = case {lists:member(Name, EnabledExplicitly), + lists:member(Name, EnabledImplicitly), + lists:member(Name, Missing)} of + {true, false, false} -> "E"; + {false, true, false} -> "e"; + {_, _, true} -> "!"; + _ -> " " + end, + RunningGlyph = case lists:member(Name, Running) of + true -> "*"; + false -> " " + end, + Glyph = rabbit_misc:format("[~s~s]", [EnabledGlyph, RunningGlyph]), Opt = fun (_F, A, A) -> ok; ( F, A, _) -> io:format(F, [A]) end, @@ -227,9 +298,9 @@ format_plugin(#plugin{name = Name, version = Version, Opt("~s", Version, undefined), io:format("~n"); verbose -> io:format("~s ~w~n", [Glyph, Name]), - Opt(" Version: \t~s~n", Version, undefined), - Opt(" Dependencies:\t~p~n", Deps, []), - Opt(" Description: \t~s~n", Description, undefined), + Opt(" Version: \t~s~n", Version, undefined), + Opt(" Dependencies:\t~p~n", Deps, []), + Opt(" Description: \t~s~n", Description, undefined), io:format("~n") end. @@ -262,6 +333,35 @@ write_enabled_plugins(PluginsFile, Plugins) -> PluginsFile, Reason}}) end. -report_change() -> - io:format("Plugin configuration has changed. " - "Restart RabbitMQ for changes to take effect.~n"). +action_change(true, _Node, Action, _Targets) -> + io:format("Offline Mode: No plugins were ~p.~n" + "Please (re)start the broker to apply your changes.~n", + [case Action of + enable -> started; + disable -> stopped + end]); +action_change(false, Node, Action, Targets) -> + rpc_call(Node, rabbit_plugins, Action, [Targets]). + +rpc_call(Node, Mod, Action, Args) -> + io:format("Changing plugin configuration on ~p.", [Node]), + AsyncKey = rpc:async_call(Node, Mod, Action, Args), + rpc_progress(AsyncKey, Node, Action). + +rpc_progress(Key, Node, Action) -> + case rpc:nb_yield(Key, 1000) of + timeout -> io:format("."), + rpc_progress(Key, Node, Action); + {value, {badrpc, nodedown}} -> + io:format(". error.~nUnable to contact ~p.~n ", [Node]), + io:format("Please start the broker to apply " + "your changes.~n"); + {value, ok} -> + io:format(". done.~n", []); + {value, Error} -> + io:format(". error.~nUnable to ~p plugin(s).~n" + "Please restart the broker to apply your changes.~n" + "Error: ~p~n", + [Action, Error]) + end. + diff --git a/src/rabbit_version.erl b/src/rabbit_version.erl index d943b599..ef480ccb 100644 --- a/src/rabbit_version.erl +++ b/src/rabbit_version.erl @@ -113,10 +113,11 @@ upgrades_required(Scope) -> %% ------------------------------------------------------------------- with_upgrade_graph(Fun, Scope) -> + Attrs = rabbit_misc:all_module_attributes(rabbit_upgrade), case rabbit_misc:build_acyclic_graph( - fun (Module, Steps) -> vertices(Module, Steps, Scope) end, - fun (Module, Steps) -> edges(Module, Steps, Scope) end, - rabbit_misc:all_module_attributes(rabbit_upgrade)) of + fun ({Module, Steps}) -> vertices(Module, Steps, Scope) end, + fun ({Module, Steps}) -> edges(Module, Steps, Scope) end, + [{Mod, Steps} || {_, Mod, Steps} <- Attrs]) of {ok, G} -> try Fun(G) after @@ -161,7 +162,7 @@ heads(G) -> categorise_by_scope(Version) when is_list(Version) -> Categorised = - [{Scope, Name} || {_Module, Attributes} <- + [{Scope, Name} || {_App, _Module, Attributes} <- rabbit_misc:all_module_attributes(rabbit_upgrade), {Name, Scope, _Requires} <- Attributes, lists:member(Name, Version)], |