emqttd_plugins.erl 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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 plugin admin.
  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([list/0, load/1, unload/1]).
  32. %%------------------------------------------------------------------------------
  33. %% @doc Load all plugins when the broker started.
  34. %% @end
  35. %%------------------------------------------------------------------------------
  36. -spec load() -> list() | {error, any()}.
  37. load() ->
  38. case read_loaded() of
  39. {ok, LoadNames} ->
  40. NotFound = LoadNames -- apps(plugin),
  41. case NotFound of
  42. [] -> ok;
  43. NotFound -> lager:error("Cannot find plugins: ~p", [NotFound])
  44. end,
  45. start_apps(LoadNames -- NotFound -- apps(started));
  46. {error, Error} ->
  47. lager:error("Read loaded_plugins file error: ~p", [Error]),
  48. {error, Error}
  49. end.
  50. start_apps(Apps) ->
  51. [start_app(App) || App <- Apps].
  52. %%------------------------------------------------------------------------------
  53. %% @doc Unload all plugins before broker stopped.
  54. %% @end
  55. %%------------------------------------------------------------------------------
  56. -spec unload() -> list() | {error, any()}.
  57. unload() ->
  58. case read_loaded() of
  59. {ok, LoadNames} ->
  60. stop_apps(LoadNames);
  61. {error, Error} ->
  62. lager:error("Read loaded_plugins file error: ~p", [Error]),
  63. {error, Error}
  64. end.
  65. stop_apps(Apps) ->
  66. [stop_app(App) || App <- Apps].
  67. %%------------------------------------------------------------------------------
  68. %% @doc List all available plugins
  69. %% @end
  70. %%------------------------------------------------------------------------------
  71. -spec list() -> [mqtt_plugin()].
  72. list() ->
  73. PluginsDir = env(dir),
  74. AppFiles = filelib:wildcard("*/ebin/*.app", PluginsDir),
  75. Plugins = [plugin(filename:join(PluginsDir, AppFile)) || AppFile <- AppFiles],
  76. StartedApps = apps(started),
  77. lists:map(fun(Plugin = #mqtt_plugin{name = Name}) ->
  78. case lists:member(Name, StartedApps) of
  79. true -> Plugin#mqtt_plugin{active = true};
  80. false -> Plugin
  81. end
  82. end, Plugins).
  83. plugin(AppFile) ->
  84. {ok, [{application, Name, Attrs}]} = file:consult(AppFile),
  85. Ver = proplists:get_value(vsn, Attrs, "0"),
  86. Descr = proplists:get_value(description, Attrs, ""),
  87. #mqtt_plugin{name = Name, version = Ver, descr = Descr}.
  88. %%------------------------------------------------------------------------------
  89. %% @doc Load One Plugin
  90. %% @end
  91. %%------------------------------------------------------------------------------
  92. -spec load(atom()) -> ok | {error, any()}.
  93. load(PluginName) when is_atom(PluginName) ->
  94. case {lists:member(PluginName, apps(started)), lists:member(PluginName, apps(plugin))} of
  95. {true, _} ->
  96. lager:error("plugin ~p is started", [PluginName]),
  97. {error, already_started};
  98. {false, true} ->
  99. load_plugin(PluginName);
  100. {false, false} ->
  101. lager:error("plugin ~p is not found", [PluginName]),
  102. {error, not_found}
  103. end.
  104. -spec load_plugin(App :: atom()) -> {ok, list()} | {error, any()}.
  105. load_plugin(PluginName) ->
  106. case start_app(PluginName) of
  107. {ok, Started} ->
  108. plugin_loaded(PluginName),
  109. {ok, Started};
  110. {error, Error} ->
  111. {error, Error}
  112. end.
  113. start_app(App) ->
  114. case application:ensure_all_started(App) of
  115. {ok, Started} ->
  116. lager:info("started apps: ~p, load plugin ~p successfully", [Started, App]),
  117. {ok, Started};
  118. {error, {ErrApp, Reason}} ->
  119. lager:error("load plugin ~p error, cannot start app ~s for ~p", [App, ErrApp, Reason]),
  120. {error, {ErrApp, Reason}}
  121. end.
  122. %%------------------------------------------------------------------------------
  123. %% @doc UnLoad One Plugin
  124. %% @end
  125. %%------------------------------------------------------------------------------
  126. -spec unload(atom()) -> ok | {error, any()}.
  127. unload(PluginName) when is_atom(PluginName) ->
  128. case {lists:member(PluginName, apps(started)), lists:member(PluginName, apps(plugin))} of
  129. {false, _} ->
  130. lager:error("plugin ~p is not started", [PluginName]),
  131. {error, not_started};
  132. {true, true} ->
  133. unload_plugin(PluginName);
  134. {true, false} ->
  135. lager:error("~s is not a plugin, cannot unload it", [PluginName]),
  136. {error, not_found}
  137. end.
  138. -spec unload_plugin(App :: atom()) -> ok | {error, any()}.
  139. unload_plugin(App) ->
  140. case stop_app(App) of
  141. ok ->
  142. plugin_unloaded(App), ok;
  143. {error, Reason} ->
  144. {error, Reason}
  145. end.
  146. stop_app(App) ->
  147. case application:stop(App) of
  148. ok ->
  149. lager:info("stop plugin ~p successfully~n", [App]), ok;
  150. {error, {not_started, App}} ->
  151. lager:error("plugin ~p is not started~n", [App]), ok;
  152. {error, Reason} ->
  153. lager:error("stop plugin ~p error: ~p", [App]), {error, Reason}
  154. end.
  155. %%%=============================================================================
  156. %%% Internal functions
  157. %%%=============================================================================
  158. apps(plugin) ->
  159. [Name || #mqtt_plugin{name = Name} <- list()];
  160. apps(started) ->
  161. [Name || {Name, _Descr, _Ver} <- application:which_applications()].
  162. plugin_loaded(Name) ->
  163. case read_loaded() of
  164. {ok, Names} ->
  165. case lists:member(Name, Names) of
  166. true ->
  167. ignore;
  168. false ->
  169. %% write file if plugin is loaded
  170. write_loaded(lists:append(Names, Name))
  171. end;
  172. {error, Error} ->
  173. lager:error("Cannot read loaded plugins: ~p", [Error])
  174. end.
  175. plugin_unloaded(Name) ->
  176. case read_loaded() of
  177. {ok, Names} ->
  178. case lists:member(Name, Names) of
  179. true ->
  180. write_loaded(lists:delete(Name, Names));
  181. false ->
  182. lager:error("Cannot find ~s in loaded_file", [Name])
  183. end;
  184. {error, Error} ->
  185. lager:error("Cannot read loaded plugins: ~p", [Error])
  186. end.
  187. read_loaded() ->
  188. file:consult(env(loaded_file)).
  189. write_loaded(AppNames) ->
  190. case file:open(env(loaded_file), [binary, write]) of
  191. {ok, Fd} ->
  192. lists:foreach(fun(Name) ->
  193. file:write(Fd, iolist_to_binary(io_lib:format("~s.~n", [Name])))
  194. end, AppNames);
  195. {error, Error} ->
  196. {error, Error}
  197. end.
  198. env(dir) ->
  199. proplists:get_value(dir, env(), "./plugins");
  200. env(loaded_file) ->
  201. proplists:get_value(loaded_file, env(), "./data/loaded_plugins").
  202. env() ->
  203. {ok, PluginsEnv} = application:get_env(emqttd, plugins), PluginsEnv.