|
|
@@ -21,8 +21,6 @@
|
|
|
|
|
|
-logger_header("[Plugins]").
|
|
|
|
|
|
--export([init/0]).
|
|
|
-
|
|
|
-export([ load/0
|
|
|
, load/1
|
|
|
, unload/0
|
|
|
@@ -30,8 +28,6 @@
|
|
|
, reload/1
|
|
|
, list/0
|
|
|
, find_plugin/1
|
|
|
- , generate_configs/1
|
|
|
- , apply_configs/1
|
|
|
]).
|
|
|
|
|
|
-export([funlog/2]).
|
|
|
@@ -41,35 +37,14 @@
|
|
|
-compile(nowarn_export_all).
|
|
|
-endif.
|
|
|
|
|
|
--dialyzer({no_match, [ plugin_loaded/2
|
|
|
- , plugin_unloaded/2
|
|
|
- ]}).
|
|
|
-
|
|
|
%%--------------------------------------------------------------------
|
|
|
%% APIs
|
|
|
%%--------------------------------------------------------------------
|
|
|
|
|
|
-%% @doc Init plugins' config
|
|
|
--spec(init() -> ok).
|
|
|
-init() ->
|
|
|
- case emqx:get_env(plugins_etc_dir) of
|
|
|
- undefined -> ok;
|
|
|
- PluginsEtc ->
|
|
|
- CfgFiles = [filename:join(PluginsEtc, File) ||
|
|
|
- File <- filelib:wildcard("*.config", PluginsEtc)],
|
|
|
- lists:foreach(fun init_config/1, CfgFiles)
|
|
|
- end.
|
|
|
-
|
|
|
%% @doc Load all plugins when the broker started.
|
|
|
-spec(load() -> ok | ignore | {error, term()}).
|
|
|
load() ->
|
|
|
- ok = load_ext_plugins(emqx:get_env(expand_plugins_dir)),
|
|
|
- case emqx:get_env(plugins_loaded_file) of
|
|
|
- undefined -> ignore; %% No plugins available
|
|
|
- File ->
|
|
|
- _ = ensure_file(File),
|
|
|
- with_loaded_file(File, fun(Names) -> load_plugins(Names, false) end)
|
|
|
- end.
|
|
|
+ ok = load_ext_plugins(emqx:get_env(expand_plugins_dir)).
|
|
|
|
|
|
%% @doc Load a Plugin
|
|
|
-spec(load(atom()) -> ok | {error, term()}).
|
|
|
@@ -82,17 +57,13 @@ load(PluginName) when is_atom(PluginName) ->
|
|
|
?LOG(notice, "Plugin ~s is already started", [PluginName]),
|
|
|
{error, already_started};
|
|
|
{_, false} ->
|
|
|
- load_plugin(PluginName, true)
|
|
|
+ load_plugin(PluginName)
|
|
|
end.
|
|
|
|
|
|
%% @doc Unload all plugins before broker stopped.
|
|
|
--spec(unload() -> list() | {error, term()}).
|
|
|
+-spec(unload() -> ok).
|
|
|
unload() ->
|
|
|
- case emqx:get_env(plugins_loaded_file) of
|
|
|
- undefined -> ignore;
|
|
|
- File ->
|
|
|
- with_loaded_file(File, fun stop_plugins/1)
|
|
|
- end.
|
|
|
+ stop_plugins(list()).
|
|
|
|
|
|
%% @doc UnLoad a Plugin
|
|
|
-spec(unload(atom()) -> ok | {error, term()}).
|
|
|
@@ -105,7 +76,7 @@ unload(PluginName) when is_atom(PluginName) ->
|
|
|
?LOG(error, "Plugin ~s is not started", [PluginName]),
|
|
|
{error, not_started};
|
|
|
{_, _} ->
|
|
|
- unload_plugin(PluginName, true)
|
|
|
+ unload_plugin(PluginName)
|
|
|
end.
|
|
|
|
|
|
reload(PluginName) when is_atom(PluginName)->
|
|
|
@@ -126,8 +97,8 @@ reload(PluginName) when is_atom(PluginName)->
|
|
|
-spec(list() -> [emqx_types:plugin()]).
|
|
|
list() ->
|
|
|
StartedApps = names(started_app),
|
|
|
- lists:map(fun({Name, _, [Type| _]}) ->
|
|
|
- Plugin = plugin(Name, Type),
|
|
|
+ lists:map(fun({Name, _, _}) ->
|
|
|
+ Plugin = plugin(Name),
|
|
|
case lists:member(Name, StartedApps) of
|
|
|
true -> Plugin#plugin{active = true};
|
|
|
false -> Plugin
|
|
|
@@ -144,12 +115,6 @@ find_plugin(Name, Plugins) ->
|
|
|
%% Internal functions
|
|
|
%%--------------------------------------------------------------------
|
|
|
|
|
|
-init_config(CfgFile) ->
|
|
|
- {ok, [AppsEnv]} = file:consult(CfgFile),
|
|
|
- lists:foreach(fun({App, Envs}) ->
|
|
|
- [application:set_env(App, Par, Val) || {Par, Val} <- Envs]
|
|
|
- end, AppsEnv).
|
|
|
-
|
|
|
%% load external plugins which are placed in etc/plugins dir
|
|
|
load_ext_plugins(undefined) -> ok;
|
|
|
load_ext_plugins(Dir) ->
|
|
|
@@ -173,15 +138,15 @@ load_ext_plugin(PluginDir) ->
|
|
|
?LOG(alert, "plugin_app_file_not_found: ~s", [AppFile]),
|
|
|
error({plugin_app_file_not_found, AppFile})
|
|
|
end,
|
|
|
- ok = load_plugin_app(AppName, Ebin),
|
|
|
- try
|
|
|
- ok = generate_configs(AppName, PluginDir)
|
|
|
- catch
|
|
|
- throw : {conf_file_not_found, ConfFile} ->
|
|
|
- %% this is maybe a dependency of an external plugin
|
|
|
- ?LOG(debug, "config_load_error_ignored for app=~p, path=~s", [AppName, ConfFile]),
|
|
|
- ok
|
|
|
- end.
|
|
|
+ ok = load_plugin_app(AppName, Ebin).
|
|
|
+ % try
|
|
|
+ % ok = generate_configs(AppName, PluginDir)
|
|
|
+ % catch
|
|
|
+ % throw : {conf_file_not_found, ConfFile} ->
|
|
|
+ % %% this is maybe a dependency of an external plugin
|
|
|
+ % ?LOG(debug, "config_load_error_ignored for app=~p, path=~s", [AppName, ConfFile]),
|
|
|
+ % ok
|
|
|
+ % end.
|
|
|
|
|
|
load_plugin_app(AppName, Ebin) ->
|
|
|
_ = code:add_patha(Ebin),
|
|
|
@@ -199,57 +164,24 @@ load_plugin_app(AppName, Ebin) ->
|
|
|
{error, {already_loaded, _}} -> ok
|
|
|
end.
|
|
|
|
|
|
-ensure_file(File) ->
|
|
|
- case filelib:is_file(File) of false -> write_loaded([]); true -> ok end.
|
|
|
-
|
|
|
-with_loaded_file(File, SuccFun) ->
|
|
|
- case read_loaded(File) of
|
|
|
- {ok, Names0} ->
|
|
|
- Names = filter_plugins(Names0),
|
|
|
- SuccFun(Names);
|
|
|
- {error, Error} ->
|
|
|
- ?LOG(alert, "Failed to read: ~p, error: ~p", [File, Error]),
|
|
|
- {error, Error}
|
|
|
- end.
|
|
|
-
|
|
|
-filter_plugins(Names) ->
|
|
|
- lists:filtermap(fun(Name1) when is_atom(Name1) -> {true, Name1};
|
|
|
- ({Name1, true}) -> {true, Name1};
|
|
|
- ({_Name1, false}) -> false
|
|
|
- end, Names).
|
|
|
-
|
|
|
-load_plugins(Names, Persistent) ->
|
|
|
- Plugins = list(),
|
|
|
- NotFound = Names -- names(Plugins),
|
|
|
- case NotFound of
|
|
|
- [] -> ok;
|
|
|
- NotFound -> ?LOG(alert, "cannot_find_plugins: ~p", [NotFound])
|
|
|
- end,
|
|
|
- NeedToLoad = Names -- NotFound -- names(started_app),
|
|
|
- lists:foreach(fun(Name) ->
|
|
|
- Plugin = find_plugin(Name, Plugins),
|
|
|
- load_plugin(Plugin#plugin.name, Persistent)
|
|
|
- end, NeedToLoad).
|
|
|
-
|
|
|
%% Stop plugins
|
|
|
-stop_plugins(Names) ->
|
|
|
- _ = [stop_app(App) || App <- Names],
|
|
|
+stop_plugins(Plugins) ->
|
|
|
+ _ = [stop_app(Plugin#plugin.name) || Plugin <- Plugins],
|
|
|
ok.
|
|
|
|
|
|
-plugin(AppName, Type) ->
|
|
|
+plugin(AppName) ->
|
|
|
case application:get_all_key(AppName) of
|
|
|
{ok, Attrs} ->
|
|
|
Descr = proplists:get_value(description, Attrs, ""),
|
|
|
- #plugin{name = AppName, descr = Descr, type = plugin_type(Type)};
|
|
|
+ #plugin{name = AppName, descr = Descr};
|
|
|
undefined -> error({plugin_not_found, AppName})
|
|
|
end.
|
|
|
|
|
|
-load_plugin(Name, Persistent) ->
|
|
|
+load_plugin(Name) ->
|
|
|
try
|
|
|
- ok = ?MODULE:generate_configs(Name),
|
|
|
case load_app(Name) of
|
|
|
ok ->
|
|
|
- start_app(Name, fun(App) -> plugin_loaded(App, Persistent) end);
|
|
|
+ start_app(Name);
|
|
|
{error, Error0} ->
|
|
|
{error, Error0}
|
|
|
end
|
|
|
@@ -268,22 +200,21 @@ load_app(App) ->
|
|
|
{error, Error}
|
|
|
end.
|
|
|
|
|
|
-start_app(App, SuccFun) ->
|
|
|
+start_app(App) ->
|
|
|
case application:ensure_all_started(App) of
|
|
|
{ok, Started} ->
|
|
|
?LOG(info, "Started plugins: ~p", [Started]),
|
|
|
?LOG(info, "Load plugin ~s successfully", [App]),
|
|
|
- _ = SuccFun(App),
|
|
|
ok;
|
|
|
{error, {ErrApp, Reason}} ->
|
|
|
?LOG(error, "Load plugin ~s failed, cannot start plugin ~s for ~0p", [App, ErrApp, Reason]),
|
|
|
{error, {ErrApp, Reason}}
|
|
|
end.
|
|
|
|
|
|
-unload_plugin(App, Persistent) ->
|
|
|
+unload_plugin(App) ->
|
|
|
case stop_app(App) of
|
|
|
ok ->
|
|
|
- _ = plugin_unloaded(App, Persistent), ok;
|
|
|
+ ok;
|
|
|
{error, Reason} ->
|
|
|
{error, Reason}
|
|
|
end.
|
|
|
@@ -307,133 +238,5 @@ names(started_app) ->
|
|
|
names(Plugins) ->
|
|
|
[Name || #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
|
|
|
- false ->
|
|
|
- %% write file if plugin is loaded
|
|
|
- write_loaded(lists:append(Names, [{Name, true}]));
|
|
|
- true ->
|
|
|
- ignore
|
|
|
- end;
|
|
|
- {error, Error} ->
|
|
|
- ?LOG(error, "Cannot read loaded plugins: ~p", [Error])
|
|
|
- end.
|
|
|
-
|
|
|
-plugin_unloaded(_Name, false) ->
|
|
|
- ok;
|
|
|
-plugin_unloaded(Name, true) ->
|
|
|
- case read_loaded() of
|
|
|
- {ok, Names0} ->
|
|
|
- Names = filter_plugins(Names0),
|
|
|
- case lists:member(Name, Names) of
|
|
|
- true ->
|
|
|
- write_loaded(lists:delete(Name, Names));
|
|
|
- false ->
|
|
|
- ?LOG(error, "Cannot find ~s in loaded_file", [Name])
|
|
|
- end;
|
|
|
- {error, Error} ->
|
|
|
- ?LOG(error, "Cannot read loaded_plugins: ~p", [Error])
|
|
|
- end.
|
|
|
-
|
|
|
-read_loaded() ->
|
|
|
- case emqx:get_env(plugins_loaded_file) of
|
|
|
- undefined -> {error, not_found};
|
|
|
- File -> read_loaded(File)
|
|
|
- end.
|
|
|
-
|
|
|
-read_loaded(File) -> file:consult(File).
|
|
|
-
|
|
|
-write_loaded(AppNames) ->
|
|
|
- FilePath = emqx:get_env(plugins_loaded_file),
|
|
|
- case file:write_file(FilePath, [io_lib:format("~p.~n", [Name]) || Name <- AppNames]) of
|
|
|
- ok -> ok;
|
|
|
- {error, Error} ->
|
|
|
- ?LOG(error, "Write File ~p Error: ~p", [FilePath, Error]),
|
|
|
- {error, Error}
|
|
|
- end.
|
|
|
-
|
|
|
-plugin_type(auth) -> auth;
|
|
|
-plugin_type(protocol) -> protocol;
|
|
|
-plugin_type(backend) -> backend;
|
|
|
-plugin_type(bridge) -> bridge;
|
|
|
-plugin_type(_) -> feature.
|
|
|
-
|
|
|
-
|
|
|
funlog(Key, Value) ->
|
|
|
?LOG(info, "~s = ~p", [string:join(Key, "."), Value]).
|
|
|
-
|
|
|
-generate_configs(App) ->
|
|
|
- PluginConfDir = emqx:get_env(plugins_etc_dir),
|
|
|
- PluginSchemaDir = code:priv_dir(App),
|
|
|
- generate_configs(App, PluginConfDir, PluginSchemaDir).
|
|
|
-
|
|
|
-generate_configs(App, PluginDir) ->
|
|
|
- PluginConfDir = filename:join([PluginDir, "etc"]),
|
|
|
- PluginSchemaDir = filename:join([PluginDir, "priv"]),
|
|
|
- generate_configs(App, PluginConfDir, PluginSchemaDir).
|
|
|
-
|
|
|
-generate_configs(App, PluginConfDir, PluginSchemaDir) ->
|
|
|
- ConfigFile = filename:join([PluginConfDir, App]) ++ ".config",
|
|
|
- case filelib:is_file(ConfigFile) of
|
|
|
- true ->
|
|
|
- {ok, [Configs]} = file:consult(ConfigFile),
|
|
|
- apply_configs(Configs);
|
|
|
- false ->
|
|
|
- SchemaFile = filename:join([PluginSchemaDir, App]) ++ ".schema",
|
|
|
- case filelib:is_file(SchemaFile) of
|
|
|
- true ->
|
|
|
- AppsEnv = do_generate_configs(App),
|
|
|
- apply_configs(AppsEnv);
|
|
|
- false ->
|
|
|
- SchemaMod = lists:concat([App, "_schema"]),
|
|
|
- ConfName = filename:join([PluginConfDir, App]) ++ ".conf",
|
|
|
- SchemaFile1 = filename:join([code:lib_dir(App), "ebin", SchemaMod]) ++ ".beam",
|
|
|
- do_generate_hocon_configs(App, ConfName, SchemaFile1)
|
|
|
- end
|
|
|
- end.
|
|
|
-
|
|
|
-do_generate_configs(App) ->
|
|
|
- Name1 = filename:join([emqx:get_env(plugins_etc_dir), App]) ++ ".conf",
|
|
|
- Name2 = filename:join([code:lib_dir(App), "etc", App]) ++ ".conf",
|
|
|
- ConfFile = case {filelib:is_file(Name1), filelib:is_file(Name2)} of
|
|
|
- {true, _} -> Name1;
|
|
|
- {false, true} -> Name2;
|
|
|
- {false, false} -> error({config_not_found, [Name1, Name2]})
|
|
|
- end,
|
|
|
- SchemaFile = filename:join([code:priv_dir(App), App]) ++ ".schema",
|
|
|
- case filelib:is_file(SchemaFile) of
|
|
|
- true ->
|
|
|
- Schema = cuttlefish_schema:files([SchemaFile]),
|
|
|
- Conf = cuttlefish_conf:file(ConfFile),
|
|
|
- cuttlefish_generator:map(Schema, Conf, undefined, fun ?MODULE:funlog/2);
|
|
|
- false ->
|
|
|
- error({schema_not_found, SchemaFile})
|
|
|
- end.
|
|
|
-
|
|
|
-do_generate_hocon_configs(App, ConfName, SchemaFile) ->
|
|
|
- SchemaMod = lists:concat([App, "_schema"]),
|
|
|
- case {filelib:is_file(ConfName), filelib:is_file(SchemaFile)} of
|
|
|
- {true, true} ->
|
|
|
- {ok, RawConfig} = hocon:load(ConfName, #{format => richmap}),
|
|
|
- _ = hocon_schema:check(list_to_atom(SchemaMod), RawConfig, #{atom_key => true,
|
|
|
- return_plain => true}),
|
|
|
- ok;
|
|
|
- % emqx_config:update_config([App], Config);
|
|
|
- {true, false} ->
|
|
|
- error({schema_not_found, [SchemaFile]});
|
|
|
- {false, true} ->
|
|
|
- error({config_not_found, [ConfName]});
|
|
|
- {false, false} ->
|
|
|
- error({conf_and_schema_not_found, [ConfName, SchemaFile]})
|
|
|
- end.
|
|
|
-
|
|
|
-apply_configs([]) ->
|
|
|
- ok;
|
|
|
-apply_configs([{App, Config} | More]) ->
|
|
|
- lists:foreach(fun({Key, _}) -> application:unset_env(App, Key) end, application:get_all_env(App)),
|
|
|
- lists:foreach(fun({Key, Val}) -> application:set_env(App, Key, Val) end, Config),
|
|
|
- apply_configs(More).
|