3636-export ([is_supported /1 , is_supported /2 ,
3737 enable /1 ,
3838 enable_default /0 ,
39- check_node_compatibility /1 ,
39+ check_node_compatibility /2 ,
4040 sync_cluster /1 ,
4141 refresh_after_app_load /0 ,
4242 get_forced_feature_flag_names /0 ]).
@@ -134,20 +134,30 @@ enable_default() ->
134134 Ret
135135 end .
136136
137- check_node_compatibility (RemoteNode ) ->
137+ check_node_compatibility (RemoteNode , LocalNodeAsVirgin ) ->
138138 ThisNode = node (),
139- ? LOG_DEBUG (
140- " Feature flags: CHECKING COMPATIBILITY between nodes `~ts ` and `~ts `" ,
141- [ThisNode , RemoteNode ],
142- #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS }),
139+ case LocalNodeAsVirgin of
140+ true ->
141+ ? LOG_DEBUG (
142+ " Feature flags: CHECKING COMPATIBILITY between nodes `~ts ` "
143+ " and `~ts `; consider node `~ts ` as virgin" ,
144+ [ThisNode , RemoteNode , ThisNode ],
145+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS });
146+ false ->
147+ ? LOG_DEBUG (
148+ " Feature flags: CHECKING COMPATIBILITY between nodes `~ts ` "
149+ " and `~ts `" ,
150+ [ThisNode , RemoteNode ],
151+ #{domain => ? RMQLOG_DOMAIN_FEAT_FLAGS })
152+ end ,
143153 % % We don't go through the controller process to check nodes compatibility
144154 % % because this function is used while `rabbit' is stopped usually.
145155 % %
146156 % % There is no benefit in starting a controller just for this check
147157 % % because it would not guaranty that the compatibility remains true after
148158 % % this function finishes and before the node starts and synchronizes
149159 % % feature flags.
150- check_node_compatibility_task (ThisNode , RemoteNode ).
160+ check_node_compatibility_task (ThisNode , RemoteNode , LocalNodeAsVirgin ).
151161
152162sync_cluster (Nodes ) ->
153163 ? LOG_DEBUG (
@@ -382,12 +392,14 @@ notify_waiting_controller({ControlerPid, _} = From) ->
382392% % Code to check compatibility between nodes.
383393% % --------------------------------------------------------------------
384394
385- -spec check_node_compatibility_task (Node , Node ) -> Ret when
386- Node :: node (),
395+ -spec check_node_compatibility_task (NodeA , NodeB , NodeAAsVirigin ) -> Ret when
396+ NodeA :: node (),
397+ NodeB :: node (),
398+ NodeAAsVirigin :: boolean (),
387399 Ret :: ok | {error , Reason },
388400 Reason :: incompatible_feature_flags .
389401
390- check_node_compatibility_task (NodeA , NodeB ) ->
402+ check_node_compatibility_task (NodeA , NodeB , NodeAAsVirigin ) ->
391403 ? LOG_NOTICE (
392404 " Feature flags: checking nodes `~ts ` and `~ts ` compatibility..." ,
393405 [NodeA , NodeB ],
@@ -400,7 +412,8 @@ check_node_compatibility_task(NodeA, NodeB) ->
400412 _ when is_list (NodesB ) ->
401413 check_node_compatibility_task1 (
402414 NodeA , NodesA ,
403- NodeB , NodesB );
415+ NodeB , NodesB ,
416+ NodeAAsVirigin );
404417 Error ->
405418 ? LOG_WARNING (
406419 " Feature flags: "
@@ -419,10 +432,12 @@ check_node_compatibility_task(NodeA, NodeB) ->
419432 {error , {aborted_feature_flags_compat_check , Error }}
420433 end .
421434
422- check_node_compatibility_task1 (NodeA , NodesA , NodeB , NodesB )
435+ check_node_compatibility_task1 (NodeA , NodesA , NodeB , NodesB , NodeAAsVirigin )
423436 when is_list (NodesA ) andalso is_list (NodesB ) ->
424437 case collect_inventory_on_nodes (NodesA ) of
425- {ok , InventoryA } ->
438+ {ok , InventoryA0 } ->
439+ InventoryA = virtually_reset_inventory (
440+ InventoryA0 , NodeAAsVirigin ),
426441 ? LOG_DEBUG (
427442 " Feature flags: inventory of node `~ts `:~n~tp " ,
428443 [NodeA , InventoryA ],
@@ -488,6 +503,42 @@ list_nodes_clustered_with(Node) ->
488503 ListOrError -> ListOrError
489504 end .
490505
506+ virtually_reset_inventory (
507+ #{feature_flags := FeatureFlags ,
508+ states_per_node := StatesPerNode } = Inventory ,
509+ true = _NodeAsVirgin ) ->
510+ [Node | _ ] = maps :keys (StatesPerNode ),
511+ FeatureStates0 = maps :get (Node , StatesPerNode ),
512+ FeatureStates1 = maps :map (
513+ fun (FeatureName , _FeatureState ) ->
514+ FeatureProps = maps :get (
515+ FeatureName , FeatureFlags ),
516+ state_after_virtual_state (
517+ FeatureName , FeatureProps )
518+ end , FeatureStates0 ),
519+ StatesPerNode1 = maps :map (
520+ fun (_Node , _FeatureStates ) ->
521+ FeatureStates1
522+ end , StatesPerNode ),
523+ Inventory1 = Inventory #{states_per_node => StatesPerNode1 },
524+ Inventory1 ;
525+ virtually_reset_inventory (
526+ Inventory ,
527+ false = _NodeAsVirgin ) ->
528+ Inventory .
529+
530+ state_after_virtual_state (_FeatureName , FeatureProps )
531+ when ? IS_FEATURE_FLAG (FeatureProps ) ->
532+ Stability = rabbit_feature_flags :get_stability (FeatureProps ),
533+ case Stability of
534+ required -> true ;
535+ _ -> false
536+ end ;
537+ state_after_virtual_state (FeatureName , FeatureProps )
538+ when ? IS_DEPRECATION (FeatureProps ) ->
539+ not rabbit_deprecated_features :should_be_permitted (
540+ FeatureName , FeatureProps ).
541+
491542-spec are_compatible (Inventory , Inventory ) -> AreCompatible when
492543 Inventory :: rabbit_feature_flags :cluster_inventory (),
493544 AreCompatible :: boolean ().
0 commit comments