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 @@ -180,7 +180,7 @@ private Long buildRemoteLogAuxState(TopicPartition topicPartition,

long nextOffset;

if (unifiedLog.remoteStorageSystemEnable() && unifiedLog.config().remoteLogConfig.remoteStorageEnable) {
if (unifiedLog.remoteStorageSystemEnable() && unifiedLog.config().remoteStorageEnable()) {
if (replicaMgr.remoteLogManager().isEmpty()) throw new IllegalStateException("RemoteLogManager is not yet instantiated");

RemoteLogManager rlm = replicaMgr.remoteLogManager().get();
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/kafka/log/UnifiedLog.scala
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ class UnifiedLog(@volatile var logStartOffset: Long,
!(config.compact || Topic.isInternal(topicPartition.topic())
|| TopicBasedRemoteLogMetadataManagerConfig.REMOTE_LOG_METADATA_TOPIC_NAME.equals(topicPartition.topic())
|| Topic.CLUSTER_METADATA_TOPIC_NAME.equals(topicPartition.topic())) &&
config.remoteLogConfig.remoteStorageEnable
config.remoteStorageEnable()
}

/**
Expand Down
34 changes: 34 additions & 0 deletions core/src/main/scala/kafka/server/DynamicBrokerConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.apache.kafka.common.network.{ListenerName, ListenerReconfigurable}
import org.apache.kafka.common.security.authenticator.LoginManager
import org.apache.kafka.common.utils.{ConfigUtils, Utils}
import org.apache.kafka.server.config.ServerTopicConfigSynonyms
import org.apache.kafka.server.log.remote.storage.RemoteLogManagerConfig
import org.apache.kafka.storage.internals.log.{LogConfig, ProducerStateManagerConfig}

import scala.annotation.nowarn
Expand Down Expand Up @@ -675,6 +676,39 @@ class DynamicLogConfig(logManager: LogManager, server: KafkaBroker) extends Brok
// For update of topic config overrides, only config names and types are validated
// Names and types have already been validated. For consistency with topic config
// validation, no additional validation is performed.

def validateLogLocalRetentionMs(): Unit = {
val logRetentionMs = newConfig.logRetentionTimeMillis
val logLocalRetentionMs: java.lang.Long = newConfig.logLocalRetentionMs
if (logRetentionMs != -1L && logLocalRetentionMs != -2L) {
if (logLocalRetentionMs == -1L) {
throw new ConfigException(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_MS_PROP, logLocalRetentionMs,
s"Value must not be -1 as ${KafkaConfig.LogRetentionTimeMillisProp} value is set as $logRetentionMs.")
}
if (logLocalRetentionMs > logRetentionMs) {
throw new ConfigException(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_MS_PROP, logLocalRetentionMs,
s"Value must not be more than ${KafkaConfig.LogRetentionTimeMillisProp} property value: $logRetentionMs")
}
}
}

def validateLogLocalRetentionBytes(): Unit = {
val logRetentionBytes = newConfig.logRetentionBytes
val logLocalRetentionBytes: java.lang.Long = newConfig.logLocalRetentionBytes
if (logRetentionBytes > -1 && logLocalRetentionBytes != -2) {
if (logLocalRetentionBytes == -1) {
throw new ConfigException(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_BYTES_PROP, logLocalRetentionBytes,
s"Value must not be -1 as ${KafkaConfig.LogRetentionBytesProp} value is set as $logRetentionBytes.")
}
if (logLocalRetentionBytes > logRetentionBytes) {
throw new ConfigException(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_BYTES_PROP, logLocalRetentionBytes,
s"Value must not be more than ${KafkaConfig.LogRetentionBytesProp} property value: $logRetentionBytes")
}
}
}

validateLogLocalRetentionMs()
validateLogLocalRetentionBytes()
}

private def updateLogsConfig(newBrokerDefaults: Map[String, Object]): Unit = {
Expand Down
13 changes: 12 additions & 1 deletion core/src/main/scala/kafka/server/KafkaConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

package kafka.server

import java.util
import java.{lang, util}
import java.util.concurrent.TimeUnit
import java.util.{Collections, Locale, Properties}
import kafka.cluster.EndPoint
Expand Down Expand Up @@ -2171,6 +2171,12 @@ class KafkaConfig private(doLog: Boolean, val props: java.util.Map[_, _], dynami
def usesTopicId: Boolean =
usesSelfManagedQuorum || interBrokerProtocolVersion.isTopicIdsSupported()


val isRemoteLogStorageSystemEnabled: lang.Boolean = getBoolean(RemoteLogManagerConfig.REMOTE_LOG_STORAGE_SYSTEM_ENABLE_PROP)
def logLocalRetentionBytes: java.lang.Long = getLong(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_BYTES_PROP)

def logLocalRetentionMs: java.lang.Long = getLong(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_MS_PROP)

validateValues()

@nowarn("cat=deprecation")
Expand Down Expand Up @@ -2203,6 +2209,9 @@ class KafkaConfig private(doLog: Boolean, val props: java.util.Map[_, _], dynami
require(logRollTimeJitterMillis >= 0, "log.roll.jitter.ms must be greater than or equal to 0")
require(logRetentionTimeMillis >= 1 || logRetentionTimeMillis == -1, "log.retention.ms must be unlimited (-1) or, greater than or equal to 1")
require(logDirs.nonEmpty, "At least one log directory must be defined via log.dirs or log.dir.")
if (isRemoteLogStorageSystemEnabled && logDirs.size > 1) {
throw new ConfigException(s"Multiple log directories `${logDirs.mkString(",")}` are not supported when remote log storage is enabled")
}
require(logCleanerDedupeBufferSize / logCleanerThreads > 1024 * 1024, "log.cleaner.dedupe.buffer.size must be at least 1MB per cleaner thread.")
require(replicaFetchWaitMaxMs <= replicaSocketTimeoutMs, "replica.socket.timeout.ms should always be at least replica.fetch.wait.max.ms" +
" to prevent unnecessary socket timeouts")
Expand Down Expand Up @@ -2442,6 +2451,8 @@ class KafkaConfig private(doLog: Boolean, val props: java.util.Map[_, _], dynami
logProps.put(TopicConfig.MESSAGE_TIMESTAMP_TYPE_CONFIG, logMessageTimestampType.name)
logProps.put(TopicConfig.MESSAGE_TIMESTAMP_DIFFERENCE_MAX_MS_CONFIG, logMessageTimestampDifferenceMaxMs: java.lang.Long)
logProps.put(TopicConfig.MESSAGE_DOWNCONVERSION_ENABLE_CONFIG, logMessageDownConversionEnable: java.lang.Boolean)
logProps.put(TopicConfig.LOCAL_LOG_RETENTION_MS_CONFIG, logLocalRetentionMs)
logProps.put(TopicConfig.LOCAL_LOG_RETENTION_BYTES_CONFIG, logLocalRetentionBytes)
logProps
}

Expand Down
39 changes: 30 additions & 9 deletions core/src/test/scala/unit/kafka/log/LogConfigTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test

import java.util.{Collections, Properties}
import org.apache.kafka.server.common.MetadataVersion.IBP_3_0_IV1
import org.apache.kafka.server.log.remote.storage.RemoteLogManagerConfig
import org.apache.kafka.storage.internals.log.{LogConfig, ThrottledReplicaListValidator}

import scala.annotation.nowarn
Expand Down Expand Up @@ -57,18 +58,24 @@ class LogConfigTest {
@Test
def testKafkaConfigToProps(): Unit = {
val millisInHour = 60L * 60L * 1000L
val millisInDay = 24L * millisInHour
val bytesInGB: Long = 1024 * 1024 * 1024
val kafkaProps = TestUtils.createBrokerConfig(nodeId = 0, zkConnect = "")
kafkaProps.put(KafkaConfig.LogRollTimeHoursProp, "2")
kafkaProps.put(KafkaConfig.LogRollTimeJitterHoursProp, "2")
kafkaProps.put(KafkaConfig.LogRetentionTimeHoursProp, "2")
kafkaProps.put(KafkaConfig.LogRetentionTimeHoursProp, "960") // 40 days
kafkaProps.put(KafkaConfig.LogMessageFormatVersionProp, "0.11.0")
kafkaProps.put(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_MS_PROP, "2592000000") // 30 days
kafkaProps.put(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_BYTES_PROP, "4294967296") // 4 GB

val logProps = KafkaConfig.fromProps(kafkaProps).extractLogConfigMap
assertEquals(2 * millisInHour, logProps.get(TopicConfig.SEGMENT_MS_CONFIG))
assertEquals(2 * millisInHour, logProps.get(TopicConfig.SEGMENT_JITTER_MS_CONFIG))
assertEquals(2 * millisInHour, logProps.get(TopicConfig.RETENTION_MS_CONFIG))
assertEquals(40 * millisInDay, logProps.get(TopicConfig.RETENTION_MS_CONFIG))
// The message format version should always be 3.0 if the inter-broker protocol version is 3.0 or higher
assertEquals(IBP_3_0_IV1.version, logProps.get(TopicConfig.MESSAGE_FORMAT_VERSION_CONFIG))
assertEquals(30 * millisInDay, logProps.get(TopicConfig.LOCAL_LOG_RETENTION_MS_CONFIG))
assertEquals(4 * bytesInGB, logProps.get(TopicConfig.LOCAL_LOG_RETENTION_BYTES_CONFIG))
}

@nowarn("cat=deprecation")
Expand Down Expand Up @@ -218,17 +225,17 @@ class LogConfigTest {
props.put(TopicConfig.RETENTION_MS_CONFIG, retentionMs.toString)
val logConfig = new LogConfig(props)

assertEquals(retentionMs, logConfig.remoteLogConfig.localRetentionMs)
assertEquals(retentionBytes, logConfig.remoteLogConfig.localRetentionBytes)
assertEquals(retentionMs, logConfig.localRetentionMs)
assertEquals(retentionBytes, logConfig.localRetentionBytes)
}

@Test
def testLocalLogRetentionDerivedDefaultProps(): Unit = {
val logConfig = new LogConfig(new Properties())

// Local retention defaults are derived from retention properties which can be default or custom.
assertEquals(LogConfig.DEFAULT_RETENTION_MS, logConfig.remoteLogConfig.localRetentionMs)
assertEquals(LogConfig.DEFAULT_RETENTION_BYTES, logConfig.remoteLogConfig.localRetentionBytes)
assertEquals(LogConfig.DEFAULT_RETENTION_MS, logConfig.localRetentionMs)
assertEquals(LogConfig.DEFAULT_RETENTION_BYTES, logConfig.localRetentionBytes)
}

@Test
Expand All @@ -243,8 +250,8 @@ class LogConfigTest {
props.put(TopicConfig.LOCAL_LOG_RETENTION_BYTES_CONFIG, localRetentionBytes.toString)
val logConfig = new LogConfig(props)

assertEquals(localRetentionMs, logConfig.remoteLogConfig.localRetentionMs)
assertEquals(localRetentionBytes, logConfig.remoteLogConfig.localRetentionBytes)
assertEquals(localRetentionMs, logConfig.localRetentionMs)
assertEquals(localRetentionBytes, logConfig.localRetentionBytes)
}

@Test
Expand Down Expand Up @@ -275,6 +282,20 @@ class LogConfigTest {

props.put(TopicConfig.LOCAL_LOG_RETENTION_MS_CONFIG, localRetentionMs.toString)
props.put(TopicConfig.LOCAL_LOG_RETENTION_BYTES_CONFIG, localRetentionBytes.toString)
assertThrows(classOf[ConfigException], () => new LogConfig(props));
assertThrows(classOf[ConfigException], () => LogConfig.validate(props))
}

@Test
def testEnableRemoteLogStorageOnCompactedTopic(): Unit = {
val props = new Properties()
props.put(TopicConfig.CLEANUP_POLICY_CONFIG, TopicConfig.CLEANUP_POLICY_DELETE)
props.put(TopicConfig.REMOTE_LOG_STORAGE_ENABLE_CONFIG, "true")
LogConfig.validate(props)
props.put(TopicConfig.CLEANUP_POLICY_CONFIG, TopicConfig.CLEANUP_POLICY_COMPACT)
assertThrows(classOf[ConfigException], () => LogConfig.validate(props))
props.put(TopicConfig.CLEANUP_POLICY_CONFIG, "delete, compact")
assertThrows(classOf[ConfigException], () => LogConfig.validate(props))
props.put(TopicConfig.CLEANUP_POLICY_CONFIG, "compact, delete")
assertThrows(classOf[ConfigException], () => LogConfig.validate(props))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import org.apache.kafka.common.config.{ConfigException, SslConfigs}
import org.apache.kafka.common.metrics.{JmxReporter, Metrics}
import org.apache.kafka.common.network.ListenerName
import org.apache.kafka.server.authorizer._
import org.apache.kafka.server.log.remote.storage.RemoteLogManagerConfig
import org.apache.kafka.server.metrics.KafkaYammerMetrics
import org.apache.kafka.server.util.KafkaScheduler
import org.apache.kafka.storage.internals.log.{LogConfig, ProducerStateManagerConfig}
Expand Down Expand Up @@ -713,6 +714,99 @@ class DynamicBrokerConfigTest {
config.updateCurrentConfig(new KafkaConfig(props))
assertFalse(config.nonInternalValues.containsKey(KafkaConfig.MetadataLogSegmentMinBytesProp))
}

@Test
def testDynamicLogLocalRetentionMsConfig(): Unit = {
val props = TestUtils.createBrokerConfig(0, TestUtils.MockZkConnect, port = 8181)
props.put(KafkaConfig.LogRetentionTimeMillisProp, "2592000000")
val config = KafkaConfig(props)
val dynamicLogConfig = new DynamicLogConfig(mock(classOf[LogManager]), mock(classOf[KafkaServer]))
config.dynamicConfig.initialize(None)
config.dynamicConfig.addBrokerReconfigurable(dynamicLogConfig)

val newProps = new Properties()
newProps.put(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_MS_PROP, "2160000000")
// update default config
config.dynamicConfig.validate(newProps, perBrokerConfig = false)
config.dynamicConfig.updateDefaultConfig(newProps)
assertEquals(2160000000L, config.logLocalRetentionMs)

// update per broker config
config.dynamicConfig.validate(newProps, perBrokerConfig = true)
newProps.put(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_MS_PROP, "2150000000")
config.dynamicConfig.updateBrokerConfig(0, newProps)
assertEquals(2150000000L, config.logLocalRetentionMs)
}

@Test
def testDynamicLogLocalRetentionSizeConfig(): Unit = {
val props = TestUtils.createBrokerConfig(0, TestUtils.MockZkConnect, port = 8181)
props.put(KafkaConfig.LogRetentionBytesProp, "4294967296")
val config = KafkaConfig(props)
val dynamicLogConfig = new DynamicLogConfig(mock(classOf[LogManager]), mock(classOf[KafkaServer]))
config.dynamicConfig.initialize(None)
config.dynamicConfig.addBrokerReconfigurable(dynamicLogConfig)

val newProps = new Properties()
newProps.put(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_BYTES_PROP, "4294967295")
// update default config
config.dynamicConfig.validate(newProps, perBrokerConfig = false)
config.dynamicConfig.updateDefaultConfig(newProps)
assertEquals(4294967295L, config.logLocalRetentionBytes)

// update per broker config
config.dynamicConfig.validate(newProps, perBrokerConfig = true)
newProps.put(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_BYTES_PROP, "4294967294")
config.dynamicConfig.updateBrokerConfig(0, newProps)
assertEquals(4294967294L, config.logLocalRetentionBytes)
}

@Test
def testDynamicLogLocalRetentionSkipsOnInvalidConfig(): Unit = {
val props = TestUtils.createBrokerConfig(0, TestUtils.MockZkConnect, port = 8181)
props.put(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_MS_PROP, "1000")
props.put(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_BYTES_PROP, "1024")
val config = KafkaConfig(props)
config.dynamicConfig.initialize(None)

// Check for invalid localRetentionMs < -2
verifyConfigUpdateWithInvalidConfig(config, props, Map.empty, Map(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_MS_PROP -> "-3"))
// Check for invalid localRetentionBytes < -2
verifyConfigUpdateWithInvalidConfig(config, props, Map.empty, Map(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_BYTES_PROP -> "-3"))
}

@Test
def testDynamicLogLocalRetentionThrowsOnIncorrectConfig(): Unit = {
// Check for incorrect case of logLocalRetentionMs > retentionMs
verifyIncorrectLogLocalRetentionProps(2000L, 1000L, 2, 100)
// Check for incorrect case of logLocalRetentionBytes > retentionBytes
verifyIncorrectLogLocalRetentionProps(500L, 1000L, 200, 100)
// Check for incorrect case of logLocalRetentionMs (-1 viz unlimited) > retentionMs,
verifyIncorrectLogLocalRetentionProps(-1, 1000L, 200, 100)
// Check for incorrect case of logLocalRetentionBytes(-1 viz unlimited) > retentionBytes
verifyIncorrectLogLocalRetentionProps(2000L, 1000L, -1, 100)
}

def verifyIncorrectLogLocalRetentionProps(logLocalRetentionMs: Long,
retentionMs: Long,
logLocalRetentionBytes: Long,
retentionBytes: Long): Unit = {
val props = TestUtils.createBrokerConfig(0, TestUtils.MockZkConnect, port = 8181)
props.put(KafkaConfig.LogRetentionTimeMillisProp, retentionMs.toString)
props.put(KafkaConfig.LogRetentionBytesProp, retentionBytes.toString)
val config = KafkaConfig(props)
val dynamicLogConfig = new DynamicLogConfig(mock(classOf[LogManager]), mock(classOf[KafkaServer]))
config.dynamicConfig.initialize(None)
config.dynamicConfig.addBrokerReconfigurable(dynamicLogConfig)

val newProps = new Properties()
newProps.put(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_MS_PROP, logLocalRetentionMs.toString)
newProps.put(RemoteLogManagerConfig.LOG_LOCAL_RETENTION_BYTES_PROP, logLocalRetentionBytes.toString)
// validate default config
assertThrows(classOf[ConfigException], () => config.dynamicConfig.validate(newProps, perBrokerConfig = false))
// validate per broker config
assertThrows(classOf[ConfigException], () => config.dynamicConfig.validate(newProps, perBrokerConfig = true))
}
}

class TestDynamicThreadPool() extends BrokerReconfigurable {
Expand Down
28 changes: 25 additions & 3 deletions core/src/test/scala/unit/kafka/server/KafkaConfigTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,8 @@ class KafkaConfigTest {
case RemoteLogManagerConfig.REMOTE_LOG_MANAGER_TASK_RETRY_JITTER_PROP => assertPropertyInvalid(baseProperties, name, "not_a_number", -1, 0.51)
case RemoteLogManagerConfig.REMOTE_LOG_READER_THREADS_PROP => assertPropertyInvalid(baseProperties, name, "not_a_number", 0, -1)
case RemoteLogManagerConfig.REMOTE_LOG_READER_MAX_PENDING_TASKS_PROP => assertPropertyInvalid(baseProperties, name, "not_a_number", 0, -1)
case RemoteLogManagerConfig.LOG_LOCAL_RETENTION_MS_PROP => assertPropertyInvalid(baseProperties, name, "not_a_number", -3)
case RemoteLogManagerConfig.LOG_LOCAL_RETENTION_BYTES_PROP => assertPropertyInvalid(baseProperties, name, "not_a_number", -3)

/** New group coordinator configs */
case KafkaConfig.NewGroupCoordinatorEnableProp => // ignore
Expand All @@ -1035,9 +1037,6 @@ class KafkaConfigTest {
case KafkaConfig.ConsumerGroupMaxSizeProp => assertPropertyInvalid(baseProperties, name, "not_a_number", 0, -1)
case KafkaConfig.ConsumerGroupAssignorsProp => // ignore string

case TopicConfig.LOCAL_LOG_RETENTION_MS_CONFIG => assertPropertyInvalid(baseProperties, name, "not_a_number", 0, -2)
case TopicConfig.LOCAL_LOG_RETENTION_BYTES_CONFIG => assertPropertyInvalid(baseProperties, name, "not_a_number", 0, -2)

case _ => assertPropertyInvalid(baseProperties, name, "not_a_number", "-1")
}
}
Expand All @@ -1053,6 +1052,7 @@ class KafkaConfigTest {
}

val props = baseProperties
props.put(RemoteLogManagerConfig.REMOTE_LOG_STORAGE_SYSTEM_ENABLE_PROP, "true")
val config = KafkaConfig.fromProps(props)

def assertDynamic(property: String, value: Any, accessor: () => Any): Unit = {
Expand Down Expand Up @@ -1114,6 +1114,10 @@ class KafkaConfigTest {
assertDynamic(kafkaConfigProp, 10014L, () => config.logRollTimeJitterMillis)
case TopicConfig.UNCLEAN_LEADER_ELECTION_ENABLE_CONFIG =>
assertDynamic(kafkaConfigProp, true, () => config.uncleanLeaderElectionEnable)
case TopicConfig.LOCAL_LOG_RETENTION_MS_CONFIG =>
assertDynamic(kafkaConfigProp, 10015L, () => config.logLocalRetentionMs)
case TopicConfig.LOCAL_LOG_RETENTION_BYTES_CONFIG =>
assertDynamic(kafkaConfigProp, 10016L, () => config.logLocalRetentionBytes)
case TopicConfig.MESSAGE_FORMAT_VERSION_CONFIG =>
// not dynamically updatable
case LogConfig.FOLLOWER_REPLICATION_THROTTLED_REPLICAS_CONFIG =>
Expand Down Expand Up @@ -1784,4 +1788,22 @@ class KafkaConfigTest {
props.put(KafkaConfig.ConsumerGroupHeartbeatIntervalMsProp, "25")
assertThrows(classOf[IllegalArgumentException], () => KafkaConfig.fromProps(props))
}

@Test
def testMultipleLogDirectoriesNotSupportedWithRemoteLogStorage(): Unit = {
val props = TestUtils.createBrokerConfig(0, TestUtils.MockZkConnect, port = 8181)
props.put(RemoteLogManagerConfig.REMOTE_LOG_STORAGE_SYSTEM_ENABLE_PROP, String.valueOf(true))
props.put(KafkaConfig.LogDirsProp, "/tmp/a,/tmp/b")

val caught = assertThrows(classOf[ConfigException], () => KafkaConfig.fromProps(props))
assertTrue(caught.getMessage.contains("Multiple log directories `/tmp/a,/tmp/b` are not supported when remote log storage is enabled"))
}

@Test
def testSingleLogDirectoryWithRemoteLogStorage(): Unit = {
val props = TestUtils.createBrokerConfig(0, TestUtils.MockZkConnect, port = 8181)
props.put(RemoteLogManagerConfig.REMOTE_LOG_STORAGE_SYSTEM_ENABLE_PROP, String.valueOf(true))
props.put(KafkaConfig.LogDirsProp, "/tmp/a")
assertDoesNotThrow(() => KafkaConfig.fromProps(props))
}
}
Loading