Skip to content

Commit

Permalink
Merge pull request #10422 from thalesmg/fix-plugin-sync-single-node-v50
Browse files Browse the repository at this point in the history
fix(plugins): attempt to extract plugin from current node on startup
  • Loading branch information
thalesmg committed Apr 18, 2023
2 parents 9efbb71 + 89cd6cf commit 6a1ef5e
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 12 deletions.
1 change: 1 addition & 0 deletions apps/emqx/test/emqx_common_test_helpers.erl
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,7 @@ setup_node(Node, Opts) when is_map(Opts) ->
load_apps => LoadApps,
apps => Apps,
env => Env,
join_to => JoinTo,
start_apps => StartApps
}
]
Expand Down
2 changes: 1 addition & 1 deletion apps/emqx_plugins/src/emqx_plugins.app.src
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
%% -*- mode: erlang -*-
{application, emqx_plugins, [
{description, "EMQX Plugin Management"},
{vsn, "0.1.3"},
{vsn, "0.1.4"},
{modules, []},
{mod, {emqx_plugins_app, []}},
{applications, [kernel, stdlib, emqx]},
Expand Down
37 changes: 27 additions & 10 deletions apps/emqx_plugins/src/emqx_plugins.erl
Original file line number Diff line number Diff line change
Expand Up @@ -479,22 +479,39 @@ ensure_exists_and_installed(NameVsn) ->
case filelib:is_dir(dir(NameVsn)) of
true ->
ok;
_ ->
Nodes = [N || N <- mria:running_nodes(), N /= node()],
case get_from_any_node(Nodes, NameVsn, []) of
false ->
%% Do we have the package, but it's not extracted yet?
case get_tar(NameVsn) of
{ok, TarContent} ->
ok = file:write_file(pkg_file(NameVsn), TarContent),
ok = do_ensure_installed(NameVsn);
{error, NodeErrors} ->
?SLOG(error, #{
msg => "failed_to_copy_plugin_from_other_nodes",
name_vsn => NameVsn,
node_errors => NodeErrors
}),
{error, plugin_not_found}
_ ->
%% If not, try to get it from the cluster.
do_get_from_cluster(NameVsn)
end
end.

do_get_from_cluster(NameVsn) ->
Nodes = [N || N <- mria:running_nodes(), N /= node()],
case get_from_any_node(Nodes, NameVsn, []) of
{ok, TarContent} ->
ok = file:write_file(pkg_file(NameVsn), TarContent),
ok = do_ensure_installed(NameVsn);
{error, NodeErrors} when Nodes =/= [] ->
?SLOG(error, #{
msg => "failed_to_copy_plugin_from_other_nodes",
name_vsn => NameVsn,
node_errors => NodeErrors
}),
{error, plugin_not_found};
{error, _} ->
?SLOG(error, #{
msg => "no_nodes_to_copy_plugin_from",
name_vsn => NameVsn
}),
{error, plugin_not_found}
end.

get_from_any_node([], _NameVsn, Errors) ->
{error, Errors};
get_from_any_node([Node | T], NameVsn, Errors) ->
Expand Down
77 changes: 76 additions & 1 deletion apps/emqx_plugins/test/emqx_plugins_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ all() ->

groups() ->
[
{copy_plugin, [sequence], [group_t_copy_plugin_to_a_new_node]},
{copy_plugin, [sequence], [
group_t_copy_plugin_to_a_new_node,
group_t_copy_plugin_to_a_new_node_single_node
]},
{create_tar_copy_plugin, [sequence], [group_t_copy_plugin_to_a_new_node]}
].

Expand Down Expand Up @@ -601,6 +604,78 @@ group_t_copy_plugin_to_a_new_node(Config) ->
rpc:call(CopyToNode, emqx_plugins, describe, [NameVsn])
).

%% checks that we can start a cluster with a lone node.
group_t_copy_plugin_to_a_new_node_single_node({init, Config}) ->
PrivDataDir = ?config(priv_dir, Config),
ToInstallDir = filename:join(PrivDataDir, "plugins_copy_to"),
file:del_dir_r(ToInstallDir),
ok = filelib:ensure_path(ToInstallDir),
#{package := Package, release_name := PluginName} = get_demo_plugin_package(ToInstallDir),
NameVsn = filename:basename(Package, ?PACKAGE_SUFFIX),
[{CopyTo, CopyToOpts}] =
emqx_common_test_helpers:emqx_cluster(
[
{core, plugins_copy_to}
],
#{
apps => [emqx_conf, emqx_plugins],
env => [
{emqx, init_config_load_done, false},
{emqx, boot_modules, []}
],
env_handler => fun
(emqx_plugins) ->
ok = emqx_plugins:put_config(install_dir, ToInstallDir),
%% this is to simulate an user setting the state
%% via environment variables before starting the node
ok = emqx_plugins:put_config(
states,
[#{name_vsn => NameVsn, enable => true}]
),
ok;
(_) ->
ok
end,
priv_data_dir => PrivDataDir,
schema_mod => emqx_conf_schema,
peer_mod => slave,
load_schema => true
}
),
[
{to_install_dir, ToInstallDir},
{copy_to_node_name, CopyTo},
{copy_to_opts, CopyToOpts},
{name_vsn, NameVsn},
{plugin_name, PluginName}
| Config
];
group_t_copy_plugin_to_a_new_node_single_node({'end', Config}) ->
CopyToNode = proplists:get_value(copy_to_node, Config),
ok = emqx_common_test_helpers:stop_slave(CopyToNode),
ok = file:del_dir_r(proplists:get_value(to_install_dir, Config)),
ok;
group_t_copy_plugin_to_a_new_node_single_node(Config) ->
CopyTo = ?config(copy_to_node_name, Config),
CopyToOpts = ?config(copy_to_opts, Config),
ToInstallDir = ?config(to_install_dir, Config),
NameVsn = proplists:get_value(name_vsn, Config),
%% Start the node for the first time. The plugin should start
%% successfully even if it's not extracted yet. Simply starting
%% the node would crash if not working properly.
CopyToNode = emqx_common_test_helpers:start_slave(CopyTo, CopyToOpts),
ct:pal("~p config:\n ~p", [
CopyToNode, erpc:call(CopyToNode, emqx_plugins, get_config, [[], #{}])
]),
ct:pal("~p install_dir:\n ~p", [
CopyToNode, erpc:call(CopyToNode, file, list_dir, [ToInstallDir])
]),
?assertMatch(
{ok, #{running_status := running, config_status := enabled}},
rpc:call(CopyToNode, emqx_plugins, describe, [NameVsn])
),
ok.

make_tar(Cwd, NameWithVsn) ->
make_tar(Cwd, NameWithVsn, NameWithVsn).

Expand Down
1 change: 1 addition & 0 deletions changes/ce/fix-10422.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a bug where external plugins could not be configured via environment variables in a lone-node cluster.

0 comments on commit 6a1ef5e

Please sign in to comment.