Skip to content

Commit bf973c2

Browse files
committed
rabbit_feature_flags: Support virgin node init with feature flags v2
So far, when a new node is started, the feature flags v1 code is used to enable all stable feature flags (the default behavior). In the list, there would be `feature_flags_v2`, so this one would be enabled first and all others would be using the v2 code path. If we mark `feature_flags_v2` as required, the v2 code path didn't handle this so far. This patch adds this missing code. Now, a virgin node's feature flags can be enabled fully using the v2 code path. As part of that, th `get_forced_feature_flag_names()` function is moved from `rabbit_feature_flags` to `rabbit_ff_controller`.
1 parent aa71d9e commit bf973c2

File tree

2 files changed

+147
-73
lines changed

2 files changed

+147
-73
lines changed

deps/rabbit/src/rabbit_feature_flags.erl

Lines changed: 7 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,15 +1826,19 @@ sync_feature_flags_with_cluster(Nodes, NodeIsVirgin) ->
18261826
%% @private
18271827

18281828
sync_feature_flags_with_cluster(Nodes, NodeIsVirgin, Timeout) ->
1829+
Clustered = Nodes =/= [],
18291830
case is_enabled(feature_flags_v2) of
1830-
true -> rabbit_ff_controller:sync_cluster();
1831-
false -> sync_cluster_v1(Nodes, NodeIsVirgin, Timeout)
1831+
true when Clustered -> rabbit_ff_controller:sync_cluster();
1832+
true when NodeIsVirgin -> rabbit_ff_controller:enable_default();
1833+
true -> ok;
1834+
false -> sync_cluster_v1(Nodes, NodeIsVirgin, Timeout)
18321835
end.
18331836

18341837
sync_cluster_v1([], NodeIsVirgin, _) ->
18351838
case NodeIsVirgin of
18361839
true ->
1837-
FeatureNames = get_forced_feature_flag_names(),
1840+
FeatureNames =
1841+
rabbit_ff_controller:get_forced_feature_flag_names(),
18381842
case remote_nodes() of
18391843
[] when FeatureNames =:= undefined ->
18401844
rabbit_log_feature_flags:debug(
@@ -2016,75 +2020,6 @@ do_sync_feature_flags_with_node([FeatureFlag | Rest]) ->
20162020
do_sync_feature_flags_with_node([]) ->
20172021
ok.
20182022

2019-
-spec get_forced_feature_flag_names() -> [feature_name()] | undefined.
2020-
%% @private
2021-
%% @doc
2022-
%% Returns the (possibly empty) list of feature flags the user want
2023-
%% to enable out-of-the-box when starting a node for the first time.
2024-
%%
2025-
%% Without this, the default is to enable all the supported feature
2026-
%% flags.
2027-
%%
2028-
%% There are two ways to specify that list:
2029-
%% <ol>
2030-
%% <li>Using the `$RABBITMQ_FEATURE_FLAGS' environment variable; for
2031-
%% instance `RABBITMQ_FEATURE_FLAGS=quorum_queue,mnevis'.</li>
2032-
%% <li>Using the `forced_feature_flags_on_init' configuration parameter;
2033-
%% for instance
2034-
%% `{rabbit, [{forced_feature_flags_on_init, [quorum_queue, mnevis]}]}'.</li>
2035-
%% </ol>
2036-
%%
2037-
%% The environment variable has precedence over the configuration
2038-
%% parameter.
2039-
2040-
get_forced_feature_flag_names() ->
2041-
Ret = case get_forced_feature_flag_names_from_env() of
2042-
undefined -> get_forced_feature_flag_names_from_config();
2043-
List -> List
2044-
end,
2045-
case Ret of
2046-
undefined -> ok;
2047-
[] -> rabbit_log_feature_flags:info(
2048-
"Feature flags: automatic enablement of feature "
2049-
"flags disabled (i.e. none will be enabled "
2050-
"automatically)");
2051-
_ -> rabbit_log_feature_flags:info(
2052-
"Feature flags: automatic enablement of feature "
2053-
"flags limited to the following list: ~tp", [Ret])
2054-
end,
2055-
Ret.
2056-
2057-
-spec get_forced_feature_flag_names_from_env() -> [feature_name()] | undefined.
2058-
%% @private
2059-
2060-
get_forced_feature_flag_names_from_env() ->
2061-
case rabbit_prelaunch:get_context() of
2062-
#{forced_feature_flags_on_init := ForcedFFs}
2063-
when is_list(ForcedFFs) ->
2064-
ForcedFFs;
2065-
_ ->
2066-
undefined
2067-
end.
2068-
2069-
-spec get_forced_feature_flag_names_from_config() -> [feature_name()] | undefined.
2070-
%% @private
2071-
2072-
get_forced_feature_flag_names_from_config() ->
2073-
Value = application:get_env(rabbit,
2074-
forced_feature_flags_on_init,
2075-
undefined),
2076-
case Value of
2077-
undefined ->
2078-
Value;
2079-
_ when is_list(Value) ->
2080-
case lists:all(fun is_atom/1, Value) of
2081-
true -> Value;
2082-
false -> undefined
2083-
end;
2084-
_ ->
2085-
undefined
2086-
end.
2087-
20882023
-spec verify_which_feature_flags_are_actually_enabled() ->
20892024
ok | {error, any()} | no_return().
20902025
%% @private

deps/rabbit/src/rabbit_ff_controller.erl

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@
3333

3434
-export([is_supported/1, is_supported/2,
3535
enable/1,
36+
enable_default/0,
3637
check_node_compatibility/1,
3738
sync_cluster/0,
38-
refresh_after_app_load/0]).
39+
refresh_after_app_load/0,
40+
get_forced_feature_flag_names/0]).
3941

