emqx/apps/emqx_plugins/test/emqx_plugins_SUITE.erl

276 lines
11 KiB
Erlang

%%--------------------------------------------------------------------
%% Copyright (c) 2019-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_plugins_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("emqx/include/emqx.hrl").
-include_lib("eunit/include/eunit.hrl").
-define(EMQX_PLUGIN_TEMPLATE_VSN, "5.0-rc.1").
-define(PACKAGE_SUFFIX, ".tar.gz").
all() -> emqx_common_test_helpers:all(?MODULE).
init_per_suite(Config) ->
WorkDir = proplists:get_value(data_dir, Config),
OrigInstallDir = emqx_plugins:get_config(install_dir, undefined),
emqx_plugins:put_config(install_dir, WorkDir),
emqx_common_test_helpers:start_apps([]),
[{orig_install_dir, OrigInstallDir} | Config].
end_per_suite(Config) ->
emqx_common_test_helpers:boot_modules(all),
emqx_common_test_helpers:stop_apps([]),
emqx_config:erase(plugins),
%% restore config
case proplists:get_value(orig_install_dir, Config) of
undefined -> ok;
OrigInstallDir -> emqx_plugins:put_config(install_dir, OrigInstallDir)
end.
init_per_testcase(TestCase, Config) ->
emqx_plugins:put_configured([]),
lists:foreach(fun(#{<<"name">> := Name, <<"rel_vsn">> := Vsn}) ->
emqx_plugins:purge(bin([Name, "-", Vsn]))
end, emqx_plugins:list()),
?MODULE:TestCase({init, Config}).
end_per_testcase(TestCase, Config) ->
emqx_plugins:put_configured([]),
?MODULE:TestCase({'end', Config}).
build_demo_plugin_package() ->
WorkDir = emqx_plugins:install_dir(),
BuildSh = filename:join([WorkDir, "build-demo-plugin.sh"]),
case emqx_run_sh:do(BuildSh ++ " " ++ ?EMQX_PLUGIN_TEMPLATE_VSN,
[{cd, WorkDir}]) of
{ok, _} ->
Pkg = filename:join([WorkDir, "emqx_plugin_template-" ++
?EMQX_PLUGIN_TEMPLATE_VSN ++
?PACKAGE_SUFFIX]),
case filelib:is_regular(Pkg) of
true -> Pkg;
false -> error(#{reason => unexpected_build_result, not_found => Pkg})
end;
{error, {Rc, Output}} ->
io:format(user, "failed_to_build_demo_plugin, Exit = ~p, Output:~n~ts\n", [Rc, Output]),
error(failed_to_build_demo_plugin)
end.
bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
bin(L) when is_list(L) -> unicode:characters_to_binary(L, utf8);
bin(B) when is_binary(B) -> B.
t_demo_install_start_stop_uninstall({init, Config}) ->
Package = build_demo_plugin_package(),
NameVsn = filename:basename(Package, ?PACKAGE_SUFFIX),
[{name_vsn, NameVsn} | Config];
t_demo_install_start_stop_uninstall({'end', _Config}) -> ok;
t_demo_install_start_stop_uninstall(Config) ->
NameVsn = proplists:get_value(name_vsn, Config),
ok = emqx_plugins:ensure_installed(NameVsn),
%% idempotent
ok = emqx_plugins:ensure_installed(NameVsn),
{ok, Info} = emqx_plugins:read_plugin(NameVsn),
?assertEqual([Info], emqx_plugins:list()),
%% start
ok = emqx_plugins:ensure_started(NameVsn),
ok = assert_app_running(emqx_plugin_template, true),
ok = assert_app_running(map_sets, true),
%% start (idempotent)
ok = emqx_plugins:ensure_started(bin(NameVsn)),
ok = assert_app_running(emqx_plugin_template, true),
ok = assert_app_running(map_sets, true),
%% running app can not be un-installed
?assertMatch({error, _},
emqx_plugins:ensure_uninstalled(NameVsn)),
%% stop
ok = emqx_plugins:ensure_stopped(NameVsn),
ok = assert_app_running(emqx_plugin_template, false),
ok = assert_app_running(map_sets, false),
%% stop (idempotent)
ok = emqx_plugins:ensure_stopped(bin(NameVsn)),
ok = assert_app_running(emqx_plugin_template, false),
ok = assert_app_running(map_sets, false),
%% still listed after stopped
?assertMatch([#{<<"name">> := <<"emqx_plugin_template">>,
<<"rel_vsn">> := <<?EMQX_PLUGIN_TEMPLATE_VSN>>
}], emqx_plugins:list()),
ok = emqx_plugins:ensure_uninstalled(NameVsn),
?assertEqual([], emqx_plugins:list()),
ok.
%% help funtion to create a info file.
%% The file is in JSON format when built
%% but since we are using hocon:load to load it
%% ad-hoc test files can be in hocon format
write_info_file(Config, NameVsn, Content) ->
WorkDir = proplists:get_value(data_dir, Config),
InfoFile = filename:join([WorkDir, NameVsn, "release.json"]),
ok = filelib:ensure_dir(InfoFile),
ok = file:write_file(InfoFile, Content).
t_start_restart_and_stop({init, Config}) ->
Package = build_demo_plugin_package(),
NameVsn = filename:basename(Package, ?PACKAGE_SUFFIX),
[{name_vsn, NameVsn} | Config];
t_start_restart_and_stop({'end', _Config}) -> ok;
t_start_restart_and_stop(Config) ->
NameVsn = proplists:get_value(name_vsn, Config),
ok = emqx_plugins:ensure_installed(NameVsn),
ok = emqx_plugins:ensure_enabled(NameVsn),
FakeInfo = "name=bar, rel_vsn=\"2\", rel_apps=[\"bar-9\"],"
"description=\"desc bar\"",
Bar2 = <<"bar-2">>,
ok = write_info_file(Config, Bar2, FakeInfo),
%% fake a disabled plugin in config
ok = emqx_plugins:ensure_state(Bar2, front, false),
assert_app_running(emqx_plugin_template, false),
ok = emqx_plugins:ensure_started(),
assert_app_running(emqx_plugin_template, true),
%% fake enable bar-2
ok = emqx_plugins:ensure_state(Bar2, rear, true),
%% should cause an error
?assertError(#{function := _, errors := [_ | _]},
emqx_plugins:ensure_started()),
%% but demo plugin should still be running
assert_app_running(emqx_plugin_template, true),
%% stop all
ok = emqx_plugins:ensure_stopped(),
assert_app_running(emqx_plugin_template, false),
ok = emqx_plugins:ensure_state(Bar2, rear, false),
ok = emqx_plugins:restart(NameVsn),
assert_app_running(emqx_plugin_template, true),
%% repeat
ok = emqx_plugins:restart(NameVsn),
assert_app_running(emqx_plugin_template, true),
ok = emqx_plugins:ensure_stopped(),
ok = emqx_plugins:ensure_disabled(NameVsn),
ok = emqx_plugins:ensure_uninstalled(NameVsn),
ok = emqx_plugins:ensure_uninstalled(Bar2),
?assertEqual([], emqx_plugins:list()),
ok.
t_enable_disable({init, Config}) ->
Package = build_demo_plugin_package(),
NameVsn = filename:basename(Package, ?PACKAGE_SUFFIX),
[{name_vsn, NameVsn} | Config];
t_enable_disable({'end', Config}) ->
ok = emqx_plugins:ensure_uninstalled(proplists:get_value(name_vsn, Config));
t_enable_disable(Config) ->
NameVsn = proplists:get_value(name_vsn, Config),
ok = emqx_plugins:ensure_installed(NameVsn),
?assertEqual([], emqx_plugins:configured()),
ok = emqx_plugins:ensure_enabled(NameVsn),
?assertEqual([#{name_vsn => NameVsn, enable => true}], emqx_plugins:configured()),
ok = emqx_plugins:ensure_disabled(NameVsn),
?assertEqual([#{name_vsn => NameVsn, enable => false}], emqx_plugins:configured()),
ok = emqx_plugins:ensure_enabled(bin(NameVsn)),
?assertEqual([#{name_vsn => NameVsn, enable => true}], emqx_plugins:configured()),
?assertMatch({error, #{reason := "bad_plugin_config_status",
hint := "disable_the_plugin_first"
}}, emqx_plugins:ensure_uninstalled(NameVsn)),
ok = emqx_plugins:ensure_disabled(bin(NameVsn)),
ok = emqx_plugins:ensure_uninstalled(NameVsn),
?assertMatch({error, _}, emqx_plugins:ensure_enabled(NameVsn)),
?assertMatch({error, _}, emqx_plugins:ensure_disabled(NameVsn)),
ok.
assert_app_running(Name, true) ->
AllApps = application:which_applications(),
?assertMatch({Name, _, _}, lists:keyfind(Name, 1, AllApps));
assert_app_running(Name, false) ->
AllApps = application:which_applications(),
?assertEqual(false, lists:keyfind(Name, 1, AllApps)).
t_bad_tar_gz({init, Config}) -> Config;
t_bad_tar_gz({'end', _Config}) -> ok;
t_bad_tar_gz(Config) ->
WorkDir = proplists:get_value(data_dir, Config),
FakeTarTz = filename:join([WorkDir, "fake-vsn.tar.gz"]),
ok = file:write_file(FakeTarTz, "a\n"),
?assertMatch({error, #{reason := "bad_plugin_package",
return := eof
}},
emqx_plugins:ensure_installed("fake-vsn")),
?assertMatch({error, #{reason := "failed_to_extract_plugin_package",
return := not_found
}},
emqx_plugins:ensure_installed("nonexisting")),
?assertEqual([], emqx_plugins:list()),
ok = emqx_plugins:delete_package("fake-vsn"),
%% idempotent
ok = emqx_plugins:delete_package("fake-vsn").
%% create a corrupted .tar.gz
%% failed install attempts should not leave behind extracted dir
t_bad_tar_gz2({init, Config}) -> Config;
t_bad_tar_gz2({'end', _Config}) -> ok;
t_bad_tar_gz2(Config) ->
WorkDir = proplists:get_value(data_dir, Config),
NameVsn = "foo-0.2",
%% this an invalid info file content
BadInfo = "name=foo, rel_vsn=\"0.2\", rel_apps=[foo]",
ok = write_info_file(Config, NameVsn, BadInfo),
TarGz = filename:join([WorkDir, NameVsn ++ ".tar.gz"]),
ok = make_tar(WorkDir, NameVsn),
?assert(filelib:is_regular(TarGz)),
%% failed to install, it also cleans up the bad .tar.gz file
?assertMatch({error, _}, emqx_plugins:ensure_installed(NameVsn)),
%% the tar.gz file is still around
?assert(filelib:is_regular(TarGz)),
?assertEqual({error, enoent}, file:read_file_info(emqx_plugins:dir(NameVsn))),
ok = emqx_plugins:delete_package(NameVsn).
t_bad_info_json({init, Config}) -> Config;
t_bad_info_json({'end', _}) -> ok;
t_bad_info_json(Config) ->
NameVsn = "test-2",
ok = write_info_file(Config, NameVsn, "bad-syntax"),
?assertMatch({error, #{error := "bad_info_file",
return := {parse_error, _}
}},
emqx_plugins:read_plugin(NameVsn)),
ok = write_info_file(Config, NameVsn, "{\"bad\": \"obj\"}"),
?assertMatch({error, #{error := "bad_info_file_content",
mandatory_fields := _
}},
emqx_plugins:read_plugin(NameVsn)),
?assertEqual([], emqx_plugins:list()),
emqx_plugins:purge(NameVsn),
ok.
make_tar(Cwd, NameWithVsn) ->
{ok, OriginalCwd} = file:get_cwd(),
ok = file:set_cwd(Cwd),
try
Files = filelib:wildcard(NameWithVsn ++ "/**"),
TarFile = NameWithVsn ++ ".tar.gz",
ok = erl_tar:create(TarFile, Files, [compressed])
after
file:set_cwd(OriginalCwd)
end.