Skip to content

Commit 3691ab6

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` and `feature2`. For users setting `forced_feature_flags_on_init` in the config, the corresponding syntax is: {forced_feature_flags_on_init, {'+', [feature1, feature2]}}
1 parent 3540541 commit 3691ab6

File tree

2 files changed

+111
-54
lines changed

2 files changed

+111
-54
lines changed

deps/rabbit/src/rabbit_ff_controller.erl

Lines changed: 103 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -675,48 +675,84 @@ 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);
698-
[] ->
687+
{abs, []} ->
699688
?LOG_DEBUG(
700689
"Feature flags: starting an unclustered node for the first "
701690
"time: all feature flags are forcibly left disabled from "
702691
"the $RABBITMQ_FEATURE_FLAGS environment variable",
703692
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
704693
ok;
705-
_ ->
694+
{abs, 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_DEBUG(
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 :: {abs, AbsNames},
752+
AbsNames :: [rabbit_feature_flags:feature_name()],
753+
Rel :: {rel, PlusNames, MinusNames},
754+
PlusNames :: [rabbit_feature_flags:feature_name()],
755+
MinusNames :: [rabbit_feature_flags:feature_name()].
720756
%% @doc Returns the (possibly empty) list of feature flags the user wants to
721757
%% enable out-of-the-box when starting a node for the first time.
722758
%%
@@ -737,59 +773,78 @@ enable_default_task() ->
737773
%% @private
738774

739775
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
776+
Value = case get_forced_feature_flag_names_from_env() of
777+
undefined -> get_forced_feature_flag_names_from_config();
778+
List -> List
779+
end,
780+
case Value of
745781
undefined ->
746-
ok;
782+
Value;
747783
[] ->
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.
784+
{abs, Value};
785+
[[Op | _] | _] when Op =:= $+ orelse Op =:= $- ->
786+
lists:foldr(
787+
fun
788+
(_, {error, _} = Error) ->
789+
Error;
790+
([$+ | NameS], {rel, Plus, Minus}) ->
791+
Name = list_to_atom(NameS),
792+
Plus1 = [Name | Plus],
793+
{rel, Plus1, Minus};
794+
([$- | NameS], {rel, Plus, Minus}) ->
795+
Name = list_to_atom(NameS),
796+
Minus1 = [Name | Minus],
797+
{rel, Plus, Minus1};
798+
(_, _) ->
799+
{error, syntax_error_in_envvar}
800+
end, {rel, [], []}, Value);
801+
_ when is_list(Value) ->
802+
lists:foldr(
803+
fun
804+
(_, {error, _} = Error) ->
805+
Error;
806+
(Name, {abs, Abs})
807+
when is_atom(Name) ->
808+
Abs1 = [Name | Abs],
809+
{abs, Abs1};
810+
([C | _] = NameS, {abs, Abs})
811+
when C =/= $+ andalso C =/= $- ->
812+
Name = list_to_atom(NameS),
813+
Abs1 = [Name | Abs],
814+
{abs, Abs1};
815+
(_, _) ->
816+
{error, syntax_error_in_envvar}
817+
end, {abs, []}, Value)
818+
end.
760819

761820
-spec get_forced_feature_flag_names_from_env() -> Ret when
762821
Ret :: FeatureNames | undefined,
763-
FeatureNames :: [rabbit_feature_flags:feature_name()].
822+
FeatureNames :: [rabbit_feature_flags:feature_name() | string()].
764823
%% @private
765824

766825
get_forced_feature_flag_names_from_env() ->
767826
case rabbit_prelaunch:get_context() of
768827
#{forced_feature_flags_on_init := ForcedFFs}
769828
when is_list(ForcedFFs) ->
770829
ForcedFFs;
830+
#{forced_feature_flags_on_init := {'+', List} = ForcedFFs}
831+
when is_list(List) ->
832+
ForcedFFs;
771833
_ ->
772834
undefined
773835
end.
774836

775837
-spec get_forced_feature_flag_names_from_config() -> Ret when
776838
Ret :: FeatureNames | undefined,
777-
FeatureNames :: [rabbit_feature_flags:feature_name()].
839+
FeatureNames :: [rabbit_feature_flags:feature_name() | string()].
778840
%% @private
779841

780842
get_forced_feature_flag_names_from_config() ->
781843
Value = application:get_env(
782844
rabbit, forced_feature_flags_on_init, undefined),
783845
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
846+
undefined -> Value;
847+
_ when is_list(Value) -> Value
793848
end.
794849

795850
-spec sync_cluster_task() -> Ret when

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)