diff --git a/deps/rabbit/src/rabbit_feature_flags.erl b/deps/rabbit/src/rabbit_feature_flags.erl
index d61a882c4a7e..8fcad06287e8 100644
--- a/deps/rabbit/src/rabbit_feature_flags.erl
+++ b/deps/rabbit/src/rabbit_feature_flags.erl
@@ -1826,15 +1826,19 @@ sync_feature_flags_with_cluster(Nodes, NodeIsVirgin) ->
%% @private
sync_feature_flags_with_cluster(Nodes, NodeIsVirgin, Timeout) ->
+ Clustered = Nodes =/= [],
case is_enabled(feature_flags_v2) of
- true -> rabbit_ff_controller:sync_cluster();
- false -> sync_cluster_v1(Nodes, NodeIsVirgin, Timeout)
+ true when Clustered -> rabbit_ff_controller:sync_cluster();
+ true when NodeIsVirgin -> rabbit_ff_controller:enable_default();
+ true -> ok;
+ false -> sync_cluster_v1(Nodes, NodeIsVirgin, Timeout)
end.
sync_cluster_v1([], NodeIsVirgin, _) ->
case NodeIsVirgin of
true ->
- FeatureNames = get_forced_feature_flag_names(),
+ FeatureNames =
+ rabbit_ff_controller:get_forced_feature_flag_names(),
case remote_nodes() of
[] when FeatureNames =:= undefined ->
rabbit_log_feature_flags:debug(
@@ -2068,75 +2072,6 @@ do_sync_feature_flags_with_node([FeatureFlag | Rest]) ->
do_sync_feature_flags_with_node([]) ->
ok.
--spec get_forced_feature_flag_names() -> [feature_name()] | undefined.
-%% @private
-%% @doc
-%% Returns the (possibly empty) list of feature flags the user want
-%% to enable out-of-the-box when starting a node for the first time.
-%%
-%% Without this, the default is to enable all the supported feature
-%% flags.
-%%
-%% There are two ways to specify that list:
-%%
-%% - Using the `$RABBITMQ_FEATURE_FLAGS' environment variable; for
-%% instance `RABBITMQ_FEATURE_FLAGS=quorum_queue,mnevis'.
-%% - Using the `forced_feature_flags_on_init' configuration parameter;
-%% for instance
-%% `{rabbit, [{forced_feature_flags_on_init, [quorum_queue, mnevis]}]}'.
-%%
-%%
-%% The environment variable has precedence over the configuration
-%% parameter.
-
-get_forced_feature_flag_names() ->
- Ret = case get_forced_feature_flag_names_from_env() of
- undefined -> get_forced_feature_flag_names_from_config();
- List -> List
- end,
- case Ret of
- undefined -> ok;
- [] -> rabbit_log_feature_flags:info(
- "Feature flags: automatic enablement of feature "
- "flags disabled (i.e. none will be enabled "
- "automatically)");
- _ -> rabbit_log_feature_flags:info(
- "Feature flags: automatic enablement of feature "
- "flags limited to the following list: ~tp", [Ret])
- end,
- Ret.
-
--spec get_forced_feature_flag_names_from_env() -> [feature_name()] | undefined.
-%% @private
-
-get_forced_feature_flag_names_from_env() ->
- case rabbit_prelaunch:get_context() of
- #{forced_feature_flags_on_init := ForcedFFs}
- when is_list(ForcedFFs) ->
- ForcedFFs;
- _ ->
- undefined
- end.
-
--spec get_forced_feature_flag_names_from_config() -> [feature_name()] | undefined.
-%% @private
-
-get_forced_feature_flag_names_from_config() ->
- Value = application:get_env(rabbit,
- forced_feature_flags_on_init,
- undefined),
- case Value of
- undefined ->
- Value;
- _ when is_list(Value) ->
- case lists:all(fun is_atom/1, Value) of
- true -> Value;
- false -> undefined
- end;
- _ ->
- undefined
- end.
-
-spec verify_which_feature_flags_are_actually_enabled() ->
ok | {error, any()} | no_return().
%% @private
diff --git a/deps/rabbit/src/rabbit_ff_controller.erl b/deps/rabbit/src/rabbit_ff_controller.erl
index a4a796e7edb9..2eff35494295 100644
--- a/deps/rabbit/src/rabbit_ff_controller.erl
+++ b/deps/rabbit/src/rabbit_ff_controller.erl
@@ -33,16 +33,20 @@
-export([is_supported/1, is_supported/2,
enable/1,
+ enable_default/0,
check_node_compatibility/1,
sync_cluster/0,
- refresh_after_app_load/0]).
+ refresh_after_app_load/0,
+ get_forced_feature_flag_names/0]).
%% Internal use only.
-export([start/0,
start_link/0,
rpc_call/5,
all_nodes/0,
- running_nodes/0]).
+ running_nodes/0,
+ collect_inventory_on_nodes/1, collect_inventory_on_nodes/2,
+ mark_as_enabled_on_nodes/4]).
%% gen_statem callbacks.
-export([callback_mode/0,
@@ -96,6 +100,25 @@ enable(FeatureNames) when is_list(FeatureNames) ->
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
gen_statem:call(?LOCAL_NAME, {enable, FeatureNames}).
+enable_default() ->
+ ?LOG_DEBUG(
+ "Feature flags: configure initial feature flags state",
+ [],
+ #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
+ case erlang:whereis(?LOCAL_NAME) of
+ Pid when is_pid(Pid) ->
+ %% The function is called while `rabbit' is running.
+ gen_statem:call(?LOCAL_NAME, enable_default);
+ undefined ->
+ %% The function is called while `rabbit' is stopped. We need to
+ %% start a one-off controller, again to make sure concurrent
+ %% changes are blocked.
+ {ok, Pid} = start_link(),
+ Ret = gen_statem:call(Pid, enable_default),
+ gen_statem:stop(Pid),
+ Ret
+ end.
+
check_node_compatibility(RemoteNode) ->
ThisNode = node(),
?LOG_DEBUG(
@@ -248,6 +271,8 @@ updating_feature_flag_states(
proceed_with_task({enable, FeatureNames}) ->
enable_task(FeatureNames);
+proceed_with_task(enable_default) ->
+ enable_default_task();
proceed_with_task(sync_cluster) ->
sync_cluster_task();
proceed_with_task(refresh_after_app_load) ->
@@ -451,6 +476,122 @@ enable_task(FeatureNames) ->
{error, missing_clustered_nodes}
end.
+enable_default_task() ->
+ FeatureNames = get_forced_feature_flag_names(),
+ case FeatureNames of
+ undefined ->
+ ?LOG_DEBUG(
+ "Feature flags: starting an unclustered node for the first "
+ "time: all stable feature flags will be enabled by default",
+ #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
+ {ok, Inventory} = collect_inventory_on_nodes([node()]),
+ #{feature_flags := FeatureFlags} = Inventory,
+ StableFeatureNames =
+ maps:fold(
+ fun
+ (FeatureName, #{stability := stable}, Acc) ->
+ [FeatureName | Acc];
+ (_FeatureName, _FeatureProps, Acc) ->
+ Acc
+ end, [], FeatureFlags),
+ enable_many(Inventory, StableFeatureNames);
+ [] ->
+ ?LOG_DEBUG(
+ "Feature flags: starting an unclustered node for the first "
+ "time: all feature flags are forcibly left disabled from "
+ "the $RABBITMQ_FEATURE_FLAGS environment variable",
+ #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
+ ok;
+ _ ->
+ ?LOG_DEBUG(
+ "Feature flags: starting an unclustered node for the first "
+ "time: only the following feature flags specified in the "
+ "$RABBITMQ_FEATURE_FLAGS environment variable will be enabled: "
+ "~tp",
+ [FeatureNames],
+ #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
+ {ok, Inventory} = collect_inventory_on_nodes([node()]),
+ enable_many(Inventory, FeatureNames)
+ end.
+
+-spec get_forced_feature_flag_names() -> Ret when
+ Ret :: FeatureNames | undefined,
+ FeatureNames :: [rabbit_feature_flags:feature_name()].
+%% @doc Returns the (possibly empty) list of feature flags the user wants to
+%% enable out-of-the-box when starting a node for the first time.
+%%
+%% Without this, the default is to enable all the supported stable feature
+%% flags.
+%%
+%% There are two ways to specify that list:
+%%
+%% - Using the `$RABBITMQ_FEATURE_FLAGS' environment variable; for
+%% instance `RABBITMQ_FEATURE_FLAGS=quorum_queue,mnevis'.
+%% - Using the `forced_feature_flags_on_init' configuration parameter;
+%% for instance
+%% `{rabbit, [{forced_feature_flags_on_init, [quorum_queue, mnevis]}]}'.
+%%
+%%
+%% The environment variable has precedence over the configuration parameter.
+%%
+%% @private
+
+get_forced_feature_flag_names() ->
+ Ret = case get_forced_feature_flag_names_from_env() of
+ undefined -> get_forced_feature_flag_names_from_config();
+ List -> List
+ end,
+ case Ret of
+ undefined ->
+ ok;
+ [] ->
+ ?LOG_INFO(
+ "Feature flags: automatic enablement of feature flags "
+ "disabled (i.e. none will be enabled automatically)",
+ #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS});
+ _ ->
+ ?LOG_INFO(
+ "Feature flags: automatic enablement of feature flags "
+ "limited to the following list: ~tp",
+ [Ret],
+ #{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS})
+ end,
+ Ret.
+
+-spec get_forced_feature_flag_names_from_env() -> Ret when
+ Ret :: FeatureNames | undefined,
+ FeatureNames :: [rabbit_feature_flags:feature_name()].
+%% @private
+
+get_forced_feature_flag_names_from_env() ->
+ case rabbit_prelaunch:get_context() of
+ #{forced_feature_flags_on_init := ForcedFFs}
+ when is_list(ForcedFFs) ->
+ ForcedFFs;
+ _ ->
+ undefined
+ end.
+
+-spec get_forced_feature_flag_names_from_config() -> Ret when
+ Ret :: FeatureNames | undefined,
+ FeatureNames :: [rabbit_feature_flags:feature_name()].
+%% @private
+
+get_forced_feature_flag_names_from_config() ->
+ Value = application:get_env(
+ rabbit, forced_feature_flags_on_init, undefined),
+ case Value of
+ undefined ->
+ Value;
+ _ when is_list(Value) ->
+ case lists:all(fun is_atom/1, Value) of
+ true -> Value;
+ false -> undefined
+ end;
+ _ ->
+ undefined
+ end.
+
-spec sync_cluster_task() -> Ret when
Ret :: ok | {error, Reason},
Reason :: term().