diff --git a/include/emqttd.hrl b/include/emqttd.hrl index fc5ade8e8..6c4b55776 100644 --- a/include/emqttd.hrl +++ b/include/emqttd.hrl @@ -149,6 +149,7 @@ name, version, descr, + config, active = false }). diff --git a/plugins/emqttd_plugin_mysql b/plugins/emqttd_plugin_mysql index 892810dbc..04baf44a4 160000 --- a/plugins/emqttd_plugin_mysql +++ b/plugins/emqttd_plugin_mysql @@ -1 +1 @@ -Subproject commit 892810dbc853ba147f9acabecd52bb51218275e2 +Subproject commit 04baf44a465c1513e75dfdcc2f6507e7a315d2d5 diff --git a/rel/files/emqttd.config b/rel/files/emqttd.config index f49718ff6..9ee5fcb29 100644 --- a/rel/files/emqttd.config +++ b/rel/files/emqttd.config @@ -164,8 +164,11 @@ ]}, %% Plugins {plugins, [ - {dir, "./plugins"}, - {loaded_file, "./data/plugins.config"} + %% Plugin App Library Dir + {plugins_dir, "./plugins"}, + + %% File to store loaded plugin names. + {loaded_file, "./data/loaded_plugins"} ]}, %% Listeners {listeners, [ diff --git a/rel/reltool.config b/rel/reltool.config index 19c11600a..72dfede6e 100644 --- a/rel/reltool.config +++ b/rel/reltool.config @@ -88,5 +88,5 @@ {template, "files/rewrite.config", "etc/rewrite.config"}, {template, "files/clients.config", "etc/clients.config"}, {template, "files/vm.args", "etc/vm.args"}, - {copy, "files/plugins.config", "data/plugins.config"} + {copy, "files/loaded_plugins", "data/loaded_plugins"} ]}. diff --git a/src/emqttd_plugins.erl b/src/emqttd_plugins.erl index f8bcc7696..9f3a9e2ef 100644 --- a/src/emqttd_plugins.erl +++ b/src/emqttd_plugins.erl @@ -20,7 +20,7 @@ %%% SOFTWARE. %%%----------------------------------------------------------------------------- %%% @doc -%%% emqttd plugin admin. +%%% emqttd plugins. %%% %%% @end %%%----------------------------------------------------------------------------- @@ -33,29 +33,42 @@ -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. %% @end %%------------------------------------------------------------------------------ + -spec load() -> list() | {error, any()}. load() -> - case read_loaded() of - {ok, LoadNames} -> - NotFound = LoadNames -- apps(plugin), - case NotFound of - [] -> ok; - NotFound -> lager:error("Cannot find plugins: ~p", [NotFound]) - end, - start_apps(LoadNames -- NotFound -- apps(started)); + case env(loaded_file) of + {ok, File} -> + with_loaded_file(File, fun(Names) -> load_plugins(Names, false) end); + undefined -> + %% No plugins available + ignore + end. + +with_loaded_file(File, SuccFun) -> + case read_loaded(File) of + {ok, Names} -> + SuccFun(Names); {error, Error} -> - lager:error("Read loaded_plugins file error: ~p", [Error]), + lager:error("Failed to read: ~p, error: ~p", [File, Error]), {error, Error} end. -start_apps(Apps) -> - [start_app(App) || App <- Apps]. +load_plugins(Names, Persistent) -> + 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. @@ -63,16 +76,16 @@ start_apps(Apps) -> %%------------------------------------------------------------------------------ -spec unload() -> list() | {error, any()}. unload() -> - case read_loaded() of - {ok, LoadNames} -> - stop_apps(LoadNames); - {error, Error} -> - lager:error("Read loaded_plugins file error: ~p", [Error]), - {error, Error} + case env(loaded_file) of + {ok, File} -> + with_loaded_file(File, fun(Names) -> stop_plugins(Names) end); + undefined -> + ignore end. -stop_apps(Apps) -> - [stop_app(App) || App <- Apps]. +%% stop plugins +stop_plugins(Names) -> + [stop_app(App) || App <- Names]. %%------------------------------------------------------------------------------ %% @doc List all available plugins @@ -80,82 +93,119 @@ stop_apps(Apps) -> %%------------------------------------------------------------------------------ -spec list() -> [mqtt_plugin()]. list() -> - PluginsDir = env(dir), - AppFiles = filelib:wildcard("*/ebin/*.app", PluginsDir), - Plugins = [plugin(filename:join(PluginsDir, AppFile)) || AppFile <- AppFiles], - StartedApps = apps(started), - lists:map(fun(Plugin = #mqtt_plugin{name = Name}) -> - case lists:member(Name, StartedApps) of - true -> Plugin#mqtt_plugin{active = true}; - false -> Plugin - end - end, Plugins). + case env(plugins_dir) of + {ok, PluginsDir} -> + AppFiles = filelib:wildcard("*/ebin/*.app", PluginsDir), + Plugins = [plugin(PluginsDir, AppFile) || AppFile <- AppFiles], + StartedApps = names(started_app), + lists:map(fun(Plugin = #mqtt_plugin{name = Name}) -> + case lists:member(Name, StartedApps) of + true -> Plugin#mqtt_plugin{active = true}; + false -> Plugin + end + end, Plugins); + undefined -> + [] + end. -plugin(AppFile) -> +plugin(PluginsDir, AppFile0) -> + AppFile = filename:join(PluginsDir, AppFile0), {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"), 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 %% @end %%------------------------------------------------------------------------------ + -spec load(atom()) -> ok | {error, any()}. load(PluginName) when is_atom(PluginName) -> - case {lists:member(PluginName, apps(started)), lists:member(PluginName, apps(plugin))} of - {true, _} -> - lager:error("plugin ~p is started", [PluginName]), + case lists:member(PluginName, names(started_app)) of + true -> + lager:error("Plugin ~p is already started", [PluginName]), {error, already_started}; - {false, true} -> - load_plugin(PluginName); - {false, false} -> - lager:error("plugin ~p is not found", [PluginName]), - {error, not_found} + false -> + case find_plugin(PluginName) of + false -> + lager:error("Plugin ~s not found", [PluginName]), + {error, not_found}; + Plugin -> + load_plugin(Plugin, true) + end end. - --spec load_plugin(App :: atom()) -> {ok, list()} | {error, any()}. -load_plugin(PluginName) -> - case start_app(PluginName) of - {ok, Started} -> - plugin_loaded(PluginName), - {ok, Started}; + +load_plugin(#mqtt_plugin{name = Name, config = Config}, Persistent) -> + case load_app(Name, Config) of + ok -> + start_app(Name, fun(App) -> plugin_loaded(App, Persistent) end); {error, Error} -> {error, Error} 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 {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}; {error, {ErrApp, Reason}} -> lager:error("load plugin ~p error, cannot start app ~s for ~p", [App, ErrApp, Reason]), {error, {ErrApp, Reason}} end. +find_plugin(Name) -> + find_plugin(Name, list()). + +find_plugin(Name, Plugins) -> + lists:keyfind(Name, 2, Plugins). + %%------------------------------------------------------------------------------ %% @doc UnLoad One Plugin %% @end %%------------------------------------------------------------------------------ -spec unload(atom()) -> ok | {error, any()}. unload(PluginName) when is_atom(PluginName) -> - case {lists:member(PluginName, apps(started)), lists:member(PluginName, apps(plugin))} of - {false, _} -> - lager:error("plugin ~p is not started", [PluginName]), - {error, not_started}; + case {lists:member(PluginName, names(started_app)), lists:member(PluginName, names(plugin))} of {true, true} -> - unload_plugin(PluginName); + unload_plugin(PluginName, true); + {false, _} -> + lager:error("Plugin ~p is not started", [PluginName]), + {error, not_started}; {true, false} -> lager:error("~s is not a plugin, cannot unload it", [PluginName]), {error, not_found} end. --spec unload_plugin(App :: atom()) -> ok | {error, any()}. -unload_plugin(App) -> +unload_plugin(App, Persistent) -> case stop_app(App) of ok -> - plugin_unloaded(App), ok; + plugin_unloaded(App, Persistent), ok; {error, Reason} -> {error, Reason} end. @@ -174,27 +224,34 @@ stop_app(App) -> %%% Internal functions %%%============================================================================= -apps(plugin) -> - [Name || #mqtt_plugin{name = Name} <- list()]; +names(plugin) -> + names(list()); -apps(started) -> - [Name || {Name, _Descr, _Ver} <- application:which_applications()]. +names(started_app) -> + [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 {ok, Names} -> case lists:member(Name, Names) of - true -> - ignore; false -> %% write file if plugin is loaded - write_loaded(lists:append(Names, Name)) + write_loaded(lists:append(Names, Name)); + true -> + ignore end; {error, Error} -> lager:error("Cannot read loaded plugins: ~p", [Error]) end. -plugin_unloaded(Name) -> +plugin_unloaded(_Name, false) -> + ok; +plugin_unloaded(Name, true) -> case read_loaded() of {ok, Names} -> case lists:member(Name, Names) of @@ -204,11 +261,15 @@ plugin_unloaded(Name) -> lager:error("Cannot find ~s in loaded_file", [Name]) end; {error, Error} -> - lager:error("Cannot read loaded plugins: ~p", [Error]) + lager:error("Cannot read loaded_plugins: ~p", [Error]) end. read_loaded() -> - file:consult(env(loaded_file)). + {ok, File} = env(loaded_file), + read_loaded(File). + +read_loaded(File) -> + file:consult(File). write_loaded(AppNames) -> case file:open(env(loaded_file), [binary, write]) of @@ -220,12 +281,16 @@ write_loaded(AppNames) -> {error, Error} end. -env(dir) -> - proplists:get_value(dir, env(), "./plugins"); - -env(loaded_file) -> - proplists:get_value(loaded_file, env(), "./data/loaded_plugins"). - -env() -> - {ok, PluginsEnv} = application:get_env(emqttd, plugins), PluginsEnv. +env(Name) -> + case application:get_env(emqttd, plugins) of + {ok, PluginsEnv} -> + case proplists:get_value(Name, PluginsEnv) of + undefined -> + undefined; + Val -> + {ok, Val} + end; + undefined -> + undefined + end.