Skip to content

Commit 2c2142e

Browse files
committed
rabbit_feature_flags: Accept "+feature1,-feature2" in $RABBITMQ_FEATURE_FLAGS
[Why] Before this patch, the $RABBITMQ_FEATURE_FLAGS environment variable took an exhaustive list of feature flags to enable. This list overrode the default of enabling all stable feature flags. It made it inconvenient when a user wanted to enable an experimental feature flag like `khepri_db` while still leaving the default behavior. [How] $RABBITMQ_FEATURE_FLAGS now acceps the following syntax: RABBITMQ_FEATURE_FLAGS=+feature1,-feature2 This will start RabbitMQ with all stable feature flags, plus `feature1`, but without `feature2`. For users setting `forced_feature_flags_on_init` in the config, the corresponding syntax is: {forced_feature_flags_on_init, {rel, [feature1], [feature2]}}
1 parent 3540541 commit 2c2142e

File tree

2 files changed

+106
-54
lines changed

2 files changed

+106
-54
lines changed

deps/rabbit/src/rabbit_ff_controller.erl

Lines changed: 98 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -675,25 +675,14 @@ enable_task(FeatureNames) ->
675675
end.
676676

677677
enable_default_task() ->
678-
FeatureNames = get_forced_feature_flag_names(),
679-
case FeatureNames of
678+
case get_forced_feature_flag_names() of
680679
undefined ->
681680
?LOG_DEBUG(
682681
"Feature flags: starting an unclustered node for the first "
683682
"time: all stable feature flags will be enabled by default",
684683
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
685684
{ok, Inventory} = collect_inventory_on_nodes([node()]),
686-
#{feature_flags := FeatureFlags} = Inventory,
687-
StableFeatureNames =
688-
maps:fold(
689-
fun(FeatureName, FeatureProps, Acc) ->
690-
Stability = rabbit_feature_flags:get_stability(
691-
FeatureProps),
692-
case Stability of
693-
stable -> [FeatureName | Acc];
694-
_ -> Acc
695-
end
696-
end, [], FeatureFlags),
685+
StableFeatureNames = get_stable_feature_flags(Inventory),
697686
enable_many(Inventory, StableFeatureNames);
698687
[] ->
699688
?LOG_DEBUG(
@@ -702,21 +691,67 @@ enable_default_task() ->
702691
"the $RABBITMQ_FEATURE_FLAGS environment variable",
703692
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
704693
ok;
705-
_ ->
694+
FeatureNames when is_list(FeatureNames) ->
706695
?LOG_DEBUG(
707696
"Feature flags: starting an unclustered node for the first "
708697
"time: only the following feature flags specified in the "
709698
"$RABBITMQ_FEATURE_FLAGS environment variable will be enabled: "
710-
"~tp",
699+
"~0tp",
711700
[FeatureNames],
712701
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
713702
{ok, Inventory} = collect_inventory_on_nodes([node()]),
714-
enable_many(Inventory, FeatureNames)
703+
enable_many(Inventory, FeatureNames);
704+
{rel, Plus, Minus} ->
705+
?LOG_DEBUG(
706+
"Feature flags: starting an unclustered node for the first "
707+
"time: all stable feature flags will be enabled, after "
708+
"applying changes from $RABBITMQ_FEATURE_FLAGS: adding ~0tp, "
709+
"skipping ~0tp",
710+
[Plus, Minus],
711+
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
712+
{ok, Inventory} = collect_inventory_on_nodes([node()]),
713+
StableFeatureNames = get_stable_feature_flags(Inventory),
714+
Unsupported = lists:filter(
715+
fun(FeatureName) ->
716+
not is_known_and_supported(
717+
Inventory, FeatureName)
718+
end, Minus),
719+
case Unsupported of
720+
[] ->
721+
FeatureNames = (StableFeatureNames -- Minus) ++ Plus,
722+
enable_many(Inventory, FeatureNames);
723+
_ ->
724+
?LOG_ERROR(
725+
"Feature flags: unsupported feature flags to skip in "
726+
"$RABBITMQ_FEATURE_FLAGS: ~0tp",
727+
[Unsupported],
728+
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
729+
{error, unsupported}
730+
end;
731+
{error, syntax_error_in_envvar} = Error ->
732+
?LOG_DEBUG(
733+
"Feature flags: invalid mix of `feature_flag` and "
734+
"`+/-feature_flag` in $RABBITMQ_FEATURE_FLAGS",
735+
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
736+
Error
715737
end.
716738

739+
get_stable_feature_flags(#{feature_flags := FeatureFlags}) ->
740+
maps:fold(
741+
fun(FeatureName, FeatureProps, Acc) ->
742+
Stability = rabbit_feature_flags:get_stability(FeatureProps),
743+
case Stability of
744+
stable -> [FeatureName | Acc];
745+
_ -> Acc
746+
end
747+
end, [], FeatureFlags).
748+
717749
-spec get_forced_feature_flag_names() -> Ret when
718-
Ret :: FeatureNames | undefined,
719-
FeatureNames :: [rabbit_feature_flags:feature_name()].
750+
Ret :: Abs | Rel | undefined,
751+
Abs :: [rabbit_feature_flags:feature_name()],
752+
Rel :: {rel,
753+
[rabbit_feature_flags:feature_name()],
754+
[rabbit_feature_flags:feature_name()]}.
720755
%% @doc Returns the (possibly empty) list of feature flags the user wants to
721756
%% enable out-of-the-box when starting a node for the first time.
722757
%%
@@ -737,59 +772,74 @@ enable_default_task() ->
737772
%% @private
738773

739774
get_forced_feature_flag_names() ->
740-
Ret = case get_forced_feature_flag_names_from_env() of
741-
undefined -> get_forced_feature_flag_names_from_config();
742-
List -> List
743-
end,
744-
case Ret of
775+
Value = case get_forced_feature_flag_names_from_env() of
776+
undefined -> get_forced_feature_flag_names_from_config();
777+
List -> List
778+
end,
779+
case Value of
745780
undefined ->
746-
ok;
781+
Value;
747782
[] ->
748-
?LOG_INFO(
749-
"Feature flags: automatic enablement of feature flags "
750-
"disabled (i.e. none will be enabled automatically)",
751-
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS});
752-
_ ->
753-
?LOG_INFO(
754-
"Feature flags: automatic enablement of feature flags "
755-
"limited to the following list: ~tp",
756-
[Ret],
757-
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS})
758-
end,
759-
Ret.
783+
Value;
784+
[[Op | _] | _] when Op =:= $+ orelse Op =:= $- ->
785+
lists:foldr(
786+
fun
787+
(_, {error, _} = Error) ->
788+
Error;
789+
([$+ | NameS], {rel, Plus, Minus}) ->
790+
Name = list_to_atom(NameS),
791+
Plus1 = [Name | Plus],
792+
{rel, Plus1, Minus};
793+
([$- | NameS], {rel, Plus, Minus}) ->
794+
Name = list_to_atom(NameS),
795+
Minus1 = [Name | Minus],
796+
{rel, Plus, Minus1};
797+
(_, _) ->
798+
{error, syntax_error_in_envvar}
799+
end, {rel, [], []}, Value);
800+
_ when is_list(Value) ->
801+
lists:foldr(
802+
fun
803+
(_, {error, _} = Error) ->
804+
Error;
805+
(Name, Abs) when is_atom(Name) ->
806+
[Name | Abs];
807+
([C | _] = NameS, Abs) when C =/= $+ andalso C =/= $- ->
808+
Name = list_to_atom(NameS),
809+
[Name | Abs];
810+
(_, _) ->
811+
{error, syntax_error_in_envvar}
812+
end, [], Value)
813+
end.
760814

761815
-spec get_forced_feature_flag_names_from_env() -> Ret when
762816
Ret :: FeatureNames | undefined,
763-
FeatureNames :: [rabbit_feature_flags:feature_name()].
817+
FeatureNames :: [rabbit_feature_flags:feature_name() | string()].
764818
%% @private
765819

766820
get_forced_feature_flag_names_from_env() ->
767821
case rabbit_prelaunch:get_context() of
768822
#{forced_feature_flags_on_init := ForcedFFs}
769823
when is_list(ForcedFFs) ->
770824
ForcedFFs;
825+
#{forced_feature_flags_on_init := {'+', List} = ForcedFFs}
826+
when is_list(List) ->
827+
ForcedFFs;
771828
_ ->
772829
undefined
773830
end.
774831

775832
-spec get_forced_feature_flag_names_from_config() -> Ret when
776833
Ret :: FeatureNames | undefined,
777-
FeatureNames :: [rabbit_feature_flags:feature_name()].
834+
FeatureNames :: [rabbit_feature_flags:feature_name() | string()].
778835
%% @private
779836

780837
get_forced_feature_flag_names_from_config() ->
781838
Value = application:get_env(
782839
rabbit, forced_feature_flags_on_init, undefined),
783840
case Value of
784-
undefined ->
785-
Value;
786-
_ when is_list(Value) ->
787-
case lists:all(fun is_atom/1, Value) of
788-
true -> Value;
789-
false -> undefined
790-
end;
791-
_ ->
792-
undefined
841+
undefined -> Value;
842+
_ when is_list(Value) -> Value
793843
end.
794844

795845
-spec sync_cluster_task() -> Ret when
@@ -914,7 +964,7 @@ enable_if_supported(#{states_per_node := _} = Inventory, FeatureName) ->
914964
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
915965
enable_with_registry_locked(Inventory, FeatureName);
916966
false ->
917-
?LOG_DEBUG(
967+
?LOG_ERROR(
918968
"Feature flags: `~ts`: unsupported; aborting",
919969
[FeatureName],
920970
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),

deps/rabbit_common/src/rabbit_env.erl

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -999,13 +999,15 @@ forced_feature_flags_on_init(Context) ->
999999
case Value of
10001000
false ->
10011001
%% get_prefixed_env_var() considers an empty string
1002-
%% is the same as an undefined environment variable.
1003-
update_context(Context,
1004-
forced_feature_flags_on_init, undefined, default);
1002+
%% as an undefined environment variable.
1003+
update_context(
1004+
Context,
1005+
forced_feature_flags_on_init, undefined, default);
10051006
_ ->
1006-
Flags = [list_to_atom(V) || V <- string:lexemes(Value, ",")],
1007-
update_context(Context,
1008-
forced_feature_flags_on_init, Flags, environment)
1007+
FeatureNames = string:lexemes(Value, ","),
1008+
update_context(
1009+
Context,
1010+
forced_feature_flags_on_init, FeatureNames, environment)
10091011
end.
10101012

10111013
log_feature_flags_registry(Context) ->

0 commit comments

Comments
 (0)