emqttd_plugins.erl 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. %%%-----------------------------------------------------------------------------
  2. %%% Copyright (c) 2012-2015 eMQTT.IO, All Rights Reserved.
  3. %%%
  4. %%% Permission is hereby granted, free of charge, to any person obtaining a copy
  5. %%% of this software and associated documentation files (the "Software"), to deal
  6. %%% in the Software without restriction, including without limitation the rights
  7. %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. %%% copies of the Software, and to permit persons to whom the Software is
  9. %%% furnished to do so, subject to the following conditions:
  10. %%%
  11. %%% The above copyright notice and this permission notice shall be included in all
  12. %%% copies or substantial portions of the Software.
  13. %%%
  14. %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. %%% SOFTWARE.
  21. %%%-----------------------------------------------------------------------------
  22. %%% @doc
  23. %%% emqttd plugins.
  24. %%%
  25. %%% @end
  26. %%%-----------------------------------------------------------------------------
  27. -module(emqttd_plugins).
  28. -author("Feng Lee <feng@emqtt.io>").
  29. -include("emqttd.hrl").
  30. -export([load/0, unload/0]).
  31. -export([load/1, unload/1]).
  32. -export([list/0]).
  33. %%------------------------------------------------------------------------------
  34. %% @doc Load all plugins when the broker started.
  35. %% @end
  36. %%------------------------------------------------------------------------------
  37. -spec load() -> list() | {error, any()}.
  38. load() ->
  39. case env(loaded_file) of
  40. {ok, File} ->
  41. with_loaded_file(File, fun(Names) -> load_plugins(Names, false) end);
  42. undefined ->
  43. %% No plugins available
  44. ignore
  45. end.
  46. with_loaded_file(File, SuccFun) ->
  47. case read_loaded(File) of
  48. {ok, Names} ->
  49. SuccFun(Names);
  50. {error, Error} ->
  51. lager:error("Failed to read: ~p, error: ~p", [File, Error]),
  52. {error, Error}
  53. end.
  54. load_plugins(Names, Persistent) ->
  55. Plugins = list(), NotFound = Names -- names(Plugins),
  56. case NotFound of
  57. [] -> ok;
  58. NotFound -> lager:error("Cannot find plugins: ~p", [NotFound])
  59. end,
  60. NeedToLoad = Names -- NotFound -- names(started_app),
  61. [load_plugin(find_plugin(Name, Plugins), Persistent) || Name <- NeedToLoad].
  62. %%------------------------------------------------------------------------------
  63. %% @doc Unload all plugins before broker stopped.
  64. %% @end
  65. %%------------------------------------------------------------------------------
  66. -spec unload() -> list() | {error, any()}.
  67. unload() ->
  68. case env(loaded_file) of
  69. {ok, File} ->
  70. with_loaded_file(File, fun(Names) -> stop_plugins(Names) end);
  71. undefined ->
  72. ignore
  73. end.
  74. %% stop plugins
  75. stop_plugins(Names) ->
  76. [stop_app(App) || App <- Names].
  77. %%------------------------------------------------------------------------------
  78. %% @doc List all available plugins
  79. %% @end
  80. %%------------------------------------------------------------------------------
  81. -spec list() -> [mqtt_plugin()].
  82. list() ->
  83. case env(plugins_dir) of
  84. {ok, PluginsDir} ->
  85. AppFiles = filelib:wildcard("*/ebin/*.app", PluginsDir),
  86. Plugins = [plugin(PluginsDir, AppFile) || AppFile <- AppFiles],
  87. StartedApps = names(started_app),
  88. lists:map(fun(Plugin = #mqtt_plugin{name = Name}) ->
  89. case lists:member(Name, StartedApps) of
  90. true -> Plugin#mqtt_plugin{active = true};
  91. false -> Plugin
  92. end
  93. end, Plugins);
  94. undefined ->
  95. []
  96. end.
  97. plugin(PluginsDir, AppFile0) ->
  98. AppFile = filename:join(PluginsDir, AppFile0),
  99. {ok, [{application, Name, Attrs}]} = file:consult(AppFile),
  100. CfgFile = filename:join([PluginsDir, Name, "etc/plugin.config"]),
  101. AppsEnv1 =
  102. case filelib:is_file(CfgFile) of
  103. true ->
  104. {ok, [AppsEnv]} = file:consult(CfgFile),
  105. AppsEnv;
  106. false ->
  107. []
  108. end,
  109. Ver = proplists:get_value(vsn, Attrs, "0"),
  110. Descr = proplists:get_value(description, Attrs, ""),
  111. #mqtt_plugin{name = Name, version = Ver, config = AppsEnv1, descr = Descr}.
  112. %%------------------------------------------------------------------------------
  113. %% @doc Load One Plugin
  114. %% @end
  115. %%------------------------------------------------------------------------------
  116. -spec load(atom()) -> ok | {error, any()}.
  117. load(PluginName) when is_atom(PluginName) ->
  118. case lists:member(PluginName, names(started_app)) of
  119. true ->
  120. lager:error("Plugin ~p is already started", [PluginName]),
  121. {error, already_started};
  122. false ->
  123. case find_plugin(PluginName) of
  124. false ->
  125. lager:error("Plugin ~s not found", [PluginName]),
  126. {error, not_found};
  127. Plugin ->
  128. load_plugin(Plugin, true)
  129. end
  130. end.
  131. load_plugin(#mqtt_plugin{name = Name, config = Config}, Persistent) ->
  132. case load_app(Name, Config) of
  133. ok ->
  134. start_app(Name, fun(App) -> plugin_loaded(App, Persistent) end);
  135. {error, Error} ->
  136. {error, Error}
  137. end.
  138. load_app(App, Config) ->
  139. case application:load(App) of
  140. ok ->
  141. set_config(Config);
  142. {error, {already_loaded, App}} ->
  143. set_config(Config);
  144. {error, Error} ->
  145. {error, Error}
  146. end.
  147. %% This trick is awesome:)
  148. set_config([]) ->
  149. ok;
  150. set_config([{AppName, Envs} | Config]) ->
  151. [application:set_env(AppName, Par, Val) || {Par, Val} <- Envs],
  152. set_config(Config).
  153. start_app(App, SuccFun) ->
  154. case application:ensure_all_started(App) of
  155. {ok, Started} ->
  156. lager:info("Started Apps: ~p", [Started]),
  157. lager:info("Load plugin ~p successfully", [App]),
  158. SuccFun(App),
  159. {ok, Started};
  160. {error, {ErrApp, Reason}} ->
  161. lager:error("load plugin ~p error, cannot start app ~s for ~p", [App, ErrApp, Reason]),
  162. {error, {ErrApp, Reason}}
  163. end.
  164. find_plugin(Name) ->
  165. find_plugin(Name, list()).
  166. find_plugin(Name, Plugins) ->
  167. lists:keyfind(Name, 2, Plugins).
  168. %%------------------------------------------------------------------------------
  169. %% @doc UnLoad One Plugin
  170. %% @end
  171. %%------------------------------------------------------------------------------
  172. -spec unload(atom()) -> ok | {error, any()}.
  173. unload(PluginName) when is_atom(PluginName) ->
  174. case {lists:member(PluginName, names(started_app)), lists:member(PluginName, names(plugin))} of
  175. {true, true} ->
  176. unload_plugin(PluginName, true);
  177. {false, _} ->
  178. lager:error("Plugin ~p is not started", [PluginName]),
  179. {error, not_started};
  180. {true, false} ->
  181. lager:error("~s is not a plugin, cannot unload it", [PluginName]),
  182. {error, not_found}
  183. end.
  184. unload_plugin(App, Persistent) ->
  185. case stop_app(App) of
  186. ok ->
  187. plugin_unloaded(App, Persistent), ok;
  188. {error, Reason} ->
  189. {error, Reason}
  190. end.
  191. stop_app(App) ->
  192. case application:stop(App) of
  193. ok ->
  194. lager:info("stop plugin ~p successfully~n", [App]), ok;
  195. {error, {not_started, App}} ->
  196. lager:error("plugin ~p is not started~n", [App]), ok;
  197. {error, Reason} ->
  198. lager:error("stop plugin ~p error: ~p", [App]), {error, Reason}
  199. end.
  200. %%%=============================================================================
  201. %%% Internal functions
  202. %%%=============================================================================
  203. names(plugin) ->
  204. names(list());
  205. names(started_app) ->
  206. [Name || {Name, _Descr, _Ver} <- application:which_applications()];
  207. names(Plugins) ->
  208. [Name || #mqtt_plugin{name = Name} <- Plugins].
  209. plugin_loaded(_Name, false) ->
  210. ok;
  211. plugin_loaded(Name, true) ->
  212. case read_loaded() of
  213. {ok, Names} ->
  214. case lists:member(Name, Names) of
  215. false ->
  216. %% write file if plugin is loaded
  217. write_loaded(lists:append(Names, [Name]));
  218. true ->
  219. ignore
  220. end;
  221. {error, Error} ->
  222. lager:error("Cannot read loaded plugins: ~p", [Error])
  223. end.
  224. plugin_unloaded(_Name, false) ->
  225. ok;
  226. plugin_unloaded(Name, true) ->
  227. case read_loaded() of
  228. {ok, Names} ->
  229. case lists:member(Name, Names) of
  230. true ->
  231. write_loaded(lists:delete(Name, Names));
  232. false ->
  233. lager:error("Cannot find ~s in loaded_file", [Name])
  234. end;
  235. {error, Error} ->
  236. lager:error("Cannot read loaded_plugins: ~p", [Error])
  237. end.
  238. read_loaded() ->
  239. {ok, File} = env(loaded_file),
  240. read_loaded(File).
  241. read_loaded(File) ->
  242. file:consult(File).
  243. write_loaded(AppNames) ->
  244. {ok, File} = env(loaded_file),
  245. case file:open(File, [binary, write]) of
  246. {ok, Fd} ->
  247. lists:foreach(fun(Name) ->
  248. file:write(Fd, iolist_to_binary(io_lib:format("~s.~n", [Name])))
  249. end, AppNames);
  250. {error, Error} ->
  251. lager:error("Open File ~p Error: ~p", [File, Error]),
  252. {error, Error}
  253. end.
  254. env(Name) ->
  255. case application:get_env(emqttd, plugins) of
  256. {ok, PluginsEnv} ->
  257. case proplists:get_value(Name, PluginsEnv) of
  258. undefined ->
  259. undefined;
  260. Val ->
  261. {ok, Val}
  262. end;
  263. undefined ->
  264. undefined
  265. end.