KAFKA-14136 Generate ConfigRecord for brokers even if the value is unchanged (#12483)

This commit is contained in:
David Arthur 2022-08-04 15:09:08 -04:00
parent 4e049c706f
commit a7369bd52f
2 changed files with 43 additions and 22 deletions

View File

@ -67,6 +67,7 @@ import org.junit.jupiter.api.{AfterEach, BeforeEach, Disabled, Test, TestInfo}
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource import org.junit.jupiter.params.provider.ValueSource
import java.util.concurrent.atomic.AtomicInteger
import scala.annotation.nowarn import scala.annotation.nowarn
import scala.collection._ import scala.collection._
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
@ -352,8 +353,9 @@ class DynamicBrokerReconfigurationTest extends QuorumTestHarness with SaslSetup
} }
} }
@Test // TODO KAFKA-14126 add KRaft support @ParameterizedTest(name = TestInfoUtils.TestWithParameterizedQuorumName)
def testKeyStoreAlter(): Unit = { @ValueSource(strings = Array("zk", "kraft"))
def testKeyStoreAlter(quorum: String): Unit = {
val topic2 = "testtopic2" val topic2 = "testtopic2"
TestUtils.createTopicWithAdmin(adminClients.head, topic2, servers, numPartitions, replicationFactor = numServers) TestUtils.createTopicWithAdmin(adminClients.head, topic2, servers, numPartitions, replicationFactor = numServers)
@ -419,8 +421,9 @@ class DynamicBrokerReconfigurationTest extends QuorumTestHarness with SaslSetup
stopAndVerifyProduceConsume(producerThread, consumerThread) stopAndVerifyProduceConsume(producerThread, consumerThread)
} }
@Test // TODO KAFKA-14126 add KRaft support @ParameterizedTest(name = TestInfoUtils.TestWithParameterizedQuorumName)
def testTrustStoreAlter(): Unit = { @ValueSource(strings = Array("zk", "kraft"))
def testTrustStoreAlter(quorum: String): Unit = {
val producerBuilder = ProducerBuilder().listenerName(SecureInternal).securityProtocol(SecurityProtocol.SSL) val producerBuilder = ProducerBuilder().listenerName(SecureInternal).securityProtocol(SecurityProtocol.SSL)
// Producer with new keystore should fail to connect before truststore update // Producer with new keystore should fail to connect before truststore update
@ -467,9 +470,12 @@ class DynamicBrokerReconfigurationTest extends QuorumTestHarness with SaslSetup
assertFalse(response.wasDisconnected(), "Request failed because broker is not available") assertFalse(response.wasDisconnected(), "Request failed because broker is not available")
} }
val group_id = new AtomicInteger(1)
def next_group_name(): String = s"alter-truststore-${group_id.getAndIncrement()}"
// Produce/consume should work with old as well as new client keystore // Produce/consume should work with old as well as new client keystore
verifySslProduceConsume(sslProperties1, "alter-truststore-1") verifySslProduceConsume(sslProperties1, next_group_name())
verifySslProduceConsume(sslProperties2, "alter-truststore-2") verifySslProduceConsume(sslProperties2, next_group_name())
// Revert to old truststore with only one certificate and update. Clients should connect only with old keystore. // Revert to old truststore with only one certificate and update. Clients should connect only with old keystore.
val oldTruststoreProps = new Properties val oldTruststoreProps = new Properties
@ -478,7 +484,7 @@ class DynamicBrokerReconfigurationTest extends QuorumTestHarness with SaslSetup
reconfigureServers(oldTruststoreProps, perBrokerConfig = true, reconfigureServers(oldTruststoreProps, perBrokerConfig = true,
(s"$prefix$SSL_TRUSTSTORE_LOCATION_CONFIG", sslProperties1.getProperty(SSL_TRUSTSTORE_LOCATION_CONFIG))) (s"$prefix$SSL_TRUSTSTORE_LOCATION_CONFIG", sslProperties1.getProperty(SSL_TRUSTSTORE_LOCATION_CONFIG)))
verifyAuthenticationFailure(producerBuilder.keyStoreProps(sslProperties2).build()) verifyAuthenticationFailure(producerBuilder.keyStoreProps(sslProperties2).build())
verifySslProduceConsume(sslProperties1, "alter-truststore-3") verifySslProduceConsume(sslProperties1, next_group_name())
// Update same truststore file to contain both certificates without changing any configs. // Update same truststore file to contain both certificates without changing any configs.
// Clients should connect successfully with either keystore after admin client AlterConfigsRequest completes. // Clients should connect successfully with either keystore after admin client AlterConfigsRequest completes.
@ -486,8 +492,14 @@ class DynamicBrokerReconfigurationTest extends QuorumTestHarness with SaslSetup
Paths.get(sslProperties1.getProperty(SSL_TRUSTSTORE_LOCATION_CONFIG)), Paths.get(sslProperties1.getProperty(SSL_TRUSTSTORE_LOCATION_CONFIG)),
StandardCopyOption.REPLACE_EXISTING) StandardCopyOption.REPLACE_EXISTING)
TestUtils.incrementalAlterConfigs(servers, adminClients.head, oldTruststoreProps, perBrokerConfig = true).all.get() TestUtils.incrementalAlterConfigs(servers, adminClients.head, oldTruststoreProps, perBrokerConfig = true).all.get()
verifySslProduceConsume(sslProperties1, "alter-truststore-4") TestUtils.retry(30000) {
verifySslProduceConsume(sslProperties2, "alter-truststore-5") try {
verifySslProduceConsume(sslProperties1, next_group_name())
verifySslProduceConsume(sslProperties2, next_group_name())
} catch {
case t: Throwable => throw new AssertionError(t)
}
}
// Update internal keystore/truststore and validate new client connections from broker (e.g. controller). // Update internal keystore/truststore and validate new client connections from broker (e.g. controller).
// Alter internal keystore from `sslProperties1` to `sslProperties2`, force disconnect of a controller connection // Alter internal keystore from `sslProperties1` to `sslProperties2`, force disconnect of a controller connection
@ -495,21 +507,23 @@ class DynamicBrokerReconfigurationTest extends QuorumTestHarness with SaslSetup
val props2 = securityProps(sslProperties2, KEYSTORE_PROPS, prefix) val props2 = securityProps(sslProperties2, KEYSTORE_PROPS, prefix)
props2 ++= securityProps(combinedStoreProps, TRUSTSTORE_PROPS, prefix) props2 ++= securityProps(combinedStoreProps, TRUSTSTORE_PROPS, prefix)
TestUtils.incrementalAlterConfigs(servers, adminClients.head, props2, perBrokerConfig = true).all.get(15, TimeUnit.SECONDS) TestUtils.incrementalAlterConfigs(servers, adminClients.head, props2, perBrokerConfig = true).all.get(15, TimeUnit.SECONDS)
verifySslProduceConsume(sslProperties2, "alter-truststore-6") verifySslProduceConsume(sslProperties2, next_group_name())
props2 ++= securityProps(sslProperties2, TRUSTSTORE_PROPS, prefix) props2 ++= securityProps(sslProperties2, TRUSTSTORE_PROPS, prefix)
TestUtils.incrementalAlterConfigs(servers, adminClients.head, props2, perBrokerConfig = true).all.get(15, TimeUnit.SECONDS) TestUtils.incrementalAlterConfigs(servers, adminClients.head, props2, perBrokerConfig = true).all.get(15, TimeUnit.SECONDS)
verifySslProduceConsume(sslProperties2, "alter-truststore-7") verifySslProduceConsume(sslProperties2, next_group_name())
waitForAuthenticationFailure(producerBuilder.keyStoreProps(sslProperties1)) waitForAuthenticationFailure(producerBuilder.keyStoreProps(sslProperties1))
val controller = servers.find(_.config.brokerId == TestUtils.waitUntilControllerElected(zkClient)).get.asInstanceOf[KafkaServer] if (!isKRaftTest()) {
val controllerChannelManager = controller.kafkaController.controllerChannelManager val controller = servers.find(_.config.brokerId == TestUtils.waitUntilControllerElected(zkClient)).get.asInstanceOf[KafkaServer]
val brokerStateInfo: mutable.HashMap[Int, ControllerBrokerStateInfo] = val controllerChannelManager = controller.kafkaController.controllerChannelManager
JTestUtils.fieldValue(controllerChannelManager, classOf[ControllerChannelManager], "brokerStateInfo") val brokerStateInfo: mutable.HashMap[Int, ControllerBrokerStateInfo] =
brokerStateInfo(0).networkClient.disconnect("0") JTestUtils.fieldValue(controllerChannelManager, classOf[ControllerChannelManager], "brokerStateInfo")
TestUtils.createTopic(zkClient, "testtopic2", numPartitions, replicationFactor = numServers, servers) brokerStateInfo(0).networkClient.disconnect("0")
TestUtils.createTopic(zkClient, "testtopic2", numPartitions, replicationFactor = numServers, servers)
// validate that the brokerToController request works fine // validate that the brokerToController request works fine
verifyBrokerToControllerCall(controller) verifyBrokerToControllerCall(controller)
}
} }
@ParameterizedTest(name = TestInfoUtils.TestWithParameterizedQuorumName) @ParameterizedTest(name = TestInfoUtils.TestWithParameterizedQuorumName)

