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: -%%
    -%%
  1. Using the `$RABBITMQ_FEATURE_FLAGS' environment variable; for -%% instance `RABBITMQ_FEATURE_FLAGS=quorum_queue,mnevis'.
  2. -%%
  3. Using the `forced_feature_flags_on_init' configuration parameter; -%% for instance -%% `{rabbit, [{forced_feature_flags_on_init, [quorum_queue, mnevis]}]}'.
  4. -%%
-%% -%% 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: +%%
    +%%
  1. Using the `$RABBITMQ_FEATURE_FLAGS' environment variable; for +%% instance `RABBITMQ_FEATURE_FLAGS=quorum_queue,mnevis'.
  2. +%%
  3. Using the `forced_feature_flags_on_init' configuration parameter; +%% for instance +%% `{rabbit, [{forced_feature_flags_on_init, [quorum_queue, mnevis]}]}'.
  4. +%%
+%% +%% 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().