3232import org .apache .kafka .common .utils .LogContext ;
3333import org .apache .kafka .metadata .KafkaConfigSchema ;
3434import org .apache .kafka .server .common .ApiMessageAndVersion ;
35+ import org .apache .kafka .server .common .ConfigFeatureGate ;
3536import org .apache .kafka .server .common .EligibleLeaderReplicasVersion ;
3637import org .apache .kafka .server .common .MetadataVersion ;
3738import org .apache .kafka .server .mutable .BoundedList ;
5354import java .util .Objects ;
5455import java .util .Optional ;
5556import java .util .function .Consumer ;
57+ import java .util .stream .Collectors ;
5658
5759import static org .apache .kafka .clients .admin .AlterConfigOp .OpType .APPEND ;
5860import static org .apache .kafka .common .config .ConfigResource .Type .BROKER ;
@@ -173,8 +175,7 @@ private ConfigurationControlManager(LogContext logContext,
173175 Map <String , Object > staticConfig ,
174176 int nodeId ,
175177 FeatureControlManager featureControl ,
176- ClusterControlManager clusterControl
177- ) {
178+ ClusterControlManager clusterControl ) {
178179 this .log = logContext .logger (ConfigurationControlManager .class );
179180 this .snapshotRegistry = snapshotRegistry ;
180181 this .configSchema = configSchema ;
@@ -692,9 +693,9 @@ Optional<ApiError> validateConfigsForFeatureUpgrade(Map<String, Short> updates)
692693 }
693694
694695 /**
695- * Validate that all existing configurations pass their ConfigDef validators before a
696- * metadata version upgrade. This catches config values that were accepted under a previous
697- * version but would be invalid under the new version's stricter constraints .
696+ * Validate that all existing configurations are compatible with a proposed metadata version
697+ * upgrade. Uses MV-specific constraints when available, falling back to ConfigDef validators
698+ * only when crossing the initial threshold (< IBP_4_0_IV0 to >= IBP_4_0_IV0) .
698699 *
699700 * @param proposedLevel The proposed metadata version feature level.
700701 * @return An error if any config value is invalid, or empty if all are valid.
@@ -704,39 +705,41 @@ private Optional<ApiError> validateMetadataVersionUpgradeConstraints(short propo
704705 return Optional .empty ();
705706 }
706707
708+ MetadataVersion proposedMV = MetadataVersion .fromFeatureLevel (proposedLevel );
709+
707710 Map <ConfigResource , String > violations = new HashMap <>();
708- for (Entry <ConfigResource , TimelineHashMap <String , String >> entry : configData .entrySet ()) {
709- ConfigResource resource = entry .getKey ();
710- Map <String , String > configs = entry .getValue ();
711- for (Entry <String , String > configEntry : configs .entrySet ()) {
712- try {
713- configSchema .validateValue (resource .type (), configEntry .getKey (), configEntry .getValue ());
714- } catch (ConfigException e ) {
715- violations .put (resource , e .getMessage ());
716- }
717- }
718- }
719- // Validate broker static configs
711+
712+ // Validate broker configs
720713 if (clusterControl != null ) {
721- Map <Integer , Map <String , String >> brokerStatics = clusterControl .brokerStaticConfigs ();
722- for (Entry <Integer , Map <String , String >> brokerEntry : brokerStatics .entrySet ()) {
723- int brokerId = brokerEntry .getKey ();
724- for (Entry <String , String > configEntry : brokerEntry .getValue ().entrySet ()) {
714+ for (Entry <ConfigResource , Map <String , String >> brokerEntry : clusterControl .brokerStaticConfigs ().entrySet ()) {
715+ int brokerId = Integer .parseInt (brokerEntry .getKey ().name ());
716+ ConfigResource brokerResource = brokerEntry .getKey ();
717+
718+ List <String > brokerViolations = new ArrayList <>();
719+
720+ for (Entry <String , ConfigEntry > configEntry : computeEffectiveBrokerConfigs (brokerId ).entrySet ()) {
725721 try {
726- configSchema . validateValue ( ConfigResource . Type . BROKER , configEntry .getKey (), configEntry .getValue ());
727- } catch (ConfigException e ) {
728- violations . put ( new ConfigResource ( ConfigResource . Type . BROKER , String . valueOf ( brokerId )), e .getMessage ());
722+ ConfigFeatureGate . validate ( proposedMV , configEntry .getKey (), configEntry .getValue (). value ());
723+ } catch (Exception e ) {
724+ brokerViolations . add ( e .getMessage ());
729725 }
730726 }
727+ if (!brokerViolations .isEmpty ()) {
728+ violations .put (brokerResource , String .join (", " , brokerViolations ));
729+ }
731730 }
732731 }
733732
734733 if (!violations .isEmpty ()) {
735- return Optional .of (new ApiError (INVALID_CONFIG ,
736- "Cannot upgrade " + MetadataVersion .FEATURE_NAME +
737- " to version " + proposedLevel +
738- " because existing configs are invalid: " + violations +
739- ". Fix these configs before upgrading." ));
734+ String errorMessage = violations .entrySet ().stream ()
735+ .map (entry -> "NodeId=" + entry .getKey ().name () + " -> " + entry .getValue ())
736+ .collect (Collectors .joining ("; " ));
737+
738+ return Optional .of (new ApiError (Errors .INVALID_CONFIG ,
739+ "Cannot upgrade " + MetadataVersion .FEATURE_NAME +
740+ " to version " + proposedLevel +
741+ " because existing configs are invalid: " + errorMessage +
742+ ". Fix these configs before upgrading." ));
740743 }
741744 return Optional .empty ();
742745 }
@@ -797,11 +800,25 @@ Map<String, ConfigEntry> computeEffectiveTopicConfigs(Map<String, String> creati
797800 currentControllerConfig (), creationConfigs );
798801 }
799802
803+ Map <String , ConfigEntry > computeEffectiveBrokerConfigs (int brokerId ) {
804+ return configSchema .resolveEffectiveBrokerConfigs (brokerStaticConfig (brokerId ), clusterConfig (), brokerDynamicConfig (brokerId ));
805+ }
806+
800807 Map <String , String > clusterConfig () {
801808 Map <String , String > result = configData .get (DEFAULT_NODE );
802809 return (result == null ) ? Map .of () : result ;
803810 }
804811
812+ Map <String , String > brokerStaticConfig (int brokerId ) {
813+ Map <String , String > result = clusterControl .brokerStaticConfigs ().get (new ConfigResource (BROKER , String .valueOf (brokerId )));
814+ return (result == null ) ? Map .of () : result ;
815+ }
816+
817+ Map <String , String > brokerDynamicConfig (int brokerId ) {
818+ Map <String , String > result = configData .get (new ConfigResource (BROKER , String .valueOf (brokerId )));
819+ return (result == null ) ? Map .of () : result ;
820+ }
821+
805822 Map <String , String > currentControllerConfig () {
806823 Map <String , String > result = configData .get (currentController );
807824 return (result == null ) ? Map .of () : result ;
0 commit comments