emqx_plugins_SUITE.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2019-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
  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(emqx_plugins_SUITE).
  17. -compile(export_all).
  18. -compile(nowarn_export_all).
  19. -include_lib("emqx/include/emqx.hrl").
  20. -include_lib("eunit/include/eunit.hrl").
  21. -define(EMQX_PLUGIN_TEMPLATE_VSN, "5.0-rc.1").
  22. -define(PACKAGE_SUFFIX, ".tar.gz").
  23. all() -> emqx_common_test_helpers:all(?MODULE).
  24. init_per_suite(Config) ->
  25. WorkDir = proplists:get_value(data_dir, Config),
  26. OrigInstallDir = emqx_plugins:get_config(install_dir, undefined),
  27. emqx_plugins:put_config(install_dir, WorkDir),
  28. emqx_common_test_helpers:start_apps([]),
  29. [{orig_install_dir, OrigInstallDir} | Config].
  30. end_per_suite(Config) ->
  31. emqx_common_test_helpers:boot_modules(all),
  32. emqx_common_test_helpers:stop_apps([]),
  33. emqx_config:erase(plugins),
  34. %% restore config
  35. case proplists:get_value(orig_install_dir, Config) of
  36. undefined -> ok;
  37. OrigInstallDir -> emqx_plugins:put_config(install_dir, OrigInstallDir)
  38. end.
  39. init_per_testcase(TestCase, Config) ->
  40. emqx_plugins:put_configured([]),
  41. lists:foreach(fun(#{<<"name">> := Name, <<"rel_vsn">> := Vsn}) ->
  42. emqx_plugins:purge(bin([Name, "-", Vsn]))
  43. end, emqx_plugins:list()),
  44. ?MODULE:TestCase({init, Config}).
  45. end_per_testcase(TestCase, Config) ->
  46. emqx_plugins:put_configured([]),
  47. ?MODULE:TestCase({'end', Config}).
  48. build_demo_plugin_package() ->
  49. WorkDir = emqx_plugins:install_dir(),
  50. BuildSh = filename:join([WorkDir, "build-demo-plugin.sh"]),
  51. case emqx_run_sh:do(BuildSh ++ " " ++ ?EMQX_PLUGIN_TEMPLATE_VSN,
  52. [{cd, WorkDir}]) of
  53. {ok, _} ->
  54. Pkg = filename:join([WorkDir, "emqx_plugin_template-" ++
  55. ?EMQX_PLUGIN_TEMPLATE_VSN ++
  56. ?PACKAGE_SUFFIX]),
  57. case filelib:is_regular(Pkg) of
  58. true -> Pkg;
  59. false -> error(#{reason => unexpected_build_result, not_found => Pkg})
  60. end;
  61. {error, {Rc, Output}} ->
  62. io:format(user, "failed_to_build_demo_plugin, Exit = ~p, Output:~n~ts\n", [Rc, Output]),
  63. error(failed_to_build_demo_plugin)
  64. end.
  65. bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
  66. bin(L) when is_list(L) -> unicode:characters_to_binary(L, utf8);
  67. bin(B) when is_binary(B) -> B.
  68. t_demo_install_start_stop_uninstall({init, Config}) ->
  69. Package = build_demo_plugin_package(),
  70. NameVsn = filename:basename(Package, ?PACKAGE_SUFFIX),
  71. [{name_vsn, NameVsn} | Config];
  72. t_demo_install_start_stop_uninstall({'end', _Config}) -> ok;
  73. t_demo_install_start_stop_uninstall(Config) ->
  74. NameVsn = proplists:get_value(name_vsn, Config),
  75. ok = emqx_plugins:ensure_installed(NameVsn),
  76. %% idempotent
  77. ok = emqx_plugins:ensure_installed(NameVsn),
  78. {ok, Info} = emqx_plugins:read_plugin(NameVsn),
  79. ?assertEqual([Info], emqx_plugins:list()),
  80. %% start
  81. ok = emqx_plugins:ensure_started(NameVsn),
  82. ok = assert_app_running(emqx_plugin_template, true),
  83. ok = assert_app_running(map_sets, true),
  84. %% start (idempotent)
  85. ok = emqx_plugins:ensure_started(bin(NameVsn)),
  86. ok = assert_app_running(emqx_plugin_template, true),
  87. ok = assert_app_running(map_sets, true),
  88. %% running app can not be un-installed
  89. ?assertMatch({error, _},
  90. emqx_plugins:ensure_uninstalled(NameVsn)),
  91. %% stop
  92. ok = emqx_plugins:ensure_stopped(NameVsn),
  93. ok = assert_app_running(emqx_plugin_template, false),
  94. ok = assert_app_running(map_sets, false),
  95. %% stop (idempotent)
  96. ok = emqx_plugins:ensure_stopped(bin(NameVsn)),
  97. ok = assert_app_running(emqx_plugin_template, false),
  98. ok = assert_app_running(map_sets, false),
  99. %% still listed after stopped
  100. ?assertMatch([#{<<"name">> := <<"emqx_plugin_template">>,
  101. <<"rel_vsn">> := <<?EMQX_PLUGIN_TEMPLATE_VSN>>
  102. }], emqx_plugins:list()),
  103. ok = emqx_plugins:ensure_uninstalled(NameVsn),
  104. ?assertEqual([], emqx_plugins:list()),
  105. ok.
  106. %% help funtion to create a info file.
  107. %% The file is in JSON format when built
  108. %% but since we are using hocon:load to load it
  109. %% ad-hoc test files can be in hocon format
  110. write_info_file(Config, NameVsn, Content) ->
  111. WorkDir = proplists:get_value(data_dir, Config),
  112. InfoFile = filename:join([WorkDir, NameVsn, "release.json"]),
  113. ok = filelib:ensure_dir(InfoFile),
  114. ok = file:write_file(InfoFile, Content).
  115. t_start_restart_and_stop({init, Config}) ->
  116. Package = build_demo_plugin_package(),
  117. NameVsn = filename:basename(Package, ?PACKAGE_SUFFIX),
  118. [{name_vsn, NameVsn} | Config];
  119. t_start_restart_and_stop({'end', _Config}) -> ok;
  120. t_start_restart_and_stop(Config) ->
  121. NameVsn = proplists:get_value(name_vsn, Config),
  122. ok = emqx_plugins:ensure_installed(NameVsn),
  123. ok = emqx_plugins:ensure_enabled(NameVsn),
  124. FakeInfo = "name=bar, rel_vsn=\"2\", rel_apps=[\"bar-9\"],"
  125. "description=\"desc bar\"",
  126. Bar2 = <<"bar-2">>,
  127. ok = write_info_file(Config, Bar2, FakeInfo),
  128. %% fake a disabled plugin in config
  129. ok = emqx_plugins:ensure_state(Bar2, front, false),
  130. assert_app_running(emqx_plugin_template, false),
  131. ok = emqx_plugins:ensure_started(),
  132. assert_app_running(emqx_plugin_template, true),
  133. %% fake enable bar-2
  134. ok = emqx_plugins:ensure_state(Bar2, rear, true),
  135. %% should cause an error
  136. ?assertError(#{function := _, errors := [_ | _]},
  137. emqx_plugins:ensure_started()),
  138. %% but demo plugin should still be running
  139. assert_app_running(emqx_plugin_template, true),
  140. %% stop all
  141. ok = emqx_plugins:ensure_stopped(),
  142. assert_app_running(emqx_plugin_template, false),
  143. ok = emqx_plugins:ensure_state(Bar2, rear, false),
  144. ok = emqx_plugins:restart(NameVsn),
  145. assert_app_running(emqx_plugin_template, true),
  146. %% repeat
  147. ok = emqx_plugins:restart(NameVsn),
  148. assert_app_running(emqx_plugin_template, true),
  149. ok = emqx_plugins:ensure_stopped(),
  150. ok = emqx_plugins:ensure_disabled(NameVsn),
  151. ok = emqx_plugins:ensure_uninstalled(NameVsn),
  152. ok = emqx_plugins:ensure_uninstalled(Bar2),
  153. ?assertEqual([], emqx_plugins:list()),
  154. ok.
  155. t_enable_disable({init, Config}) ->
  156. Package = build_demo_plugin_package(),
  157. NameVsn = filename:basename(Package, ?PACKAGE_SUFFIX),
  158. [{name_vsn, NameVsn} | Config];
  159. t_enable_disable({'end', Config}) ->
  160. ok = emqx_plugins:ensure_uninstalled(proplists:get_value(name_vsn, Config));
  161. t_enable_disable(Config) ->
  162. NameVsn = proplists:get_value(name_vsn, Config),
  163. ok = emqx_plugins:ensure_installed(NameVsn),
  164. ?assertEqual([], emqx_plugins:configured()),
  165. ok = emqx_plugins:ensure_enabled(NameVsn),
  166. ?assertEqual([#{name_vsn => NameVsn, enable => true}], emqx_plugins:configured()),
  167. ok = emqx_plugins:ensure_disabled(NameVsn),
  168. ?assertEqual([#{name_vsn => NameVsn, enable => false}], emqx_plugins:configured()),
  169. ok = emqx_plugins:ensure_enabled(bin(NameVsn)),
  170. ?assertEqual([#{name_vsn => NameVsn, enable => true}], emqx_plugins:configured()),
  171. ?assertMatch({error, #{reason := "bad_plugin_config_status",
  172. hint := "disable_the_plugin_first"
  173. }}, emqx_plugins:ensure_uninstalled(NameVsn)),
  174. ok = emqx_plugins:ensure_disabled(bin(NameVsn)),
  175. ok = emqx_plugins:ensure_uninstalled(NameVsn),
  176. ?assertMatch({error, _}, emqx_plugins:ensure_enabled(NameVsn)),
  177. ?assertMatch({error, _}, emqx_plugins:ensure_disabled(NameVsn)),
  178. ok.
  179. assert_app_running(Name, true) ->
  180. AllApps = application:which_applications(),
  181. ?assertMatch({Name, _, _}, lists:keyfind(Name, 1, AllApps));
  182. assert_app_running(Name, false) ->
  183. AllApps = application:which_applications(),
  184. ?assertEqual(false, lists:keyfind(Name, 1, AllApps)).
  185. t_bad_tar_gz({init, Config}) -> Config;
  186. t_bad_tar_gz({'end', _Config}) -> ok;
  187. t_bad_tar_gz(Config) ->
  188. WorkDir = proplists:get_value(data_dir, Config),
  189. FakeTarTz = filename:join([WorkDir, "fake-vsn.tar.gz"]),
  190. ok = file:write_file(FakeTarTz, "a\n"),
  191. ?assertMatch({error, #{reason := "bad_plugin_package",
  192. return := eof
  193. }},
  194. emqx_plugins:ensure_installed("fake-vsn")),
  195. ?assertMatch({error, #{reason := "failed_to_extract_plugin_package",
  196. return := not_found
  197. }},
  198. emqx_plugins:ensure_installed("nonexisting")),
  199. ?assertEqual([], emqx_plugins:list()),
  200. ok = emqx_plugins:delete_package("fake-vsn"),
  201. %% idempotent
  202. ok = emqx_plugins:delete_package("fake-vsn").
  203. %% create a corrupted .tar.gz
  204. %% failed install attempts should not leave behind extracted dir
  205. t_bad_tar_gz2({init, Config}) -> Config;
  206. t_bad_tar_gz2({'end', _Config}) -> ok;
  207. t_bad_tar_gz2(Config) ->
  208. WorkDir = proplists:get_value(data_dir, Config),
  209. NameVsn = "foo-0.2",
  210. %% this an invalid info file content
  211. BadInfo = "name=foo, rel_vsn=\"0.2\", rel_apps=[foo]",
  212. ok = write_info_file(Config, NameVsn, BadInfo),
  213. TarGz = filename:join([WorkDir, NameVsn ++ ".tar.gz"]),
  214. ok = make_tar(WorkDir, NameVsn),
  215. ?assert(filelib:is_regular(TarGz)),
  216. %% failed to install, it also cleans up the bad .tar.gz file
  217. ?assertMatch({error, _}, emqx_plugins:ensure_installed(NameVsn)),
  218. %% the tar.gz file is still around
  219. ?assert(filelib:is_regular(TarGz)),
  220. ?assertEqual({error, enoent}, file:read_file_info(emqx_plugins:dir(NameVsn))),
  221. ok = emqx_plugins:delete_package(NameVsn).
  222. t_bad_info_json({init, Config}) -> Config;
  223. t_bad_info_json({'end', _}) -> ok;
  224. t_bad_info_json(Config) ->
  225. NameVsn = "test-2",
  226. ok = write_info_file(Config, NameVsn, "bad-syntax"),
  227. ?assertMatch({error, #{error := "bad_info_file",
  228. return := {parse_error, _}
  229. }},
  230. emqx_plugins:read_plugin(NameVsn)),
  231. ok = write_info_file(Config, NameVsn, "{\"bad\": \"obj\"}"),
  232. ?assertMatch({error, #{error := "bad_info_file_content",
  233. mandatory_fields := _
  234. }},
  235. emqx_plugins:read_plugin(NameVsn)),
  236. ?assertEqual([], emqx_plugins:list()),
  237. emqx_plugins:purge(NameVsn),
  238. ok.
  239. make_tar(Cwd, NameWithVsn) ->
  240. {ok, OriginalCwd} = file:get_cwd(),
  241. ok = file:set_cwd(Cwd),
  242. try
  243. Files = filelib:wildcard(NameWithVsn ++ "/**"),
  244. TarFile = NameWithVsn ++ ".tar.gz",
  245. ok = erl_tar:create(TarFile, Files, [compressed])
  246. after
  247. file:set_cwd(OriginalCwd)
  248. end.