emqttd_plugins.erl 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2012-2016 Feng Lee <feng@emqtt.io>.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqttd_plugins).
  17. -include("emqttd.hrl").
  18. -export([load/0, unload/0]).
  19. -export([load/1, unload/1]).
  20. -export([list/0]).
  21. %% @doc Load all plugins when the broker started.
  22. -spec load() -> list() | {error, any()}.
  23. load() ->
  24. case env(loaded_file) of
  25. {ok, File} ->
  26. with_loaded_file(File, fun(Names) -> load_plugins(Names, false) end);
  27. undefined ->
  28. %% No plugins available
  29. ignore
  30. end.
  31. with_loaded_file(File, SuccFun) ->
  32. case read_loaded(File) of
  33. {ok, Names} ->
  34. SuccFun(Names);
  35. {error, Error} ->
  36. lager:error("Failed to read: ~p, error: ~p", [File, Error]),
  37. {error, Error}
  38. end.
  39. load_plugins(Names, Persistent) ->
  40. Plugins = list(), NotFound = Names -- names(Plugins),
  41. case NotFound of
  42. [] -> ok;
  43. NotFound -> lager:error("Cannot find plugins: ~p", [NotFound])
  44. end,
  45. NeedToLoad = Names -- NotFound -- names(started_app),
  46. [load_plugin(find_plugin(Name, Plugins), Persistent) || Name <- NeedToLoad].
  47. %% @doc Unload all plugins before broker stopped.
  48. -spec unload() -> list() | {error, any()}.
  49. unload() ->
  50. case env(loaded_file) of
  51. {ok, File} ->
  52. with_loaded_file(File, fun stop_plugins/1);
  53. undefined ->
  54. ignore
  55. end.
  56. %% stop plugins
  57. stop_plugins(Names) ->
  58. [stop_app(App) || App <- Names].
  59. %% @doc List all available plugins
  60. -spec list() -> [mqtt_plugin()].
  61. list() ->
  62. case env(plugins_dir) of
  63. {ok, PluginsDir} ->
  64. AppFiles = filelib:wildcard("*/ebin/*.app", PluginsDir),
  65. Plugins = [plugin(PluginsDir, AppFile) || AppFile <- AppFiles],
  66. StartedApps = names(started_app),
  67. lists:map(fun(Plugin = #mqtt_plugin{name = Name}) ->
  68. case lists:member(Name, StartedApps) of
  69. true -> Plugin#mqtt_plugin{active = true};
  70. false -> Plugin
  71. end
  72. end, Plugins);
  73. undefined ->
  74. []
  75. end.
  76. plugin(PluginsDir, AppFile0) ->
  77. AppFile = filename:join(PluginsDir, AppFile0),
  78. {ok, [{application, Name, Attrs}]} = file:consult(AppFile),
  79. CfgFile = filename:join([PluginsDir, Name, "etc/plugin.config"]),
  80. AppsEnv1 =
  81. case filelib:is_file(CfgFile) of
  82. true ->
  83. {ok, [AppsEnv]} = file:consult(CfgFile),
  84. AppsEnv;
  85. false ->
  86. []
  87. end,
  88. Ver = proplists:get_value(vsn, Attrs, "0"),
  89. Descr = proplists:get_value(description, Attrs, ""),
  90. #mqtt_plugin{name = Name, version = Ver, config = AppsEnv1, descr = Descr}.
  91. %% @doc Load One Plugin
  92. -spec load(atom()) -> ok | {error, any()}.
  93. load(PluginName) when is_atom(PluginName) ->
  94. case lists:member(PluginName, names(started_app)) of
  95. true ->
  96. lager:error("Plugin ~p is already started", [PluginName]),
  97. {error, already_started};
  98. false ->
  99. case find_plugin(PluginName) of
  100. false ->
  101. lager:error("Plugin ~s not found", [PluginName]),
  102. {error, not_found};
  103. Plugin ->
  104. load_plugin(Plugin, true)
  105. end
  106. end.
  107. load_plugin(#mqtt_plugin{name = Name, config = Config}, Persistent) ->
  108. case load_app(Name, Config) of
  109. ok ->
  110. start_app(Name, fun(App) -> plugin_loaded(App, Persistent) end);
  111. {error, Error} ->
  112. {error, Error}
  113. end.
  114. load_app(App, Config) ->
  115. case application:load(App) of
  116. ok ->
  117. set_config(Config);
  118. {error, {already_loaded, App}} ->
  119. set_config(Config);
  120. {error, Error} ->
  121. {error, Error}
  122. end.
  123. %% This trick is awesome:)
  124. set_config([]) ->
  125. ok;
  126. set_config([{AppName, Envs} | Config]) ->
  127. [application:set_env(AppName, Par, Val) || {Par, Val} <- Envs],
  128. set_config(Config).
  129. start_app(App, SuccFun) ->
  130. case application:ensure_all_started(App) of
  131. {ok, Started} ->
  132. lager:info("started Apps: ~p", [Started]),
  133. lager:info("load plugin ~p successfully", [App]),
  134. SuccFun(App),
  135. {ok, Started};
  136. {error, {ErrApp, Reason}} ->
  137. lager:error("load plugin ~p error, cannot start app ~s for ~p", [App, ErrApp, Reason]),
  138. {error, {ErrApp, Reason}}
  139. end.
  140. find_plugin(Name) ->
  141. find_plugin(Name, list()).
  142. find_plugin(Name, Plugins) ->
  143. lists:keyfind(Name, 2, Plugins).
  144. %% @doc UnLoad One Plugin
  145. -spec unload(atom()) -> ok | {error, any()}.
  146. unload(PluginName) when is_atom(PluginName) ->
  147. case {lists:member(PluginName, names(started_app)), lists:member(PluginName, names(plugin))} of
  148. {true, true} ->
  149. unload_plugin(PluginName, true);
  150. {false, _} ->
  151. lager:error("Plugin ~p is not started", [PluginName]),
  152. {error, not_started};
  153. {true, false} ->
  154. lager:error("~s is not a plugin, cannot unload it", [PluginName]),
  155. {error, not_found}
  156. end.
  157. unload_plugin(App, Persistent) ->
  158. case stop_app(App) of
  159. ok ->
  160. plugin_unloaded(App, Persistent), ok;
  161. {error, Reason} ->
  162. {error, Reason}
  163. end.
  164. stop_app(App) ->
  165. case application:stop(App) of
  166. ok ->
  167. lager:info("stop plugin ~p successfully~n", [App]), ok;
  168. {error, {not_started, App}} ->
  169. lager:error("plugin ~p is not started~n", [App]), ok;
  170. {error, Reason} ->
  171. lager:error("stop plugin ~p error: ~p", [App]), {error, Reason}
  172. end.
  173. %%--------------------------------------------------------------------
  174. %% Internal functions
  175. %%--------------------------------------------------------------------
  176. names(plugin) ->
  177. names(list());
  178. names(started_app) ->
  179. [Name || {Name, _Descr, _Ver} <- application:which_applications()];
  180. names(Plugins) ->
  181. [Name || #mqtt_plugin{name = Name} <- Plugins].
  182. plugin_loaded(_Name, false) ->
  183. ok;
  184. plugin_loaded(Name, true) ->
  185. case read_loaded() of
  186. {ok, Names} ->
  187. case lists:member(Name, Names) of
  188. false ->
  189. %% write file if plugin is loaded
  190. write_loaded(lists:append(Names, [Name]));
  191. true ->
  192. ignore
  193. end;
  194. {error, Error} ->
  195. lager:error("Cannot read loaded plugins: ~p", [Error])
  196. end.
  197. plugin_unloaded(_Name, false) ->
  198. ok;
  199. plugin_unloaded(Name, true) ->
  200. case read_loaded() of
  201. {ok, Names} ->
  202. case lists:member(Name, Names) of
  203. true ->
  204. write_loaded(lists:delete(Name, Names));
  205. false ->
  206. lager:error("Cannot find ~s in loaded_file", [Name])
  207. end;
  208. {error, Error} ->
  209. lager:error("Cannot read loaded_plugins: ~p", [Error])
  210. end.
  211. read_loaded() ->
  212. {ok, File} = env(loaded_file),
  213. read_loaded(File).
  214. read_loaded(File) ->
  215. file:consult(File).
  216. write_loaded(AppNames) ->
  217. {ok, File} = env(loaded_file),
  218. case file:open(File, [binary, write]) of
  219. {ok, Fd} ->
  220. lists:foreach(fun(Name) ->
  221. file:write(Fd, iolist_to_binary(io_lib:format("~s.~n", [Name])))
  222. end, AppNames);
  223. {error, Error} ->
  224. lager:error("Open File ~p Error: ~p", [File, Error]),
  225. {error, Error}
  226. end.
  227. env(Name) ->
  228. case application:get_env(emqttd, plugins) of
  229. {ok, PluginsEnv} ->
  230. case proplists:get_value(Name, PluginsEnv) of
  231. undefined ->
  232. undefined;
  233. Val ->
  234. {ok, Val}
  235. end;
  236. undefined ->
  237. undefined
  238. end.