| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- %%%-----------------------------------------------------------------------------
- %%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
- %%%
- %%% Permission is hereby granted, free of charge, to any person obtaining a copy
- %%% of this software and associated documentation files (the "Software"), to deal
- %%% in the Software without restriction, including without limitation the rights
- %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- %%% copies of the Software, and to permit persons to whom the Software is
- %%% furnished to do so, subject to the following conditions:
- %%%
- %%% The above copyright notice and this permission notice shall be included in all
- %%% copies or substantial portions of the Software.
- %%%
- %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- %%% SOFTWARE.
- %%%-----------------------------------------------------------------------------
- %%% @doc
- %%% emqttd plugins.
- %%%
- %%% @end
- %%%-----------------------------------------------------------------------------
- -module(emqttd_plugins).
- -author("Feng Lee <feng@emqtt.io>").
- -include("emqttd.hrl").
- -export([load/0, unload/0]).
- -export([load/1, unload/1]).
- -export([list/0]).
- %%------------------------------------------------------------------------------
- %% @doc Load all plugins when the broker started.
- %% @end
- %%------------------------------------------------------------------------------
- -spec load() -> list() | {error, any()}.
- load() ->
- 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("Failed to read: ~p, error: ~p", [File, Error]),
- {error, Error}
- end.
- 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.
- %% @end
- %%------------------------------------------------------------------------------
- -spec unload() -> list() | {error, any()}.
- unload() ->
- case env(loaded_file) of
- {ok, File} ->
- with_loaded_file(File, fun(Names) -> stop_plugins(Names) end);
- undefined ->
- ignore
- end.
- %% stop plugins
- stop_plugins(Names) ->
- [stop_app(App) || App <- Names].
- %%------------------------------------------------------------------------------
- %% @doc List all available plugins
- %% @end
- %%------------------------------------------------------------------------------
- -spec list() -> [mqtt_plugin()].
- list() ->
- 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(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, 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, names(started_app)) of
- true ->
- lager:error("Plugin ~p is already started", [PluginName]),
- {error, already_started};
- false ->
- case find_plugin(PluginName) of
- false ->
- lager:error("Plugin ~s not found", [PluginName]),
- {error, not_found};
- Plugin ->
- load_plugin(Plugin, true)
- end
- end.
- 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.
- load_app(App, Config) ->
- case application:load(App) of
- ok ->
- set_config(Config);
- {error, {already_loaded, App}} ->
- 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", [Started]),
- lager:info("Load plugin ~p successfully", [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, names(started_app)), lists:member(PluginName, names(plugin))} of
- {true, true} ->
- 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.
- unload_plugin(App, Persistent) ->
- case stop_app(App) of
- ok ->
- plugin_unloaded(App, Persistent), ok;
- {error, Reason} ->
- {error, Reason}
- end.
-
- stop_app(App) ->
- case application:stop(App) of
- ok ->
- lager:info("stop plugin ~p successfully~n", [App]), ok;
- {error, {not_started, App}} ->
- lager:error("plugin ~p is not started~n", [App]), ok;
- {error, Reason} ->
- lager:error("stop plugin ~p error: ~p", [App]), {error, Reason}
- end.
- %%%=============================================================================
- %%% Internal functions
- %%%=============================================================================
- names(plugin) ->
- names(list());
- names(started_app) ->
- [Name || {Name, _Descr, _Ver} <- application:which_applications()];
- 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
- false ->
- %% write file if plugin is loaded
- write_loaded(lists:append(Names, [Name]));
- true ->
- ignore
- end;
- {error, Error} ->
- lager:error("Cannot read loaded plugins: ~p", [Error])
- end.
- plugin_unloaded(_Name, false) ->
- ok;
- plugin_unloaded(Name, true) ->
- case read_loaded() of
- {ok, Names} ->
- case lists:member(Name, Names) of
- true ->
- write_loaded(lists:delete(Name, Names));
- false ->
- lager:error("Cannot find ~s in loaded_file", [Name])
- end;
- {error, Error} ->
- lager:error("Cannot read loaded_plugins: ~p", [Error])
- end.
- read_loaded() ->
- {ok, File} = env(loaded_file),
- read_loaded(File).
- read_loaded(File) ->
- file:consult(File).
- write_loaded(AppNames) ->
- {ok, File} = env(loaded_file),
- case file:open(File, [binary, write]) of
- {ok, Fd} ->
- lists:foreach(fun(Name) ->
- file:write(Fd, iolist_to_binary(io_lib:format("~s.~n", [Name])))
- end, AppNames);
- {error, Error} ->
- lager:error("Open File ~p Error: ~p", [File, Error]),
- {error, Error}
- end.
- 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.
|