From aa19283ff23df363d3bd98c7bf0e5fdc8dbce623 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 5 Apr 2022 12:03:10 -0300 Subject: [PATCH] fix: ensure default values for `loaded_{plugins,modules}` Fixes #7455 . This tries to populate `loaded_{plugins,modules}` files with default values before loading them, in case they don't exist. --- lib-ce/emqx_modules/src/emqx_modules.erl | 30 ++++++++++- .../emqx_modules/test/emqx_modules_SUITE.erl | 40 +++++++++++++++ src/emqx_plugins.erl | 16 +++++- test/emqx_plugins_SUITE.erl | 51 ++++++++++++++++++- 4 files changed, 133 insertions(+), 4 deletions(-) diff --git a/lib-ce/emqx_modules/src/emqx_modules.erl b/lib-ce/emqx_modules/src/emqx_modules.erl index 3de9c6ba3..c5565d7d6 100644 --- a/lib-ce/emqx_modules/src/emqx_modules.erl +++ b/lib-ce/emqx_modules/src/emqx_modules.erl @@ -43,6 +43,7 @@ load() -> case emqx:get_env(modules_loaded_file) of undefined -> ok; File -> + ensure_loaded_modules_file(File), load_modules(File) end. @@ -58,6 +59,31 @@ load(ModuleName) -> emqx_modules:load_module(ModuleName, true) end. +%% @doc Creates a `loaded_modules' file with default values if one +%% doesn't exist. +-spec ensure_loaded_modules_file(file:filename()) -> ok. +ensure_loaded_modules_file(Filepath) -> + case filelib:is_regular(Filepath) of + true -> + ok; + false -> + do_ensure_loaded_modules_file(Filepath) + end. + +do_ensure_loaded_modules_file(Filepath) -> + DefaultModules = [emqx_mod_acl_internal, emqx_mod_presence], + Res = file:write_file(Filepath, + [io_lib:format("{~p, true}.~n", [Mod]) + || Mod <- DefaultModules]), + case Res of + ok -> + ok; + {error, Reason} -> + ?LOG(error, "Could not write default loaded_modules file ~p ; Error: ~p", + [Filepath, Reason]), + ok + end. + %% @doc Unload all the extended modules. -spec(unload() -> ok). unload() -> @@ -175,8 +201,10 @@ write_loaded(false) -> ok. %%-------------------------------------------------------------------- %% @doc Modules Command +%%-------------------------------------------------------------------- + cli(["list"]) -> - lists:foreach(fun({Name, Active}) -> + lists:foreach(fun({Name, Active}) -> emqx_ctl:print("Module(~s, description=~s, active=~s)~n", [Name, Name:description(), Active]) end, emqx_modules:list()); diff --git a/lib-ce/emqx_modules/test/emqx_modules_SUITE.erl b/lib-ce/emqx_modules/test/emqx_modules_SUITE.erl index 175b24bba..a149d9991 100644 --- a/lib-ce/emqx_modules/test/emqx_modules_SUITE.erl +++ b/lib-ce/emqx_modules/test/emqx_modules_SUITE.erl @@ -20,6 +20,7 @@ -compile(nowarn_export_all). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). -define(CONTENT_TYPE, "application/x-www-form-urlencoded"). @@ -44,6 +45,32 @@ end_per_suite(_Config) -> emqx_ct_http:delete_default_app(), emqx_ct_helpers:stop_apps([emqx_modules, emqx_management]). +init_per_testcase(t_ensure_default_loaded_modules_file, Config) -> + LoadedModulesFilepath = application:get_env(emqx, modules_loaded_file), + ok = application:stop(emqx_modules), + TmpFilepath = filename:join(["/", "tmp", "loaded_modules_tmp"]), + case file:delete(TmpFilepath) of + ok -> ok; + {error, enoent} -> ok + end, + application:set_env(emqx, modules_loaded_file, TmpFilepath), + [ {loaded_modules_filepath, LoadedModulesFilepath} + , {tmp_filepath, TmpFilepath} + | Config]; +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(t_ensure_default_loaded_modules_file, Config) -> + LoadedModulesFilepath = ?config(loaded_modules_filepath, Config), + TmpFilepath = ?config(tmp_filepath, Config), + file:delete(TmpFilepath), + ok = application:stop(emqx_modules), + application:set_env(emqx, modules_loaded_file, LoadedModulesFilepath), + ok = application:start(emqx_modules), + ok; +end_per_testcase(_TestCase, _Config) -> + ok. + t_load(_) -> ?assertEqual(ok, emqx_modules:unload()), ?assertEqual(ok, emqx_modules:load()), @@ -52,6 +79,19 @@ t_load(_) -> ?assertEqual(ignore, emqx_modules:reload(emqx_mod_rewrite)), ?assertEqual(ok, emqx_modules:reload(emqx_mod_acl_internal)). +t_ensure_default_loaded_modules_file(_Config) -> + ok = application:start(emqx_modules), + ?assertEqual( + [ {emqx_mod_acl_internal,true} + , {emqx_mod_delayed,false} + , {emqx_mod_presence,true} + , {emqx_mod_rewrite,false} + , {emqx_mod_subscription,false} + , {emqx_mod_topic_metrics,false} + ], + lists:sort(emqx_modules:list())), + ok. + t_list(_) -> ?assertMatch([{_, _} | _ ], emqx_modules:list()). diff --git a/src/emqx_plugins.erl b/src/emqx_plugins.erl index 7c4633e38..3aa0287da 100644 --- a/src/emqx_plugins.erl +++ b/src/emqx_plugins.erl @@ -215,7 +215,21 @@ load_plugin_conf(AppName, PluginDir) -> end, AppsEnv). ensure_file(File) -> - case filelib:is_file(File) of false -> write_loaded([]); true -> ok end. + case filelib:is_file(File) of + false -> + DefaultPlugins = [ {emqx_management, true} + , {emqx_dashboard, true} + , {emqx_modules, false} + , {emqx_recon, true} + , {emqx_retainer, true} + , {emqx_telemetry, true} + , {emqx_rule_engine, true} + , {emqx_bridge_mqtt, false} + ], + write_loaded(DefaultPlugins); + true -> + ok + end. with_loaded_file(File, SuccFun) -> case read_loaded(File) of diff --git a/test/emqx_plugins_SUITE.erl b/test/emqx_plugins_SUITE.erl index 6d8847f43..395318d43 100644 --- a/test/emqx_plugins_SUITE.erl +++ b/test/emqx_plugins_SUITE.erl @@ -21,11 +21,11 @@ -include_lib("emqx/include/emqx.hrl"). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). all() -> emqx_ct:all(?MODULE). init_per_suite(Config) -> - %% Compile extra plugin code DataPath = proplists:get_value(data_dir, Config), @@ -47,7 +47,35 @@ set_special_cfg(PluginsDir) -> ok. end_per_suite(_Config) -> - emqx_ct_helpers:stop_apps([]). + emqx_ct_helpers:stop_apps([]), + file:delete(get(loaded_file)). + +init_per_testcase(t_ensure_default_loaded_plugins_file, Config) -> + {ok, LoadedPluginsFilepath} = application:get_env(emqx, plugins_loaded_file), + TmpFilepath = filename:join(["/", "tmp", "loaded_plugins_tmp"]), + case file:delete(TmpFilepath) of + ok -> ok; + {error, enoent} -> ok + end, + application:set_env(emqx, plugins_loaded_file, TmpFilepath), + [ {loaded_plugins_filepath, LoadedPluginsFilepath} + , {tmp_filepath, TmpFilepath} + | Config]; +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(t_ensure_default_loaded_plugins_file, Config) -> + LoadedPluginsFilepath = ?config(loaded_plugins_filepath, Config), + TmpFilepath = ?config(tmp_filepath, Config), + file:delete(TmpFilepath), + emqx_plugins:unload(), + application:set_env(emqx, plugins_loaded_file, LoadedPluginsFilepath), + %% need to purge the plugin to avoid inter-testcase dependencies. + code:purge(emqx_mini_plugin_app), + ok; +end_per_testcase(_TestCase, _Config) -> + emqx_plugins:unload(), + ok. t_load(_) -> ?assertEqual(ok, emqx_plugins:load()), @@ -61,6 +89,25 @@ t_load(_) -> ?assertEqual(ignore, emqx_plugins:load()), ?assertEqual(ignore, emqx_plugins:unload()). +t_ensure_default_loaded_plugins_file(Config) -> + %% this will trigger it to write the default plugins to the + %% inexistent file; but it won't truly load them in this test + %% because there are no config files in `expand_plugins_dir'. + TmpFilepath = ?config(tmp_filepath, Config), + ok = emqx_plugins:load(), + {ok, Contents} = file:consult(TmpFilepath), + ?assertEqual( + [ {emqx_bridge_mqtt, false} + , {emqx_dashboard, true} + , {emqx_management, true} + , {emqx_modules, false} + , {emqx_recon, true} + , {emqx_retainer, true} + , {emqx_rule_engine, true} + , {emqx_telemetry, true} + ], + lists:sort(Contents)), + ok. t_init_config(_) -> ConfFile = "emqx_mini_plugin.config",