8000 fix(inkless:controller): skip unchanged control plane connection string record by jeqo · Pull Request #852 · aiven/inkless · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package kafka.server
import java.{lang, util}
import java.util.{Optional, Properties, Map => JMap}
import java.util.concurrent.{CompletionStage, TimeUnit}
import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.atomic.{AtomicInteger, AtomicReference}
import kafka.log.LogManager
import kafka.network.{DataPlaneAcceptor, SocketServer}
import kafka.utils.TestUtils
Expand Down Expand Up @@ -1345,6 +1345,28 @@ class DynamicBrokerConfigTest {
"the connection string must be updatable cluster-wide")
config.dynamicConfig.validate(update, perBrokerConfig = false)
}

@Test
def testUnchangedInklessConnectionStringDoesNotReconfigure(): Unit = {
val props = TestUtils.createBrokerConfig(0, port = 8181)
val config = KafkaConfig.fromProps(props)
config.dynamicConfig.initialize(None)
val reconfigurations = new AtomicInteger()
config.dynamicConfig.addBrokerReconfigurable(new BrokerReconfigurable {
override def reconfigurableConfigs: util.Set[String] = DynamicInklessControlPlaneConfig.ReconfigurableConfigs
override def validateReconfiguration(newConfig: KafkaConfig): Unit = {}
override def reconfigure(oldConfig: KafkaConfig, newConfig: KafkaConfig): Unit = reconfigurations.incrementAndGet()
})

val update = new Properties()
update.put("inkless.control.plane.connection.string", "")
config.dynamicConfig.updateDefaultConfig(update)
assertEquals(1, reconfigurations.get())

config.dynamicConfig.updateDefaultConfig(update)
assertEquals(1, reconfigurations.get(),
"re-applying an unchanged value must not reconfigure, so the controller can skip the ConfigRecord")
}
}

class TestDynamicThreadPool extends BrokerReconfigurable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.apache.kafka.server.common.ApiMessageAndVersion;
import org.apache.kafka.server.common.EligibleLeaderReplicasVersion;
import org.apache.kafka.server.common.MetadataVersion;
import org.apache.kafka.server.config.InklessControlPlaneConfigs;
import org.apache.kafka.server.config.ServerConfigs;
import org.apache.kafka.server.mutable.BoundedList;
import org.apache.kafka.server.policy.AlterConfigPolicy;
Expand Down Expand Up @@ -267,6 +268,14 @@ ControllerResult<ApiError> incrementalAlterConfig(
return ControllerResult.atomicOf(outputRecords, apiError);
}

/**
* Returns true if re-applying an unchanged value of this broker config needs no reload, so the
* KAFKA-14136 record can be skipped. Only the Inkless control plane connection string qualifies.
*/
private static boolean isReloadIdempotent(String key) {
return InklessControlPlaneConfigs.CONNECTION_STRING_CONFIG.equals(key);
}

