This commit is contained in:
Feng Lee 2015-08-07 17:24:03 +08:00
parent 31a85ff0d6
commit c6ccacd990
5 changed files with 150 additions and 81 deletions

View File

@ -149,6 +149,7 @@
name, name,
version, version,
descr, descr,
config,
active = false active = false
}). }).

@ -1 +1 @@
Subproject commit 892810dbc853ba147f9acabecd52bb51218275e2 Subproject commit 04baf44a465c1513e75dfdcc2f6507e7a315d2d5

View File

@ -164,8 +164,11 @@
]}, ]},
%% Plugins %% Plugins
{plugins, [ {plugins, [
{dir, "./plugins"}, %% Plugin App Library Dir
{loaded_file, "./data/plugins.config"} {plugins_dir, "./plugins"},
%% File to store loaded plugin names.
{loaded_file, "./data/loaded_plugins"}
]}, ]},
%% Listeners %% Listeners
{listeners, [ {listeners, [

View File

@ -88,5 +88,5 @@
{template, "files/rewrite.config", "etc/rewrite.config"}, {template, "files/rewrite.config", "etc/rewrite.config"},
{template, "files/clients.config", "etc/clients.config"}, {template, "files/clients.config", "etc/clients.config"},
{template, "files/vm.args", "etc/vm.args"}, {template, "files/vm.args", "etc/vm.args"},
{copy, "files/plugins.config", "data/plugins.config"} {copy, "files/loaded_plugins", "data/loaded_plugins"}
]}. ]}.

View File

