@@ -577,7 +577,7 @@ enable_with_registry_locked(
577577 [FeatureName ],
578578 #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
579579
580- case update_feature_state_and_enable (Inventory , FeatureName ) of
580+ case check_required_and_enable (Inventory , FeatureName ) of
581581 {ok , _Inventory } = Ok ->
582582 ? LOG_NOTICE (
583583 " Feature flags: `~ts ` enabled" ,
@@ -593,6 +593,71 @@ enable_with_registry_locked(
593593 end
594594 end .
595595
596+ -spec check_required_and_enable (Inventory , FeatureName ) -> Ret when
597+ Inventory :: rabbit_feature_flags :cluster_inventory (),
598+ FeatureName :: rabbit_feature_flags :feature_name (),
599+ Ret :: {ok , Inventory } | {error , Reason },
600+ Reason :: term ().
601+
602+ check_required_and_enable (
603+ #{feature_flags := FeatureFlags ,
604+ states_per_node := _ } = Inventory ,
605+ FeatureName ) ->
606+ % % Required feature flags vs. virgin nodes.
607+ FeatureProps = maps :get (FeatureName , FeatureFlags ),
608+ Stability = rabbit_feature_flags :get_stability (FeatureProps ),
609+ NodesWhereDisabled = list_nodes_where_feature_flag_is_disabled (
610+ Inventory , FeatureName ),
611+
612+ MarkDirectly = case Stability of
613+ required ->
614+ ? LOG_DEBUG (
615+ " Feature flags: `~s `: the feature flag is "
616+ " required on some nodes; list virgin nodes "
617+ " to determine if the feature flag can simply "
618+ " be marked as enabled" ,
619+ [FeatureName ],
620+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
621+ VirginNodesWhereDisabled =
622+ lists :filter (
623+ fun (Node ) ->
624+ case is_virgin_node (Node ) of
625+ IsVirgin when is_boolean (IsVirgin ) ->
626+ IsVirgin ;
627+ undefined ->
628+ false
629+ end
630+ end , NodesWhereDisabled ),
631+ VirginNodesWhereDisabled =:= NodesWhereDisabled ;
632+ _ ->
633+ false
634+ end ,
635+
636+ case MarkDirectly of
637+ false ->
638+ case Stability of
639+ required ->
640+ ? LOG_DEBUG (
641+ " Feature flags: `~s `: some nodes where the feature "
642+ " flag is disabled are not virgin, we need to perform "
643+ " a regular sync" ,
644+ [FeatureName ],
645+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS });
646+ _ ->
647+ ok
648+ end ,
649+ update_feature_state_and_enable (Inventory , FeatureName );
650+ true ->
651+ ? LOG_DEBUG (
652+ " Feature flags: `~s `: all nodes where the feature flag is "
653+ " disabled are virgin, we can directly mark it as enabled "
654+ " there" ,
655+ [FeatureName ],
656+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
657+ mark_as_enabled_on_nodes (
658+ NodesWhereDisabled , Inventory , FeatureName , true )
659+ end .
660+
596661-spec update_feature_state_and_enable (Inventory , FeatureName ) -> Ret when
597662 Inventory :: rabbit_feature_flags :cluster_inventory (),
598663 FeatureName :: rabbit_feature_flags :feature_name (),
@@ -646,6 +711,14 @@ update_feature_state_and_enable(
646711 Error
647712 end .
648713
714+ is_virgin_node (Node ) ->
715+ case rpc_call (Node , rabbit_mnesia , is_virgin_node , [], ? TIMEOUT ) of
716+ IsVirgin when is_boolean (IsVirgin ) ->
717+ IsVirgin ;
718+ {error , _ } ->
719+ undefined
720+ end .
721+
649722-spec do_enable (Inventory , FeatureName , Nodes ) -> Ret when
650723 Inventory :: rabbit_feature_flags :cluster_inventory (),
651724 FeatureName :: rabbit_feature_flags :feature_name (),
@@ -740,7 +813,7 @@ collect_inventory_on_nodes(Nodes, Timeout) ->
740813 {ok , #{feature_flags := FeatureFlags ,
741814 applications_per_node := ScannedAppsPerNode ,
742815 states_per_node := StatesPerNode } = Inventory }) ->
743- FeatureFlags2 = maps : merge (FeatureFlags , FeatureFlags1 ),
816+ FeatureFlags2 = merge_feature_flags (FeatureFlags , FeatureFlags1 ),
744817 ScannedAppsPerNode1 = ScannedAppsPerNode #{Node => ScannedApps },
745818 StatesPerNode1 = StatesPerNode #{Node => FeatureStates },
746819 Inventory1 = Inventory #{
@@ -756,6 +829,32 @@ collect_inventory_on_nodes(Nodes, Timeout) ->
756829 Error
757830 end , {ok , Inventory0 }, Rets ).
758831
832+ merge_feature_flags (FeatureFlagsA , FeatureFlagsB ) ->
833+ FeatureFlags = maps :merge (FeatureFlagsA , FeatureFlagsB ),
834+ maps :map (
835+ fun (FeatureName , FeatureProps ) ->
836+ FeaturePropsA = maps :get (FeatureName , FeatureFlagsA , #{}),
837+ FeaturePropsB = maps :get (FeatureName , FeatureFlagsB , #{}),
838+
839+ % % There is a rank between stability levels. If a feature flag
840+ % % is required somewhere, it is required globally. Otherwise if
841+ % % it is stable somewhere, it is stable globally.
842+ StabilityA = rabbit_feature_flags :get_stability (FeaturePropsA ),
843+ StabilityB = rabbit_feature_flags :get_stability (FeaturePropsB ),
844+ Stability = case {StabilityA , StabilityB } of
845+ {required , _ } -> required ;
846+ {_ , required } -> required ;
847+ {stable , _ } -> stable ;
848+ {_ , stable } -> stable ;
849+ _ -> experimental
850+ end ,
851+
852+ FeatureProps1 = FeatureProps #{stability => Stability },
853+ FeatureProps2 = maps :remove (migration_fun , FeatureProps1 ),
854+ FeatureProps3 = maps :remove (callbacks , FeatureProps2 ),
855+ FeatureProps3
856+ end , FeatureFlags ).
857+
759858- spec list_feature_flags_enabled_somewhere (Inventory , HandleStateChanging ) ->
760859 Ret when
761860 Inventory :: rabbit_feature_flags :cluster_inventory (),
0 commit comments