private ApiError incrementalAlterConfigResource(
ConfigResource configResource,
Map<String, Entry<OpType, String>> keysToOps,
Expand Down Expand Up @@ -324,7 +333,8 @@ private ApiError incrementalAlterConfigResource(
newValue = String.join(",", oldValueList);
break;
}
if (!Objects.equals(currentValue, newValue) || configResource.type().equals(Type.BROKER)) {
boolean changed = !Objects.equals(currentValue, newValue);
if (changed || (configResource.type().equals(Type.BROKER) && !isReloadIdempotent(key))) {
// KAFKA-14136 We need to generate records even if the value is unchanged to trigger reloads on the brokers
newRecords.add(new ApiMessageAndVersion(new ConfigRecord().
setResourceType(configResource.type().id()).
Expand Down Expand Up @@ -575,7 +585,8 @@ private void legacyAlterConfigResource(ConfigResource configResource,
String key = entry.getKey();
String newValue = entry.getValue();
String currentValue = currentConfigs.get(key);
if (!Objects.equals(currentValue, newValue) || configResource.type().equals(Type.BROKER)) {
boolean changed = !Objects.equals(currentValue, newValue);
if (changed || (configResource.type().equals(Type.BROKER) && !isReloadIdempotent(key))) {
// KAFKA-14136 We need to generate records even if the value is unchanged to trigger reloads on the brokers
recordsExplicitlyAltered.add(new ApiMessageAndVersion(new ConfigRecord().
setResourceType(configResource.type().id()).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.apache.kafka.server.common.EligibleLeaderReplicasVersion;
import org.apache.kafka.server.common.MetadataVersion;
import org.apache.kafka.server.config.ConfigSynonym;
import org.apache.kafka.server.config.InklessControlPlaneConfigs;
import org.apache.kafka.server.config.ServerLogConfigs;
import org.apache.kafka.server.policy.AlterConfigPolicy;
import org.apache.kafka.server.policy.AlterConfigPolicy.RequestMetadata;
Expand Down Expand Up @@ -579,6 +580,103 @@ public void testCordonedLogDirsFeature(boolean enabled) {
assertEquals(enabled ? ApiError.NONE : DISALLOWED_CORDONED_LOG_DIRS_ERROR, result.response());
}

@Test
public void testIncrementalAlterConfigSkipsRecordForUnchangedControlPlaneConnectionString() {
ConfigurationControlManager manager = new ConfigurationControlManager.Builder().
setFeatureControl(createFeatureControlManager()).
setKafkaConfigSchema(SCHEMA).
build();
String key = InklessControlPlaneConfigs.CONNECTION_STRING_CONFIG;
String value = "jdbc:postgresql://pg:5432/defaultdb?sslmode=require";
Map<String, Entry<AlterConfigOp.OpType, String>> keyToOps = toMap(entry(key, entry(SET, value)));

ControllerResult<ApiError> first = manager.incrementalAlterConfig(
ConfigurationControlManager.DEFAULT_NODE, keyToOps, false);
assertEquals(List.of(new ConfigRecord().setResourceType(BROKER.id()).setResourceName("").
setName(key).setValue(value)),
first.records().stream().map(ApiMessageAndVersion::message).toList());
RecordTestUtils.replayAll(manager, first.records());

ControllerResult<ApiError> second = manager.incrementalAlterConfig(
ConfigurationControlManager.DEFAULT_NODE, keyToOps, false);
assertEquals(ApiError.NONE, second.response());
assertEquals(List.of(), second.records());
assertEquals(Map.of(key, value), manager.getConfigs(ConfigurationControlManager.DEFAULT_NODE));

ControllerResult<ApiError> changed = manager.incrementalAlterConfig(
ConfigurationControlManager.DEFAULT_NODE, toMap(entry(key, entry(SET, ""))), false);
assertEquals(1, changed.records().size());
}

@Test
public void testIncrementalAlterConfigStillEmitsRecordForUnchangedOtherBrokerValue() {
ConfigurationControlManager manager = new ConfigurationControlManager.Builder().
setFeatureControl(createFeatureControlManager()).
setKafkaConfigSchema(SCHEMA).
build();
for (String key : List.of("abc", InklessControlPlaneConfigs.READ_CONNECTION_STRING_CONFIG)) {
Map<String, Entry<AlterConfigOp.OpType, String>> keyToOps = toMap(entry(key, entry(SET, "x")));
ControllerResult<ApiError> first = manager.incrementalAlterConfig(
ConfigurationControlManager.DEFAULT_NODE, keyToOps, false);
RecordTestUtils.replayAll(manager, first.records());

ControllerResult<ApiError> second = manager.incrementalAlterConfig(
ConfigurationControlManager.DEFAULT_NODE, keyToOps, false);
assertEquals(ApiError.NONE, second.response());
assertEquals(1, second.records().size(), "KAFKA-14136: unchanged " + key);
}
}

@Test
public void testLegacyAlterConfigsSkipsRecordForUnchangedControlPlaneConnectionString() {
ConfigurationControlManager manager = new ConfigurationControlManager.Builder().
setFeatureControl(createFeatureControlManager()).
setKafkaConfigSchema(SCHEMA).
build();
ConfigResource cluster = ConfigurationControlManager.DEFAULT_NODE;
String key = InklessControlPlaneConfigs.CONNECTION_STRING_CONFIG;
String value = "jdbc:postgresql://pg:5432/defaultdb?sslmode=require";
Map<ConfigResource, Map<String, String>> configs = toMap(entry(cluster, toMap(
entry(key, value), entry("abc", "x"))));

ControllerResult<Map<ConfigResource, ApiError>> first = manager.legacyAlterConfigs(configs, false);
assertEquals(2, first.records().size());
RecordTestUtils.replayAll(manager, first.records());

ControllerResult<Map<ConfigResource, ApiError>> second = manager.legacyAlterConfigs(configs, false);
assertEquals(Map.of(cluster, ApiError.NONE), second.response());
assertEquals(List.of(new ConfigRecord().setResourceType(BROKER.id()).setResourceName("").
setName("abc").setValue("x")),
second.records().stream().map(ApiMessageAndVersion::message).toList(),
"KAFKA-14136: only the unchanged non-connection-string config still emits");

ControllerResult<Map<ConfigResource, ApiError>> omitted = manager.legacyAlterConfigs(
toMap(entry(cluster, toMap(entry("abc", "x")))), false);
assertEquals(List.of(
new ConfigRecord().setResourceType(BROKER.id()).setResourceName("").
setName("abc").setValue("x"),
new ConfigRecord().setResourceType(BROKER.id()).setResourceName("").
setName(key).setValue(null)),
omitted.records().stream().map(ApiMessageAndVersion::message).toList());
}

@Test
public void testIncrementalAlterConfigSkipsRecordForUnchangedTopicValue() {
ConfigurationControlManager manager = new ConfigurationControlManager.Builder().
setFeatureControl(createFeatureControlManager()).
setKafkaConfigSchema(SCHEMA).
build();
Map<String, Entry<AlterConfigOp.OpType, String>> keyToOps = toMap(entry("def", entry(SET, "x")));

ControllerResult<ApiError> first = manager.incrementalAlterConfig(MYTOPIC, keyToOps, true);
assertEquals(1, first.records().size());
RecordTestUtils.replayAll(manager, first.records());

ControllerResult<ApiError> second = manager.incrementalAlterConfig(MYTOPIC, keyToOps, false);
assertEquals(ApiError.NONE, second.response());
assertEquals(List.of(), second.records());
}

private FeatureControlManager createFeatureControlManager() {
FeatureControlManager featureControlManager = new FeatureControlManager.Builder().build();
featureControlManager.replay(new FeatureLevelRecord().
Expand Down
Loading
0