Sfoglia il codice sorgente

fix(emqx_plugins): allow loading conf for plugin app dir

Prior to this change, plugin config files are only allowed
to be placed in the collective config dir etc/plugins.
In order to support external plugin's drop-in deployment,
this commit made emqx_plugins module to read conf file
in application's etc dir
Zaiming Shi 4 anni fa
parent
commit
faecde9ce1

+ 65 - 38
src/emqx_plugins.erl

@@ -61,7 +61,7 @@ init() ->
 %% @doc Load all plugins when the broker started.
 -spec(load() -> ok | ignore | {error, term()}).
 load() ->
-    load_expand_plugins(),
+    ok = load_ext_plugins(emqx:get_env(expand_plugins_dir)),
     case emqx:get_env(plugins_loaded_file) of
         undefined -> ignore; %% No plugins available
         File ->
@@ -148,46 +148,61 @@ init_config(CfgFile) ->
                       [application:set_env(App, Par, Val) || {Par, Val} <- Envs]
                   end, AppsEnv).
 
-load_expand_plugins() ->
-    case emqx:get_env(expand_plugins_dir) of
-        undefined -> ok;
-        ExpandPluginsDir ->
-            Plugins = filelib:wildcard("*", ExpandPluginsDir),
-            lists:foreach(fun(Plugin) ->
-                PluginDir = filename:join(ExpandPluginsDir, Plugin),
+%% load external plugins which are placed in etc/plugins dir
+load_ext_plugins(undefined) -> ok;
+load_ext_plugins(Dir) ->
+    lists:foreach(
+        fun(Plugin) ->
+                PluginDir = filename:join(Dir, Plugin),
                 case filelib:is_dir(PluginDir) of
-                    true  -> load_expand_plugin(PluginDir);
+                    true  -> load_ext_plugin(PluginDir);
                     false -> ok
                 end
-            end, Plugins)
-    end.
+        end, filelib:wildcard("*", Dir)).
 
-load_expand_plugin(PluginDir) ->
-    init_expand_plugin_config(PluginDir),
+load_ext_plugin(PluginDir) ->
+    ?LOG(debug, "loading_extra_plugin: ~s", [PluginDir]),
     Ebin = filename:join([PluginDir, "ebin"]),
+    AppFile = filename:join([Ebin, "*.app"]),
+    AppName = case filelib:wildcard(AppFile) of
+                  [App] ->
+                      list_to_atom(filename:basename(App, ".app"));
+                  [] ->
+                      ?LOG(alert, "plugin_app_file_not_found: ~s", [AppFile]),
+                      error({plugin_app_file_not_found, AppFile})
+              end,
+    ok = load_plugin_app(AppName, Ebin),
+    ok = load_plugin_conf(AppName, PluginDir).
+
+load_plugin_app(AppName, Ebin) ->
     _ = code:add_patha(Ebin),
     Modules = filelib:wildcard(filename:join([Ebin, "*.beam"])),
-    lists:foreach(fun(Mod) ->
-        Module = list_to_atom(filename:basename(Mod, ".beam")),
-        code:load_file(Module)
-    end, Modules),
-    case filelib:wildcard(Ebin ++ "/*.app") of
-        [App|_] -> application:load(list_to_atom(filename:basename(App, ".app")));
-        _ -> ?LOG(alert, "Plugin not found."),
-             {error, load_app_fail}
+    lists:foreach(
+        fun(BeamFile) ->
+                Module = list_to_atom(filename:basename(BeamFile, ".beam")),
+                case code:ensure_loaded(Module) of
+                    {module, Module} -> ok;
+                    {error, Reason} -> error({failed_to_load_plugin_beam, BeamFile, Reason})
+                end
+        end, Modules),
+    case application:load(AppName) of
+        ok -> ok;
+        {error, {already_loaded, _}} -> ok
     end.
 
