38
38
-export ([is_supported /1 , is_supported /2 ,
39
39
enable /1 ,
40
40
enable_default /0 ,
41
+ enable_required /0 ,
41
42
check_node_compatibility /2 ,
42
43
sync_cluster /1 ,
43
44
refresh_after_app_load /0 ,
@@ -136,6 +137,24 @@ enable_default() ->
136
137
Ret
137
138
end .
138
139
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
+
139
158
check_node_compatibility (RemoteNode , LocalNodeAsVirgin ) ->
140
159
ThisNode = node (),
141
160
case LocalNodeAsVirgin of
@@ -304,6 +323,8 @@ proceed_with_task({enable, FeatureNames}) ->
304
323
enable_task (FeatureNames );
305
324
proceed_with_task (enable_default ) ->
306
325
enable_default_task ();
326
+ proceed_with_task (enable_required ) ->
327
+ enable_required_task ();
307
328
proceed_with_task ({sync_cluster , Nodes }) ->
308
329
sync_cluster_task (Nodes );
309
330
proceed_with_task (refresh_after_app_load ) ->
@@ -841,6 +862,18 @@ get_forced_feature_flag_names_from_config() ->
841
862
_ when is_list (Value ) -> {ok , Value }
842
863
end .
843
864
865
+ - spec enable_required_task () -> Ret when
866
+ Ret :: ok | {error , Reason },
867
+ Reason :: term ().
868
+
869
+ enable_required_task () ->
870
+ ? LOG_DEBUG (
871
+ " Feature flags: enabling required feature flags on this node" ,
872
+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
873
+ {ok , Inventory } = collect_inventory_on_nodes ([node ()]),
874
+ RequiredFeatureNames = list_required_feature_flags (Inventory ),
875
+ enable_many (Inventory , RequiredFeatureNames ).
876
+
844
877
- spec sync_cluster_task () -> Ret when
845
878
Ret :: ok | {error , Reason },
846
879
Reason :: term ().
@@ -855,23 +888,6 @@ sync_cluster_task() ->
855
888
Reason :: term ().
856
889
857
890
sync_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
-
875
891
case collect_inventory_on_nodes (Nodes ) of
876
892
{ok , Inventory } ->
877
893
CantEnable = list_deprecated_features_that_cant_be_denied (
@@ -880,7 +896,17 @@ sync_cluster_task(Nodes) ->
880
896
[] ->
881
897
FeatureNames = list_feature_flags_enabled_somewhere (
882
898
Inventory , false ),
883
- enable_many (Inventory , FeatureNames );
899
+
900
+ % % In addition to feature flags enabled somewhere, we also
901
+ % % ensure required feature flags are enabled accross the
902
+ % % board.
903
+ RequiredFeatureNames = list_required_feature_flags (
904
+ Inventory ),
905
+
906
+ FeatureNamesToEnable = lists :usort (
907
+ FeatureNames ++
908
+ RequiredFeatureNames ),
909
+ enable_many (Inventory , FeatureNamesToEnable );
884
910
_ ->
885
911
? LOG_ERROR (
886
912
" Feature flags: the following deprecated features "
@@ -998,7 +1024,7 @@ enable_with_registry_locked(
998
1024
[FeatureName ],
999
1025
#{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
1000
1026
1001
- case check_required_and_enable (Inventory , FeatureName ) of
1027
+ case update_feature_state_and_enable (Inventory , FeatureName ) of
1002
1028
{ok , _Inventory } = Ok ->
1003
1029
? LOG_NOTICE (
1004
1030
" Feature flags: `~ts ` enabled" ,
@@ -1014,91 +1040,6 @@ enable_with_registry_locked(
1014
1040
end
1015
1041
end .
1016
1042
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
-
1102
1043
- spec update_feature_state_and_enable (Inventory , FeatureName ) -> Ret when
1103
1044
Inventory :: rabbit_feature_flags :cluster_inventory (),
1104
1045
FeatureName :: rabbit_feature_flags :feature_name (),
@@ -1445,6 +1386,19 @@ list_feature_flags_enabled_somewhere(
1445
1386
end , #{}, StatesPerNode ),
1446
1387
lists :sort (maps :keys (MergedStates )).
1447
1388
1389
+ list_required_feature_flags (#{feature_flags := FeatureFlags }) ->
1390
+ RequiredFeatureNames = maps :fold (
1391
+ fun (FeatureName , FeatureProps , Acc ) ->
1392
+ Stability = (
1393
+ rabbit_feature_flags :get_stability (
1394
+ FeatureProps )),
1395
+ case Stability of
1396
+ required -> [FeatureName | Acc ];
1397
+ _ -> Acc
1398
+ end
1399
+ end , [], FeatureFlags ),
1400
+ lists :sort (RequiredFeatureNames ).
1401
+
1448
1402
- spec list_deprecated_features_that_cant_be_denied (Inventory ) ->
1449
1403
Ret when
1450
1404
Inventory :: rabbit_feature_flags :cluster_inventory (),
0 commit comments