4042
%% Internal use only.
4143
-export([start/0,
@@ -96,6 +98,25 @@ enable(FeatureNames) when is_list(FeatureNames) ->
9698
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
9799
gen_statem:call(?LOCAL_NAME, {enable, FeatureNames}).
98100

101+
enable_default() ->
102+
?LOG_DEBUG(
103+
"Feature flags: configure initial feature flags state",
104+
[],
105+
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
106+
case erlang:whereis(?LOCAL_NAME) of
107+
Pid when is_pid(Pid) ->
108+
%% The function is called while `rabbit' is running.
109+
gen_statem:call(?LOCAL_NAME, enable_default);
110+
undefined ->
111+
%% The function is called while `rabbit' is stopped. We need to
112+
%% start a one-off controller, again to make sure concurrent
113+
%% changes are blocked.
114+
{ok, Pid} = start_link(),
115+
Ret = gen_statem:call(Pid, enable_default),
116+
gen_statem:stop(Pid),
117+
Ret
118+
end.
119+
99120
check_node_compatibility(RemoteNode) ->
100121
ThisNode = node(),
101122
?LOG_DEBUG(
@@ -248,6 +269,8 @@ updating_feature_flag_states(
248269

249270
proceed_with_task({enable, FeatureNames}) ->
250271
enable_task(FeatureNames);
272+
proceed_with_task(enable_default) ->
273+
enable_default_task();
251274
proceed_with_task(sync_cluster) ->
252275
sync_cluster_task();
253276
proceed_with_task(refresh_after_app_load) ->
@@ -451,6 +474,122 @@ enable_task(FeatureNames) ->
451474
{error, missing_clustered_nodes}
452475
end.
453476

477+
enable_default_task() ->
478+
FeatureNames = get_forced_feature_flag_names(),
479+
case FeatureNames of
480+
undefined ->
481+
?LOG_DEBUG(
482+
"Feature flags: starting an unclustered node for the first "
483+
"time: all stable feature flags will be enabled by default",
484+
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
485+
{ok, Inventory} = collect_inventory_on_nodes([node()]),
486+
#{feature_flags := FeatureFlags} = Inventory,
487+
StableFeatureNames =
488+
maps:fold(
489+
fun
490+
(FeatureName, #{stability := stable}, Acc) ->
491+
[FeatureName | Acc];
492+
(_FeatureName, _FeatureProps, Acc) ->
493+
Acc
494+
end, [], FeatureFlags),
495+
enable_many(Inventory, StableFeatureNames);
496+
[] ->
497+
?LOG_DEBUG(
498+
"Feature flags: starting an unclustered node for the first "
499+
"time: all feature flags are forcibly left disabled from "
500+
"the $RABBITMQ_FEATURE_FLAGS environment variable",
501+
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
502+
ok;
503+
_ ->
504+
?LOG_DEBUG(
505+
"Feature flags: starting an unclustered node for the first "
506+
"time: only the following feature flags specified in the "
507+
"$RABBITMQ_FEATURE_FLAGS environment variable will be enabled: "
508+
"~tp",
509+
[FeatureNames],
510+
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
511+
{ok, Inventory} = collect_inventory_on_nodes([node()]),
512+
enable_many(Inventory, FeatureNames)
513+
end.
514+
515+
-spec get_forced_feature_flag_names() -> Ret when
516+
Ret :: FeatureNames | undefined,
517+
FeatureNames :: [rabbit_feature_flags:feature_name()].
518+
%% @doc Returns the (possibly empty) list of feature flags the user wants to
519+
%% enable out-of-the-box when starting a node for the first time.
520+
%%
521+
%% Without this, the default is to enable all the supported stable feature
522+
%% flags.
523+
%%
524+
%% There are two ways to specify that list:
525+
%% <ol>
526+
%% <li>Using the `$RABBITMQ_FEATURE_FLAGS' environment variable; for
527+
%% instance `RABBITMQ_FEATURE_FLAGS=quorum_queue,mnevis'.</li>
528+
%% <li>Using the `forced_feature_flags_on_init' configuration parameter;
529+
%% for instance
530+
%% `{rabbit, [{forced_feature_flags_on_init, [quorum_queue, mnevis]}]}'.</li>
531+
%% </ol>
532+
%%
533+
%% The environment variable has precedence over the configuration parameter.
534+
%%
535+
%% @private
536+
537+
get_forced_feature_flag_names() ->
538+
Ret = case get_forced_feature_flag_names_from_env() of
539+
undefined -> get_forced_feature_flag_names_from_config();
540+
List -> List
541+
end,
542+
case Ret of
543+
undefined ->
544+
ok;
545+
[] ->
546+
?LOG_INFO(
547+
"Feature flags: automatic enablement of feature flags "
548+
"disabled (i.e. none will be enabled automatically)",
549+
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS});
550+
_ ->
551+
?LOG_INFO(
552+
"Feature flags: automatic enablement of feature flags "
553+
"limited to the following list: ~tp",
554+
[Ret],
555+
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS})
556+
end,
557+
Ret.
558+
559+
-spec get_forced_feature_flag_names_from_env() -> Ret when
560+
Ret :: FeatureNames | undefined,
561+
FeatureNames :: [rabbit_feature_flags:feature_name()].
562+
%% @private
563+
564+
get_forced_feature_flag_names_from_env() ->
565+
case rabbit_prelaunch:get_context() of
566+
#{forced_feature_flags_on_init := ForcedFFs}
567+
when is_list(ForcedFFs) ->
568+
ForcedFFs;
569+
_ ->
570+
undefined
571+
end.
572+
573+
-spec get_forced_feature_flag_names_from_config() -> Ret when
574+
Ret :: FeatureNames | undefined,
575+
FeatureNames :: [rabbit_feature_flags:feature_name()].
576+
%% @private
577+
578+
get_forced_feature_flag_names_from_config() ->
579+
Value = application:get_env(
580+
rabbit, forced_feature_flags_on_init, undefined),
581+
case Value of
582+
undefined ->
583+
Value;
584+
_ when is_list(Value) ->
585+
case lists:all(fun is_atom/1, Value) of
586+
true -> Value;
587+
false -> undefined
588+
end;
589+
_ ->
590+
undefined
591+
end.
592+
454593
-spec sync_cluster_task() -> Ret when
455594
Ret :: ok | {error, Reason},
456595
Reason :: term().

0 commit comments

Comments
 (0)