-init_expand_plugin_config(PluginDir) ->
-    Priv = PluginDir ++ "/priv",
-    Etc  = PluginDir ++ "/etc",
-    Schema = filelib:wildcard(Priv ++ "/*.schema"),
-    Conf = case filelib:wildcard(Etc ++ "/*.conf") of
-        [] -> [];
-        [Conf1] -> cuttlefish_conf:file(Conf1)
-    end,
+load_plugin_conf(AppName, PluginDir) ->
+    Priv = filename:join([PluginDir, "priv"]),
+    Etc  = filename:join([PluginDir, "etc"]),
+    Schema = filelib:wildcard(filename:join([Priv, "*.schema"])),
+    ConfFile = filename:join([Etc, atom_to_list(AppName) ++ ".conf"]),
+    Conf = case filelib:is_file(ConfFile) of
+               true -> cuttlefish_conf:file(ConfFile);
+               false -> error({conf_file_not_found, ConfFile})
+           end,
+    ?LOG(debug, "loading_extra_plugin_config conf=~s, schema=~s", [ConfFile, Schema]),
     AppsEnv = cuttlefish_generator:map(cuttlefish_schema:files(Schema), Conf),
-    lists:foreach(fun({AppName, Envs}) ->
-        [application:set_env(AppName, Par, Val) || {Par, Val} <- Envs]
+    lists:foreach(fun({AppName1, Envs}) ->
+        [application:set_env(AppName1, Par, Val) || {Par, Val} <- Envs]
     end, AppsEnv).
 
 ensure_file(File) ->
@@ -223,19 +238,31 @@ load_plugins(Names, Persistent) ->
 
 generate_configs(App) ->
     ConfigFile = filename:join([emqx:get_env(plugins_etc_dir), App]) ++ ".config",
-    ConfFile = filename:join([emqx:get_env(plugins_etc_dir), App]) ++ ".conf",
-    SchemaFile = filename:join([code:priv_dir(App), App]) ++ ".schema",
-    case {filelib:is_file(ConfigFile), filelib:is_file(ConfFile) andalso filelib:is_file(SchemaFile)} of
-        {true, _} ->
+    case filelib:is_file(ConfigFile) of
+        true ->
             {ok, [Configs]} = file:consult(ConfigFile),
             Configs;
-        {_, true} ->
+        false ->
+            do_generate_configs(App)
+    end.
+
+do_generate_configs(App) ->
+    Name1 = filename:join([emqx:get_env(plugins_etc_dir), App]) ++ ".conf",
+    Name2 = filename:join([code:lib_dir(App), "etc", App]) ++ ".conf",
+    ConfFile = case {filelib:is_file(Name1), filelib:is_file(Name2)} of
+                   {true, _} -> Name1;
+                   {false, true} -> Name2;
+                   {false, false} -> error({config_not_found, [Name1, Name2]})
+               end,
+    SchemaFile = filename:join([code:priv_dir(App), App]) ++ ".schema",
+    case filelib:is_file(SchemaFile) of
+        true ->
             Schema = cuttlefish_schema:files([SchemaFile]),
             Conf = cuttlefish_conf:file(ConfFile),
             LogFun = fun(Key, Value) -> ?LOG(info, "~s = ~p", [string:join(Key, "."), Value]) end,
             cuttlefish_generator:map(Schema, Conf, undefined, LogFun);
-        {false, false} ->
-            error({config_not_found, {ConfigFile, ConfFile, SchemaFile}})
+        false ->
+            error({schema_not_found, SchemaFile})
     end.
 
 apply_configs([]) ->

+ 7 - 11
test/emqx_plugins_SUITE.erl

@@ -30,24 +30,20 @@ init_per_suite(Config) ->
 
     DataPath = proplists:get_value(data_dir, Config),
     AppPath = filename:join([DataPath, "emqx_mini_plugin"]),
-    Cmd = lists:flatten(io_lib:format("cd ~s && make && cp -r etc _build/default/lib/emqx_mini_plugin/", [AppPath])),
+    Cmd = lists:flatten(io_lib:format("cd ~s && make", [AppPath])),
 
     ct:pal("Executing ~s~n", [Cmd]),
     ct:pal("~n ~s~n", [os:cmd(Cmd)]),
 
-    code:add_path(filename:join([AppPath, "_build", "default", "lib", "emqx_mini_plugin", "ebin"])),
-
     put(loaded_file, filename:join([DataPath, "loaded_plugins"])),
     emqx_ct_helpers:boot_modules([]),
-    emqx_ct_helpers:start_apps([], fun set_sepecial_cfg/1),
+    emqx_ct_helpers:start_apps([], fun(_) -> set_sepecial_cfg(DataPath) end),
 
     Config.
-    
-set_sepecial_cfg(_) ->
-    ExpandPath = filename:dirname(code:lib_dir(emqx_mini_plugin)),
 
+set_sepecial_cfg(PluginsDir) ->
     application:set_env(emqx, plugins_loaded_file, get(loaded_file)),
-    application:set_env(emqx, expand_plugins_dir, ExpandPath),
+    application:set_env(emqx, expand_plugins_dir, PluginsDir),
     ok.
 
 end_per_suite(_Config) ->
@@ -58,7 +54,6 @@ t_load(_) ->
     ?assertEqual(ok, emqx_plugins:unload()),
 
     ?assertEqual({error, not_found}, emqx_plugins:load(not_existed_plugin)),
-    ?assertEqual({error, parse_config_file_failed}, emqx_plugins:load(emqx_mini_plugin)),
     ?assertEqual({error, not_started}, emqx_plugins:unload(emqx_mini_plugin)),
 
     application:set_env(emqx, expand_plugins_dir, undefined),
@@ -75,8 +70,9 @@ t_init_config(_) ->
     file:delete(ConfFile),
     ?assertEqual({ok,test}, application:get_env(emqx_mini_plugin, mininame)).
 
-t_load_expand_plugin(_) ->
-    ?assertEqual({error, load_app_fail}, emqx_plugins:load_expand_plugin("./not_existed_path/")).
+t_load_ext_plugin(_) ->
+    ?assertError({plugin_app_file_not_found, _},
+                 emqx_plugins:load_ext_plugin("./not_existed_path/")).
 
 t_list(_) ->
     ?assertMatch([{plugin, _, _, _, _, _, _, _} | _ ], emqx_plugins:list()).

+ 2 - 11
test/emqx_plugins_SUITE_data/emqx_mini_plugin/Makefile

@@ -8,6 +8,7 @@ all: compile
 
 compile:
 	$(REBAR) compile
+	cp -r _build/default/lib/emqx_mini_plugin/ebin ./
 
 clean: distclean
 
@@ -22,14 +23,4 @@ xref:
 
 distclean:
 	@rm -rf _build
-	@rm -f data/app.*.config data/vm.*.args rebar.lock
-
-CUTTLEFISH_SCRIPT = _build/default/lib/cuttlefish/cuttlefish
-
-$(CUTTLEFISH_SCRIPT):
-	@${REBAR} get-deps
-	@if [ ! -f cuttlefish ]; then make -C _build/default/lib/cuttlefish; fi
-
-app.config: $(CUTTLEFISH_SCRIPT) etc/emqx_mini_plugin.conf
-	$(verbose) $(CUTTLEFISH_SCRIPT) -l info -e etc/ -c etc/emqx_mini_plugin.conf -i priv/emqx_mini_plugin.schema -d data
-
+	@rm -f ebin/ data/app.*.config data/vm.*.args rebar.lock

+ 1 - 3
test/emqx_plugins_SUITE_data/emqx_mini_plugin/rebar.config

@@ -1,5 +1,4 @@
-{deps,
-    []}.
+{deps, []}.
 
 {edoc_opts, [{preprocess, true}]}.
 {erl_opts, [warn_unused_vars,
@@ -19,7 +18,6 @@
 {profiles,
     [{test, [
         {deps, [ {emqx_ct_helper, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "v1.1.4"}}}
-               , {cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v3.0.0"}}}
                ]}
     ]}
 ]}.