@ -20,7 +20,7 @@
%%% SOFTWARE. %%% SOFTWARE.
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
%%% @doc %%% @doc
%%% emqttd plugin admin. %%% emqttd plugins.
%%% %%%
%%% @end %%% @end
%%%----------------------------------------------------------------------------- %%%-----------------------------------------------------------------------------
@ -33,29 +33,42 @@
-export([load/0, unload/0]). -export([load/0, unload/0]).
-export([list/0, load/1, unload/1]). -export([load/1, unload/1]).
-export([list/0]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Load all plugins when the broker started. %% @doc Load all plugins when the broker started.
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec load() -> list() | {error, any()}. -spec load() -> list() | {error, any()}.
load() -> load() ->
case read_loaded() of case env(loaded_file) of
{ok, LoadNames} -> {ok, File} ->
NotFound = LoadNames -- apps(plugin), with_loaded_file(File, fun(Names) -> load_plugins(Names, false) end);
case NotFound of undefined ->
[] -> ok; %% No plugins available
NotFound -> lager:error("Cannot find plugins: ~p", [NotFound]) ignore
end, end.
start_apps(LoadNames -- NotFound -- apps(started));
with_loaded_file(File, SuccFun) ->
case read_loaded(File) of
{ok, Names} ->
SuccFun(Names);
{error, Error} -> {error, Error} ->
lager:error("Read loaded_plugins file error: ~p", [Error]), lager:error("Failed to read: ~p, error: ~p", [File, Error]),
{error, Error} {error, Error}
end. end.
start_apps(Apps) -> load_plugins(Names, Persistent) ->
[start_app(App) || App <- Apps]. Plugins = list(), NotFound = Names -- names(Plugins),
case NotFound of
[] -> ok;
NotFound -> lager:error("Cannot find plugins: ~p", [NotFound])
end,
NeedToLoad = Names -- NotFound -- names(started_app),
[load_plugin(find_plugin(Name, Plugins), Persistent) || Name <- NeedToLoad].
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Unload all plugins before broker stopped. %% @doc Unload all plugins before broker stopped.
@ -63,16 +76,16 @@ start_apps(Apps) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec unload() -> list() | {error, any()}. -spec unload() -> list() | {error, any()}.
unload() -> unload() ->
case read_loaded() of case env(loaded_file) of
{ok, LoadNames} -> {ok, File} ->
stop_apps(LoadNames); with_loaded_file(File, fun(Names) -> stop_plugins(Names) end);
{error, Error} -> undefined ->
lager:error("Read loaded_plugins file error: ~p", [Error]), ignore
{error, Error}
end. end.
stop_apps(Apps) -> %% stop plugins
[stop_app(App) || App <- Apps]. stop_plugins(Names) ->
[stop_app(App) || App <- Names].
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc List all available plugins %% @doc List all available plugins
@ -80,82 +93,119 @@ stop_apps(Apps) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec list() -> [mqtt_plugin()]. -spec list() -> [mqtt_plugin()].
list() -> list() ->
PluginsDir = env(dir), case env(plugins_dir) of
AppFiles = filelib:wildcard("*/ebin/*.app", PluginsDir), {ok, PluginsDir} ->
Plugins = [plugin(filename:join(PluginsDir, AppFile)) || AppFile <- AppFiles], AppFiles = filelib:wildcard("*/ebin/*.app", PluginsDir),
StartedApps = apps(started), Plugins = [plugin(PluginsDir, AppFile) || AppFile <- AppFiles],
lists:map(fun(Plugin = #mqtt_plugin{name = Name}) -> StartedApps = names(started_app),
case lists:member(Name, StartedApps) of lists:map(fun(Plugin = #mqtt_plugin{name = Name}) ->
true -> Plugin#mqtt_plugin{active = true}; case lists:member(Name, StartedApps) of
false -> Plugin true -> Plugin#mqtt_plugin{active = true};
end false -> Plugin
end, Plugins). end
end, Plugins);
undefined ->
[]
end.
plugin(AppFile) -> plugin(PluginsDir, AppFile0) ->
AppFile = filename:join(PluginsDir, AppFile0),
{ok, [{application, Name, Attrs}]} = file:consult(AppFile), {ok, [{application, Name, Attrs}]} = file:consult(AppFile),
CfgFile = filename:join([PluginsDir, Name, "etc/plugin.config"]),
AppsEnv1 =
case filelib:is_file(CfgFile) of
true ->
{ok, [AppsEnv]} = file:consult(CfgFile),
AppsEnv;
false ->
[]
end,
Ver = proplists:get_value(vsn, Attrs, "0"), Ver = proplists:get_value(vsn, Attrs, "0"),
Descr = proplists:get_value(description, Attrs, ""), Descr = proplists:get_value(description, Attrs, ""),
#mqtt_plugin{name = Name, version = Ver, descr = Descr}. #mqtt_plugin{name = Name, version = Ver, config = AppsEnv1, descr = Descr}.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc Load One Plugin %% @doc Load One Plugin
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec load(atom()) -> ok | {error, any()}. -spec load(atom()) -> ok | {error, any()}.
load(PluginName) when is_atom(PluginName) -> load(PluginName) when is_atom(PluginName) ->
case {lists:member(PluginName, apps(started)), lists:member(PluginName, apps(plugin))} of case lists:member(PluginName, names(started_app)) of
{true, _} -> true ->
lager:error("plugin ~p is started", [PluginName]), lager:error("Plugin ~p is already started", [PluginName]),
{error, already_started}; {error, already_started};
{false, true} -> false ->
load_plugin(PluginName); case find_plugin(PluginName) of
{false, false} -> false ->
lager:error("plugin ~p is not found", [PluginName]), lager:error("Plugin ~s not found", [PluginName]),
{error, not_found} {error, not_found};
Plugin ->
load_plugin(Plugin, true)
end
end. end.
-spec load_plugin(App :: atom()) -> {ok, list()} | {error, any()}. load_plugin(#mqtt_plugin{name = Name, config = Config}, Persistent) ->
load_plugin(PluginName) -> case load_app(Name, Config) of
case start_app(PluginName) of ok ->
{ok, Started} -> start_app(Name, fun(App) -> plugin_loaded(App, Persistent) end);
plugin_loaded(PluginName),
{ok, Started};
{error, Error} -> {error, Error} ->
{error, Error} {error, Error}
end. end.
start_app(App) -> load_app(App, Config) ->
case application:load(App) of
ok ->
set_config(Config);
{error, Error} ->
{error, Error}
end.
%% This trick is awesome:)
set_config([]) ->
ok;
set_config([{AppName, Envs} | Config]) ->
[application:set_env(AppName, Par, Val) || {Par, Val} <- Envs],
set_config(Config).
start_app(App, SuccFun) ->
case application:ensure_all_started(App) of case application:ensure_all_started(App) of
{ok, Started} -> {ok, Started} ->
lager:info("started apps: ~p, load plugin ~p successfully", [Started, App]), lager:info("Started Apps: ~p, load plugin ~p successfully", [Started, App]),
SuccFun(App),
{ok, Started}; {ok, Started};
{error, {ErrApp, Reason}} -> {error, {ErrApp, Reason}} ->
lager:error("load plugin ~p error, cannot start app ~s for ~p", [App, ErrApp, Reason]), lager:error("load plugin ~p error, cannot start app ~s for ~p", [App, ErrApp, Reason]),
{error, {ErrApp, Reason}} {error, {ErrApp, Reason}}
end. end.
find_plugin(Name) ->
find_plugin(Name, list()).
find_plugin(Name, Plugins) ->
lists:keyfind(Name, 2, Plugins).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% @doc UnLoad One Plugin %% @doc UnLoad One Plugin
%% @end %% @end
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec unload(atom()) -> ok | {error, any()}. -spec unload(atom()) -> ok | {error, any()}.
unload(PluginName) when is_atom(PluginName) -> unload(PluginName) when is_atom(PluginName) ->
case {lists:member(PluginName, apps(started)), lists:member(PluginName, apps(plugin))} of case {lists:member(PluginName, names(started_app)), lists:member(PluginName, names(plugin))} of
{false, _} ->
lager:error("plugin ~p is not started", [PluginName]),
{error, not_started};
{true, true} -> {true, true} ->
unload_plugin(PluginName); unload_plugin(PluginName, true);
{false, _} ->
lager:error("Plugin ~p is not started", [PluginName]),
{error, not_started};
{true, false} -> {true, false} ->
lager:error("~s is not a plugin, cannot unload it", [PluginName]), lager:error("~s is not a plugin, cannot unload it", [PluginName]),
{error, not_found} {error, not_found}
end. end.
-spec unload_plugin(App :: atom()) -> ok | {error, any()}. unload_plugin(App, Persistent) ->
unload_plugin(App) ->
case stop_app(App) of case stop_app(App) of
ok -> ok ->
plugin_unloaded(App), ok; plugin_unloaded(App, Persistent), ok;
{error, Reason} -> {error, Reason} ->
{error, Reason} {error, Reason}
end. end.
@ -174,27 +224,34 @@ stop_app(App) ->
%%% Internal functions %%% Internal functions
%%%============================================================================= %%%=============================================================================
apps(plugin) -> names(plugin) ->
[Name || #mqtt_plugin{name = Name} <- list()]; names(list());
apps(started) -> names(started_app) ->
[Name || {Name, _Descr, _Ver} <- application:which_applications()]. [Name || {Name, _Descr, _Ver} <- application:which_applications()];
plugin_loaded(Name) -> names(Plugins) ->
[Name || #mqtt_plugin{name = Name} <- Plugins].
plugin_loaded(_Name, false) ->
ok;
plugin_loaded(Name, true) ->
case read_loaded() of case read_loaded() of
{ok, Names} -> {ok, Names} ->
case lists:member(Name, Names) of case lists:member(Name, Names) of
true ->
ignore;
false -> false ->
%% write file if plugin is loaded %% write file if plugin is loaded
write_loaded(lists:append(Names, Name)) write_loaded(lists:append(Names, Name));
true ->
ignore
end; end;
{error, Error} -> {error, Error} ->
lager:error("Cannot read loaded plugins: ~p", [Error]) lager:error("Cannot read loaded plugins: ~p", [Error])
end. end.
plugin_unloaded(Name) -> plugin_unloaded(_Name, false) ->
ok;
plugin_unloaded(Name, true) ->
case read_loaded() of case read_loaded() of
{ok, Names} -> {ok, Names} ->
case lists:member(Name, Names) of case lists:member(Name, Names) of
@ -204,11 +261,15 @@ plugin_unloaded(Name) ->
lager:error("Cannot find ~s in loaded_file", [Name]) lager:error("Cannot find ~s in loaded_file", [Name])
end; end;
{error, Error} -> {error, Error} ->
lager:error("Cannot read loaded plugins: ~p", [Error]) lager:error("Cannot read loaded_plugins: ~p", [Error])
end. end.
read_loaded() -> read_loaded() ->
file:consult(env(loaded_file)). {ok, File} = env(loaded_file),
read_loaded(File).
read_loaded(File) ->
file:consult(File).
write_loaded(AppNames) -> write_loaded(AppNames) ->
case file:open(env(loaded_file), [binary, write]) of case file:open(env(loaded_file), [binary, write]) of
@ -220,12 +281,16 @@ write_loaded(AppNames) ->
{error, Error} {error, Error}
end. end.
env(dir) -> env(Name) ->
proplists:get_value(dir, env(), "./plugins"); case application:get_env(emqttd, plugins) of
{ok, PluginsEnv} ->
env(loaded_file) -> case proplists:get_value(Name, PluginsEnv) of
proplists:get_value(loaded_file, env(), "./data/loaded_plugins"). undefined ->
undefined;
env() -> Val ->
{ok, PluginsEnv} = application:get_env(emqttd, plugins), PluginsEnv. {ok, Val}
end;
undefined ->
undefined
end.