View File

@ -21,6 +21,7 @@ import org.apache.kafka.clients.admin.AlterConfigOp.OpType;
import org.apache.kafka.common.config.ConfigException; import org.apache.kafka.common.config.ConfigException;
import org.apache.kafka.common.config.ConfigResource.Type; import org.apache.kafka.common.config.ConfigResource.Type;
import org.apache.kafka.common.config.ConfigResource; import org.apache.kafka.common.config.ConfigResource;
import org.apache.kafka.common.config.types.Password;
import org.apache.kafka.common.metadata.ConfigRecord; import org.apache.kafka.common.metadata.ConfigRecord;
import org.apache.kafka.common.requests.ApiError; import org.apache.kafka.common.requests.ApiError;
import org.apache.kafka.common.utils.LogContext; import org.apache.kafka.common.utils.LogContext;
@ -146,7 +147,8 @@ public class ConfigurationControlManager {
} }
break; break;
} }
if (!Objects.equals(currentValue, newValue)) { if (!Objects.equals(currentValue, newValue) || configResource.type().equals(Type.BROKER)) {
// 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(). newRecords.add(new ApiMessageAndVersion(new ConfigRecord().
setResourceType(configResource.type().id()). setResourceType(configResource.type().id()).
setResourceName(configResource.name()). setResourceName(configResource.name()).
@ -233,7 +235,8 @@ public class ConfigurationControlManager {
String key = entry.getKey(); String key = entry.getKey();
String newValue = entry.getValue(); String newValue = entry.getValue();
String currentValue = currentConfigs.get(key); String currentValue = currentConfigs.get(key);
if (!Objects.equals(newValue, currentValue)) { if (!Objects.equals(currentValue, newValue) || configResource.type().equals(Type.BROKER)) {
// 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(). newRecords.add(new ApiMessageAndVersion(new ConfigRecord().
setResourceType(configResource.type().id()). setResourceType(configResource.type().id()).
setResourceName(configResource.name()). setResourceName(configResource.name()).
@ -297,7 +300,11 @@ public class ConfigurationControlManager {
if (configs.isEmpty()) { if (configs.isEmpty()) {
configData.remove(configResource); configData.remove(configResource);
} }
log.info("{}: set configuration {} to {}", configResource, record.name(), record.value()); if (configSchema.isSensitive(record)) {
log.info("{}: set configuration {} to {}", configResource, record.name(), Password.HIDDEN);
} else {
log.info("{}: set configuration {} to {}", configResource, record.name(), record.value());
}
} }
// VisibleForTesting // VisibleForTesting