3838-export ([is_supported /1 , is_supported /2 ,
3939 enable /1 ,
4040 enable_default /0 ,
41+ enable_required /0 ,
4142 check_node_compatibility /2 ,
4243 sync_cluster /1 ,
4344 refresh_after_app_load /0 ,
@@ -136,6 +137,24 @@ enable_default() ->
136137 Ret
137138 end .
138139
140+ enable_required () ->
141+ ? LOG_DEBUG (
142+ " Feature flags: enable required feature flags" ,
143+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
144+ case erlang :whereis (? LOCAL_NAME ) of
145+ Pid when is_pid (Pid ) ->
146+ % % The function is called while `rabbit' is running.
147+ gen_statem :call (? LOCAL_NAME , enable_required );
148+ undefined ->
149+ % % The function is called while `rabbit' is stopped. We need to
150+ % % start a one-off controller, again to make sure concurrent
151+ % % changes are blocked.
152+ {ok , Pid } = start_link (),
153+ Ret = gen_statem :call (Pid , enable_required ),
154+ gen_statem :stop (Pid ),
155+ Ret
156+ end .
157+
139158check_node_compatibility (RemoteNode , LocalNodeAsVirgin ) ->
140159 ThisNode = node (),
141160 case LocalNodeAsVirgin of
@@ -304,6 +323,8 @@ proceed_with_task({enable, FeatureNames}) ->
304323 enable_task (FeatureNames );
305324proceed_with_task (enable_default ) ->
306325 enable_default_task ();
326+ proceed_with_task (enable_required ) ->
327+ enable_required_task ();
307328proceed_with_task ({sync_cluster , Nodes }) ->
308329 sync_cluster_task (Nodes );
309330proceed_with_task (refresh_after_app_load ) ->
@@ -841,6 +862,24 @@ get_forced_feature_flag_names_from_config() ->
841862 _ when is_list (Value ) -> {ok , Value }
842863 end .
843864
865+ - spec enable_required_task () -> Ret when
866+ Ret :: ok | {error , Reason },
867+ Reason :: term ().
868+
869+ enable_required_task () ->
870+ {ok , Inventory } = collect_inventory_on_nodes ([node ()]),
871+ RequiredFeatureNames = list_required_feature_flags (Inventory ),
872+ case RequiredFeatureNames of
873+ [] ->
874+ ok ;
875+ _ ->
876+ ? LOG_DEBUG (
877+ " Feature flags: enabling required feature flags: ~0p " ,
878+ [RequiredFeatureNames ],
879+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS })
880+ end ,
881+ enable_many (Inventory , RequiredFeatureNames ).
882+
844883- spec sync_cluster_task () -> Ret when
845884 Ret :: ok | {error , Reason },
846885 Reason :: term ().
@@ -855,23 +894,6 @@ sync_cluster_task() ->
855894 Reason :: term ().
856895
857896sync_cluster_task (Nodes ) ->
858- % % We assume that a feature flag can only be enabled, not disabled.
859- % % Therefore this synchronization searches for feature flags enabled on
860- % % some nodes but not all, and make sure they are enabled everywhere.
861- % %
862- % % This happens when a node joins a cluster and that node has a different
863- % % set of enabled feature flags.
864- % %
865- % % FIXME: `enable_task()' requires that all nodes in the cluster run to
866- % % enable anything. Should we require the same here? On one hand, this
867- % % would make sure a feature flag isn't enabled while there is a network
868- % % partition. On the other hand, this would require that all nodes are
869- % % running before we can expand the cluster...
870- ? LOG_DEBUG (
871- " Feature flags: synchronizing feature flags on nodes: ~tp " ,
872- [Nodes ],
873- #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
874-
875897 case collect_inventory_on_nodes (Nodes ) of
876898 {ok , Inventory } ->
877899 CantEnable = list_deprecated_features_that_cant_be_denied (
@@ -880,7 +902,27 @@ sync_cluster_task(Nodes) ->
880902 [] ->
881903 FeatureNames = list_feature_flags_enabled_somewhere (
882904 Inventory , false ),
883- enable_many (Inventory , FeatureNames );
905+
906+ % % In addition to feature flags enabled somewhere, we also
907+ % % ensure required feature flags are enabled accross the
908+ % % board.
909+ RequiredFeatureNames = list_required_feature_flags (
910+ Inventory ),
911+ case RequiredFeatureNames of
912+ [] ->
913+ ok ;
914+ _ ->
915+ ? LOG_DEBUG (
916+ " Feature flags: enabling required feature "
917+ " flags as part of cluster sync: ~0p " ,
918+ [RequiredFeatureNames ],
919+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS })
920+ end ,
921+
922+ FeatureNamesToEnable = lists :usort (
923+ FeatureNames ++
924+ RequiredFeatureNames ),
925+ enable_many (Inventory , FeatureNamesToEnable );
884926 _ ->
885927 ? LOG_ERROR (
886928 " Feature flags: the following deprecated features "
@@ -998,7 +1040,7 @@ enable_with_registry_locked(
9981040 [FeatureName ],
9991041 #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
10001042
1001- case check_required_and_enable (Inventory , FeatureName ) of
1043+ case update_feature_state_and_enable (Inventory , FeatureName ) of
10021044 {ok , _Inventory } = Ok ->
10031045 ? LOG_NOTICE (
10041046 " Feature flags: `~ts ` enabled" ,
@@ -1014,91 +1056,6 @@ enable_with_registry_locked(
10141056 end
10151057 end .
10161058
1017- - spec check_required_and_enable (Inventory , FeatureName ) -> Ret when
1018- Inventory :: rabbit_feature_flags :cluster_inventory (),
1019- FeatureName :: rabbit_feature_flags :feature_name (),
1020- Ret :: {ok , Inventory } | {error , Reason },
1021- Reason :: term ().
1022-
1023- check_required_and_enable (
1024- #{feature_flags := FeatureFlags ,
1025- states_per_node := _ } = Inventory ,
1026- FeatureName ) ->
1027- % % Required feature flags vs. virgin nodes.
1028- FeatureProps = maps :get (FeatureName , FeatureFlags ),
1029- Stability = rabbit_feature_flags :get_stability (FeatureProps ),
1030- ProvidedBy = maps :get (provided_by , FeatureProps ),
1031- NodesWhereDisabled = list_nodes_where_feature_flag_is_disabled (
1032- Inventory , FeatureName ),
1033-
1034- MarkDirectly = case Stability of
1035- required when ProvidedBy =:= rabbit ->
1036- ? LOG_DEBUG (
1037- " Feature flags: `~s `: the feature flag is "
1038- " required on some nodes; list virgin nodes "
1039- " to determine if the feature flag can simply "
1040- " be marked as enabled" ,
1041- [FeatureName ],
1042- #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
1043- VirginNodesWhereDisabled =
1044- lists :filter (
1045- fun (Node ) ->
1046- case rabbit_db :is_virgin_node (Node ) of
1047- IsVirgin when is_boolean (IsVirgin ) ->
1048- IsVirgin ;
1049- undefined ->
1050- false
1051- end
1052- end , NodesWhereDisabled ),
1053- VirginNodesWhereDisabled =:= NodesWhereDisabled ;
1054- required when ProvidedBy =/= rabbit ->
1055- % % A plugin can be enabled/disabled at runtime and
1056- % % between restarts. Thus we have no way to
1057- % % distinguish a newly enabled plugin from a plugin
1058- % % which was enabled in the past.
1059- % %
1060- % % Therefore, we always mark required feature flags
1061- % % from plugins directly as enabled. However, the
1062- % % plugin is responsible for checking that its
1063- % % possibly existing data is as it expects it or
1064- % % perform any cleanup/conversion!
1065- ? LOG_DEBUG (
1066- " Feature flags: `~s `: the feature flag is "
1067- " required on some nodes; it comes from a "
1068- " plugin which can be enabled at runtime, "
1069- " so it can be marked as enabled" ,
1070- [FeatureName ],
1071- #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
1072- true ;
1073- _ ->
1074- false
1075- end ,
1076-
1077- case MarkDirectly of
1078- false ->
1079- case Stability of
1080- required ->
1081- ? LOG_DEBUG (
1082- " Feature flags: `~s `: some nodes where the feature "
1083- " flag is disabled are not virgin, we need to perform "
1084- " a regular sync" ,
1085- [FeatureName ],
1086- #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS });
1087- _ ->
1088- ok
1089- end ,
1090- update_feature_state_and_enable (Inventory , FeatureName );
1091- true ->
1092- ? LOG_DEBUG (
1093- " Feature flags: `~s `: all nodes where the feature flag is "
1094- " disabled are virgin, we can directly mark it as enabled "
1095- " there" ,
1096- [FeatureName ],
1097- #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
1098- mark_as_enabled_on_nodes (
1099- NodesWhereDisabled , Inventory , FeatureName , true )
1100- end .
1101-
11021059- spec update_feature_state_and_enable (Inventory , FeatureName ) -> Ret when
11031060 Inventory :: rabbit_feature_flags :cluster_inventory (),
11041061 FeatureName :: rabbit_feature_flags :feature_name (),
@@ -1445,6 +1402,26 @@ list_feature_flags_enabled_somewhere(
14451402 end , #{}, StatesPerNode ),
14461403 lists :sort (maps :keys (MergedStates )).
14471404
1405+ list_required_feature_flags (
1406+ #{feature_flags := FeatureFlags , states_per_node := StatesPerNode }) ->
1407+ FeatureStates = maps :get (node (), StatesPerNode ),
1408+ RequiredFeatureNames = maps :fold (
1409+ fun (FeatureName , FeatureProps , Acc ) ->
1410+ Stability = (
1411+ rabbit_feature_flags :get_stability (
1412+ FeatureProps )),
1413+ IsEnabled = maps :get (
1414+ FeatureName , FeatureStates ,
1415+ false ),
1416+ case Stability of
1417+ required when IsEnabled =:= false ->
1418+ [FeatureName | Acc ];
1419+ _ ->
1420+ Acc
1421+ end
1422+ end , [], FeatureFlags ),
1423+ lists :sort (RequiredFeatureNames ).
1424+
14481425- spec list_deprecated_features_that_cant_be_denied (Inventory ) ->
14491426 Ret when
14501427 Inventory :: rabbit_feature_flags :cluster_inventory (),
0 commit comments