summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlexandru Scvortov <alexandru@rabbitmq.com>2011-09-22 17:34:25 +0100
committerAlexandru Scvortov <alexandru@rabbitmq.com>2011-09-22 17:34:25 +0100
commit8aff707c85ce7049d2cbb6aef920e59e9b2d3606 (patch)
tree0e27cc4547d0d8ffe2c8a6c8bb150bda0ca3f86e /src
parent1b69e1fa1527fe559130f07b31c3002d8d634b93 (diff)
parentda265ac0ec05eef8a2fb8e3b521a7e488e7a285f (diff)
downloadrabbitmq-server-git-8aff707c85ce7049d2cbb6aef920e59e9b2d3606.tar.gz
merge default into bug21319
Diffstat (limited to 'src')
-rw-r--r--src/rabbit_control.erl19
-rw-r--r--src/rabbit_misc.erl9
-rw-r--r--src/rabbit_plugins.erl315
-rw-r--r--src/rabbit_prelaunch.erl29
4 files changed, 349 insertions, 23 deletions
diff --git a/src/rabbit_control.erl b/src/rabbit_control.erl
index 1163ae9d86..b28748538e 100644
--- a/src/rabbit_control.erl
+++ b/src/rabbit_control.erl
@@ -86,24 +86,24 @@ start() ->
true -> ok;
false -> io:format("...done.~n")
end,
- quit(0);
+ rabbit_misc:quit(0);
{'EXIT', {function_clause, [{?MODULE, action, _} | _]}} ->
print_error("invalid command '~s'",
[string:join([atom_to_list(Command) | Args], " ")]),
usage();
{error, Reason} ->
print_error("~p", [Reason]),
- quit(2);
+ rabbit_misc:quit(2);
{badrpc, {'EXIT', Reason}} ->
print_error("~p", [Reason]),
- quit(2);
+ rabbit_misc:quit(2);
{badrpc, Reason} ->
print_error("unable to connect to node ~w: ~w", [Node, Reason]),
print_badrpc_diagnostics(Node),
- quit(2);
+ rabbit_misc:quit(2);
Other ->
print_error("~p", [Other]),
- quit(2)
+ rabbit_misc:quit(2)
end.
fmt_stderr(Format, Args) -> rabbit_misc:format_stderr(Format ++ "~n", Args).
@@ -154,7 +154,7 @@ stop() ->
usage() ->
io:format("~s", [rabbit_ctl_usage:usage()]),
- quit(1).
+ rabbit_misc:quit(1).
%%----------------------------------------------------------------------------
@@ -506,10 +506,3 @@ prettify_typed_amqp_value(table, Value) -> prettify_amqp_table(Value);
prettify_typed_amqp_value(array, Value) -> [prettify_typed_amqp_value(T, V) ||
{T, V} <- Value];
prettify_typed_amqp_value(_Type, Value) -> Value.
-
-%% the slower shutdown on windows required to flush stdout
-quit(Status) ->
- case os:type() of
- {unix, _} -> halt(Status);
- {win32, _} -> init:stop(Status)
- end.
diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl
index d47041d669..f50730033c 100644
--- a/src/rabbit_misc.erl
+++ b/src/rabbit_misc.erl
@@ -54,6 +54,7 @@
-export([pget/2, pget/3, pget_or_die/2]).
-export([format_message_queue/2]).
-export([append_rpc_all_nodes/4]).
+-export([quit/1]).
%%----------------------------------------------------------------------------
@@ -191,6 +192,7 @@
-spec(pget_or_die/2 :: (term(), [term()]) -> term() | no_return()).
-spec(format_message_queue/2 :: (any(), priority_queue:q()) -> term()).
-spec(append_rpc_all_nodes/4 :: ([node()], atom(), atom(), [any()]) -> [any()]).
+-spec(quit/1 :: (integer() | string()) -> no_return()).
-endif.
@@ -806,3 +808,10 @@ append_rpc_all_nodes(Nodes, M, F, A) ->
{badrpc, _} -> [];
_ -> Res
end || Res <- ResL]).
+
+%% the slower shutdown on windows required to flush stdout
+quit(Status) ->
+ case os:type() of
+ {unix, _} -> halt(Status);
+ {win32, _} -> init:stop(Status)
+ end.
diff --git a/src/rabbit_plugins.erl b/src/rabbit_plugins.erl
new file mode 100644
index 0000000000..b93b7bed99
--- /dev/null
+++ b/src/rabbit_plugins.erl
@@ -0,0 +1,315 @@
+%% 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 Developer of the Original Code is VMware, Inc.
+%% Copyright (c) 2011 VMware, Inc. All rights reserved.
+%%
+
+-module(rabbit_plugins).
+-include("rabbit.hrl").
+
+-export([start/0, stop/0, find_plugins/1, read_enabled_plugins/1,
+ lookup_plugins/2, calculate_required_plugins/2]).
+
+-define(COMPACT_OPT, "-c").
+
+%%----------------------------------------------------------------------------
+
+-ifdef(use_specs).
+
+-spec(start/0 :: () -> no_return()).
+-spec(stop/0 :: () -> 'ok').
+
+-endif.
+
+%%----------------------------------------------------------------------------
+
+start() ->
+ {ok, [[EnabledPluginsFile|_]|_]} = init:get_argument(enabled_plugins_file),
+ put(enabled_plugins_file, EnabledPluginsFile),
+ {ok, [[PluginsDistDir|_]|_]} = init:get_argument(plugins_dist_dir),
+ put(plugins_dist_dir, PluginsDistDir),
+ {[Command0 | Args], Opts} =
+ case rabbit_misc:get_options([{flag, ?COMPACT_OPT}],
+ init:get_plain_arguments()) of
+ {[], _Opts} -> usage();
+ CmdArgsAndOpts -> CmdArgsAndOpts
+ end,
+ Command = list_to_atom(Command0),
+
+ case catch action(Command, Args, Opts) of
+ ok ->
+ rabbit_misc:quit(0);
+ {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} ->
+ print_error("invalid command '~s'",
+ [string:join([atom_to_list(Command) | Args], " ")]),
+ usage();
+ {error, Reason} ->
+ print_error("~p", [Reason]),
+ rabbit_misc:quit(2);
+ Other ->
+ print_error("~p", [Other]),
+ rabbit_misc:quit(2)
+ end.
+
+stop() ->
+ ok.
+
+print_error(Format, Args) ->
+ rabbit_misc:format_stderr("Error: " ++ Format ++ "~n", Args).
+
+usage() ->
+ io:format("~s", [rabbit_plugins_usage:usage()]),
+ rabbit_misc:quit(1).
+
+%%----------------------------------------------------------------------------
+
+action(list, [], Opts) ->
+ action(list, [".*"], Opts);
+action(list, [Pat], Opts) ->
+ format_plugins(Pat, proplists:get_bool(?COMPACT_OPT, Opts));
+
+action(enable, ToEnable0, _Opts) ->
+ AllPlugins = find_plugins(),
+ EnabledPlugins = lookup_plugins(read_enabled_plugins(), AllPlugins),
+ ToEnable = [list_to_atom(Name) || Name <- ToEnable0],
+ ToEnablePlugins = lookup_plugins(ToEnable, AllPlugins),
+ Missing = ToEnable -- plugin_names(ToEnablePlugins),
+ case Missing of
+ [] -> ok;
+ _ -> io:format("Warning: the following plugins could not be found: ~p~n",
+ [Missing])
+ end,
+ NewEnabledPlugins = merge_plugin_lists(EnabledPlugins, ToEnablePlugins),
+ update_enabled_plugins(plugin_names(NewEnabledPlugins));
+
+action(disable, ToDisable0, _Opts) ->
+ ToDisable = [list_to_atom(Name) || Name <- ToDisable0],
+ Enabled = read_enabled_plugins(),
+ AllPlugins = find_plugins(),
+ Missing = ToDisable -- plugin_names(AllPlugins),
+ case Missing of
+ [] -> ok;
+ _ -> io:format("Warning: the following plugins could not be found: ~p~n",
+ [Missing])
+ end,
+ ToDisable1 = ToDisable -- Missing,
+ ToDisable2 = calculate_dependencies(true, ToDisable1, AllPlugins),
+ AlsoDisabled = sets:to_list(
+ sets:intersection(sets:from_list(ToDisable2 -- ToDisable1),
+ sets:from_list(Enabled))),
+ case AlsoDisabled of
+ [] -> ok;
+ _ -> io:format("Warning: the following plugins will also be disabled "
+ "because their dependencies are no longer met: ~p~n",
+ [AlsoDisabled])
+ end,
+ update_enabled_plugins(Enabled -- ToDisable2).
+
+%%----------------------------------------------------------------------------
+
+%% Get the #plugin{}s ready to be enabled.
+find_plugins() ->
+ find_plugins(get(plugins_dist_dir)).
+find_plugins(PluginsDistDir) ->
+ EZs = [{ez, EZ} || EZ <- filelib:wildcard("*.ez", PluginsDistDir)],
+ FreeApps = [{app, App} ||
+ App <- filelib:wildcard("*/ebin/*.app", PluginsDistDir)],
+ {Plugins, Problems} =
+ lists:foldl(fun ({error, EZ, Reason}, {Plugins1, Problems1}) ->
+ {Plugins1, [{EZ, Reason} | Problems1]};
+ (Plugin = #plugin{}, {Plugins1, Problems1}) ->
+ {[Plugin|Plugins1], Problems1}
+ end, {[], []},
+ [get_plugin_info(PluginsDistDir, Plug) ||
+ Plug <- EZs ++ FreeApps]),
+ case Problems of
+ [] -> ok;
+ _ -> io:format("Warning: Problem reading some plugins: ~p~n", [Problems])
+ end,
+ Plugins.
+
+%% Get the #plugin{} from an .ez.
+get_plugin_info(Base, {ez, EZ0}) ->
+ EZ = filename:join([Base, EZ0]),
+ case read_app_file(EZ) of
+ {application, Name, Props} ->
+ Version = proplists:get_value(vsn, Props, "0"),
+ Description = proplists:get_value(description, Props, ""),
+ Dependencies =
+ filter_applications(proplists:get_value(applications, Props, [])),
+ #plugin{name = Name, version = Version, description = Description,
+ dependencies = Dependencies, location = EZ, type = ez};
+ {error, Reason} ->
+ {error, EZ, Reason}
+ end;
+%% Get the #plugin{} from an .app.
+get_plugin_info(Base, {app, App0}) ->
+ App = filename:join([Base, App0]),
+ case rabbit_file:read_term_file(App) of
+ {ok, [{application, Name, Props}]} ->
+ Version = proplists:get_value(vsn, Props, "0"),
+ Description = proplists:get_value(description, Props, ""),
+ Dependencies =
+ filter_applications(proplists:get_value(applications, Props, [])),
+ Location = filename:absname(filename:dirname(filename:dirname(App))),
+ #plugin{name = Name, version = Version, description = Description,
+ dependencies = Dependencies, location = Location, type = dir};
+ {error, Reason} ->
+ {error, App, {invalid_app, Reason}}
+ end.
+
+%% Read the .app file from an ez.
+read_app_file(EZ) ->
+ case zip:list_dir(EZ) of
+ {ok, [_|ZippedFiles]} ->
+ case find_app_files(ZippedFiles) of
+ [AppPath|_] ->
+ {ok, [{AppPath, AppFile}]} =
+ zip:extract(EZ, [{file_list, [AppPath]}, memory]),
+ parse_binary(AppFile);
+ [] ->
+ {error, no_app_file}
+ end;
+ {error, Reason} ->
+ {error, {invalid_ez, Reason}}
+ end.
+
+%% Return the path of the .app files in ebin/.
+find_app_files(ZippedFiles) ->
+ {ok, RE} = re:compile("^.*/ebin/.*.app$"),
+ [Path || {zip_file, Path, _, _, _, _} <- ZippedFiles,
+ re:run(Path, RE, [{capture, none}]) =:= match].
+
+%% Parse a binary into a term.
+parse_binary(Bin) ->
+ try
+ {ok, Ts, _} = erl_scan:string(binary:bin_to_list(Bin)),
+ {ok, Term} = erl_parse:parse_term(Ts),
+ Term
+ catch
+ Err -> {error, {invalid_app, Err}}
+ end.
+
+%% Pretty print a list of plugins.
+format_plugins(Pattern, Compact) ->
+ AvailablePlugins = find_plugins(),
+ EnabledExplicitly = read_enabled_plugins(),
+ EnabledImplicitly =
+ calculate_required_plugins(EnabledExplicitly, AvailablePlugins) --
+ EnabledExplicitly,
+ {ok, RE} = re:compile(Pattern),
+ [ format_plugin(P, EnabledExplicitly, EnabledImplicitly, Compact)
+ || P = #plugin{name = Name} <- AvailablePlugins,
+ re:run(atom_to_list(Name), RE, [{capture, none}]) =:= match],
+ ok.
+
+format_plugin(#plugin{name = Name, version = Version, description = Description,
+ dependencies = Dependencies},
+ EnabledExplicitly, EnabledImplicitly, Compact) ->
+ Glyph = case {lists:member(Name, EnabledExplicitly),
+ lists:member(Name, EnabledImplicitly)} of
+ {true, false} -> "[E]";
+ {false, true} -> "[e]";
+ _ -> "[A]"
+ end,
+ case Compact of
+ true ->
+ io:format("~s ~w-~s: ~s~n", [Glyph, Name, Version, Description]);
+ false ->
+ io:format("~s ~w~n", [Glyph, Name]),
+ io:format(" Version: \t~s~n", [Version]),
+ case Dependencies of
+ [] -> ok;
+ _ -> io:format(" Dependencies:\t~p~n", [Dependencies])
+ end,
+ io:format(" Description:\t~s~n", [Description]),
+ io:format("~n")
+ end.
+
+usort_plugins(Plugins) ->
+ lists:usort(fun plugins_cmp/2, Plugins).
+
+%% Merge two plugin lists. In case of duplicates, only keep highest
+%% version.
+merge_plugin_lists(Ps1, Ps2) ->
+ filter_duplicates(usort_plugins(Ps1 ++ Ps2)).
+
+filter_duplicates([P1 = #plugin{name = N, version = V1},
+ P2 = #plugin{name = N, version = V2} | Ps]) ->
+ if V1 < V2 -> filter_duplicates([P2 | Ps]);
+ true -> filter_duplicates([P1 | Ps])
+ end;
+filter_duplicates([P | Ps]) ->
+ [P | filter_duplicates(Ps)];
+filter_duplicates(Ps) ->
+ Ps.
+
+plugins_cmp(#plugin{name = N1, version = V1}, #plugin{name = N2, version = V2}) ->
+ {N1, V1} =< {N2, V2}.
+
+%% Filter applications that can be loaded *right now*.
+filter_applications(Applications) ->
+ [Application || Application <- Applications,
+ case application:load(Application) of
+ {error, {already_loaded, _}} -> false;
+ ok -> application:unload(Application),
+ false;
+ _ -> true
+ end].
+
+%% Return the names of the given plugins.
+plugin_names(Plugins) ->
+ [Name || #plugin{name = Name} <- Plugins].
+
+%% Find plugins by name in a list of plugins.
+lookup_plugins(Names, AllPlugins) ->
+ AllPlugins1 = filter_duplicates(usort_plugins(AllPlugins)),
+ [P || P = #plugin{name = Name} <- AllPlugins1, lists:member(Name, Names)].
+
+%% Read the enabled plugin names from disk.
+read_enabled_plugins() ->
+ read_enabled_plugins(get(enabled_plugins_file)).
+
+read_enabled_plugins(FileName) ->
+ case rabbit_file:read_term_file(FileName) of
+ {ok, [Plugins]} -> Plugins;
+ {error, enoent} -> [];
+ {error, Reason} -> throw({error, {cannot_read_enabled_plugins_file,
+ FileName, Reason}})
+ end.
+
+%% Update the enabled plugin names on disk.
+update_enabled_plugins(Plugins) ->
+ FileName = get(enabled_plugins_file),
+ case rabbit_file:write_term_file(FileName, [Plugins]) of
+ ok -> ok;
+ {error, Reason} -> throw({error, {cannot_write_enabled_plugins_file,
+ FileName, Reason}})
+ end.
+
+calculate_required_plugins(Sources, AllPlugins) ->
+ calculate_dependencies(false, Sources, AllPlugins).
+
+calculate_dependencies(Reverse, Sources, AllPlugins) ->
+ AllPlugins1 = filter_duplicates(usort_plugins(AllPlugins)),
+ {ok, G} = rabbit_misc:build_acyclic_graph(
+ fun (App, _Deps) -> [{App, App}] end,
+ fun (App, Deps) -> [{App, Dep} || Dep <- Deps] end,
+ [{Name, Deps}
+ || #plugin{name = Name, dependencies = Deps} <- AllPlugins1]),
+ Dests = case Reverse of
+ false -> digraph_utils:reachable(Sources, G);
+ true -> digraph_utils:reaching(Sources, G)
+ end,
+ true = digraph:delete(G),
+ Dests.
diff --git a/src/rabbit_prelaunch.erl b/src/rabbit_prelaunch.erl
index cd0c322b6d..e6c7344afe 100644
--- a/src/rabbit_prelaunch.erl
+++ b/src/rabbit_prelaunch.erl
@@ -18,6 +18,8 @@
-export([start/0, stop/0]).
+-include("rabbit.hrl").
+
-define(BaseApps, [rabbit]).
-define(ERROR_CODE, 1).
@@ -41,14 +43,14 @@ start() ->
io:format("Activating RabbitMQ plugins ...~n"),
%% Determine our various directories
- [PluginDir, UnpackedPluginDir, NodeStr] = init:get_plain_arguments(),
+ [EnabledPluginsFile, PluginsDistDir, UnpackedPluginDir, NodeStr] =
+ init:get_plain_arguments(),
RootName = UnpackedPluginDir ++ "/rabbit",
- %% Unpack any .ez plugins
- unpack_ez_plugins(PluginDir, UnpackedPluginDir),
+ prepare_plugins(EnabledPluginsFile, PluginsDistDir, UnpackedPluginDir),
%% Build a list of required apps based on the fixed set, and any plugins
- PluginApps = find_plugins(PluginDir) ++ find_plugins(UnpackedPluginDir),
+ PluginApps = find_plugins(UnpackedPluginDir),
RequiredApps = ?BaseApps ++ PluginApps,
%% Build the entire set of dependencies - this will load the
@@ -145,7 +147,11 @@ delete_recursively(Fn) ->
Error -> Error
end.
-unpack_ez_plugins(SrcDir, DestDir) ->
+prepare_plugins(EnabledPluginsFile, PluginsDistDir, DestDir) ->
+ AllPlugins = rabbit_plugins:find_plugins(PluginsDistDir),
+ Enabled = rabbit_plugins:read_enabled_plugins(EnabledPluginsFile),
+ ToUnpack = rabbit_plugins:calculate_required_plugins(Enabled, AllPlugins),
+
%% Eliminate the contents of the destination directory
case delete_recursively(DestDir) of
ok -> ok;
@@ -155,12 +161,15 @@ unpack_ez_plugins(SrcDir, DestDir) ->
ok -> ok;
{error, E2} -> terminate("Could not create dir ~s (~p)", [DestDir, E2])
end,
- [unpack_ez_plugin(PluginName, DestDir) ||
- PluginName <- filelib:wildcard(SrcDir ++ "/*.ez")].
-unpack_ez_plugin(PluginFn, PluginDestDir) ->
- zip:unzip(PluginFn, [{cwd, PluginDestDir}]),
- ok.
+ [prepare_plugin(Plugin, DestDir) ||
+ Plugin <- rabbit_plugins:lookup_plugins(ToUnpack, AllPlugins)].
+
+prepare_plugin(#plugin{type = ez, location = Location}, PluginDestDir) ->
+ zip:unzip(Location, [{cwd, PluginDestDir}]);
+prepare_plugin(#plugin{type = dir, name = Name, location = Location},
+ PluginsDestDir) ->
+ file:make_symlink(Location, filename:join([PluginsDestDir, Name])).
find_plugins(PluginDir) ->
[prepare_dir_plugin(PluginName) ||