plugins
This commit is contained in:
parent
31a85ff0d6
commit
c6ccacd990
|
@ -149,6 +149,7 @@
|
|||
name,
|
||||
version,
|
||||
descr,
|
||||
config,
|
||||
active = false
|
||||
}).
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 892810dbc853ba147f9acabecd52bb51218275e2
|
||||
Subproject commit 04baf44a465c1513e75dfdcc2f6507e7a315d2d5
|
|
@ -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, [
|
||||
|
|
|
@ -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"}
|
||||
]}.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue