-
Notifications
You must be signed in to change notification settings - Fork 14.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
KAFKA-18530 Remove ZooKeeperInternals #18641
Changes from 10 commits
b7bbdff
a022b0c
6cef55f
72fc80b
8065ee7
ff19654
821acd0
2d16565
fe8b6ca
b81ae52
4ca7f31
a6a52e0
44274d4
e9b1e5b
1927166
b52aaed
b4b727d
a201ae9
dd871e2
13d9b7f
ed6353b
d098405
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,7 +29,7 @@ import org.apache.kafka.common.metrics.Metrics | |
import org.apache.kafka.common.metrics.stats.{Avg, CumulativeSum, Rate} | ||
import org.apache.kafka.common.security.auth.KafkaPrincipal | ||
import org.apache.kafka.common.utils.{Sanitizer, Time} | ||
import org.apache.kafka.server.config.{ClientQuotaManagerConfig, ZooKeeperInternals} | ||
import org.apache.kafka.server.config.ClientQuotaManagerConfig | ||
import org.apache.kafka.server.quota.{ClientQuotaCallback, ClientQuotaEntity, ClientQuotaType, QuotaType, QuotaUtils, SensorAccess, ThrottleCallback, ThrottledChannel} | ||
import org.apache.kafka.server.util.ShutdownableThread | ||
import org.apache.kafka.network.Session | ||
|
@@ -55,7 +55,7 @@ object QuotaTypes { | |
object ClientQuotaManager { | ||
// Purge sensors after 1 hour of inactivity | ||
val InactiveSensorExpirationTimeSeconds = 3600 | ||
|
||
val DefaultString = "<default>" | ||
val DefaultClientIdQuotaEntity: KafkaQuotaEntity = KafkaQuotaEntity(None, Some(DefaultClientIdEntity)) | ||
val DefaultUserQuotaEntity: KafkaQuotaEntity = KafkaQuotaEntity(Some(DefaultUserEntity), None) | ||
val DefaultUserClientIdQuotaEntity: KafkaQuotaEntity = KafkaQuotaEntity(Some(DefaultUserEntity), Some(DefaultClientIdEntity)) | ||
|
@@ -76,13 +76,13 @@ object ClientQuotaManager { | |
|
||
case object DefaultUserEntity extends BaseUserEntity { | ||
override def entityType: ClientQuotaEntity.ConfigEntityType = ClientQuotaEntity.ConfigEntityType.DEFAULT_USER | ||
override def name: String = ZooKeeperInternals.DEFAULT_STRING | ||
override def name: String = DefaultString | ||
override def toString: String = "default user" | ||
} | ||
|
||
case object DefaultClientIdEntity extends ClientQuotaEntity.ConfigEntity { | ||
override def entityType: ClientQuotaEntity.ConfigEntityType = ClientQuotaEntity.ConfigEntityType.DEFAULT_CLIENT_ID | ||
override def name: String = ZooKeeperInternals.DEFAULT_STRING | ||
override def name: String = DefaultString | ||
override def toString: String = "default client-id" | ||
} | ||
|
||
|
@@ -93,7 +93,7 @@ object ClientQuotaManager { | |
|
||
def sanitizedUser: String = userEntity.map { | ||
case entity: UserEntity => entity.sanitizedUser | ||
case DefaultUserEntity => ZooKeeperInternals.DEFAULT_STRING | ||
case DefaultUserEntity => DefaultString | ||
}.getOrElse("") | ||
|
||
def clientId: String = clientIdEntity.map(_.name).getOrElse("") | ||
|
@@ -260,7 +260,7 @@ class ClientQuotaManager(private val config: ClientQuotaManagerConfig, | |
*/ | ||
def unrecordQuotaSensor(request: RequestChannel.Request, value: Double, timeMs: Long): Unit = { | ||
val clientSensors = getOrCreateQuotaSensors(request.session, request.header.clientId) | ||
clientSensors.quotaSensor.record(value * (-1), timeMs, false) | ||
clientSensors.quotaSensor.record(value * -1, timeMs, false) | ||
} | ||
|
||
/** | ||
|
@@ -404,11 +404,14 @@ class ClientQuotaManager(private val config: ClientQuotaManagerConfig, | |
* for any of these levels. | ||
* | ||
* @param sanitizedUser user to override if quota applies to <user> or <user, client-id> | ||
* @param clientId client to override if quota applies to <client-id> or <user, client-id> | ||
* @param sanitizedClientId sanitized client ID to override if quota applies to <client-id> or <user, client-id> | ||
* @param quota custom quota to apply or None if quota override is being removed | ||
*/ | ||
def updateQuota(sanitizedUser: Option[String], clientId: Option[String], sanitizedClientId: Option[String], quota: Option[Quota]): Unit = { | ||
def updateQuota( | ||
sanitizedUser: Option[BaseUserEntity], | ||
sanitizedClientId: Option[ClientQuotaEntity.ConfigEntity], | ||
quota: Option[Quota] | ||
): Unit = { | ||
/* | ||
* Acquire the write lock to apply changes in the quota objects. | ||
* This method changes the quota in the overriddenQuota map and applies the update on the actual KafkaMetric object (if it exists). | ||
|
@@ -418,14 +421,9 @@ class ClientQuotaManager(private val config: ClientQuotaManagerConfig, | |
*/ | ||
lock.writeLock().lock() | ||
try { | ||
val userEntity = sanitizedUser.map { | ||
case ZooKeeperInternals.DEFAULT_STRING => DefaultUserEntity | ||
case user => UserEntity(user) | ||
} | ||
val clientIdEntity = sanitizedClientId.map { | ||
case ZooKeeperInternals.DEFAULT_STRING => DefaultClientIdEntity | ||
case _ => ClientIdEntity(clientId.getOrElse(throw new IllegalStateException("Client-id not provided"))) | ||
} | ||
val userEntity = getOrDefaultUser(sanitizedUser) | ||
val clientIdEntity = getOrDefaultClient(sanitizedClientId) | ||
|
||
val quotaEntity = KafkaQuotaEntity(userEntity, clientIdEntity) | ||
|
||
if (userEntity.nonEmpty) { | ||
|
@@ -451,10 +449,31 @@ class ClientQuotaManager(private val config: ClientQuotaManagerConfig, | |
} | ||
} | ||
|
||
private def getOrDefaultClient( | ||
sanitizedClientId: Option[ClientQuotaEntity.ConfigEntity] | ||
): Option[ClientQuotaEntity.ConfigEntity] = { | ||
if (sanitizedClientId.isEmpty) | ||
None | ||
else if (sanitizedClientId.get.name() == DefaultString) | ||
Some(DefaultClientIdEntity) | ||
else { | ||
val clientId = sanitizedClientId.map(s => Sanitizer.desanitize(s.name())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should handle the |
||
Some(ClientIdEntity(clientId.getOrElse(throw new IllegalStateException("Client-id not provided")))) | ||
} | ||
} | ||
|
||
private def getOrDefaultUser(sanitizedUser: Option[BaseUserEntity]): Option[BaseUserEntity] = { | ||
if (sanitizedUser.isEmpty) | ||
None | ||
else if (sanitizedUser.get.name() == DefaultString) | ||
Some(DefaultUserEntity) | ||
else | ||
sanitizedUser | ||
} | ||
|
||
/** | ||
* Updates metrics configs. This is invoked when quota configs are updated in ZooKeeper | ||
* or when partitions leaders change and custom callbacks that implement partition-based quotas | ||
* have updated quotas. | ||
* Updates metrics configs. This is invoked when quota configs are updated when partitions leaders change | ||
* and custom callbacks that implement partition-based quotas have updated quotas. | ||
* | ||
* @param updatedQuotaEntity If set to one entity and quotas have only been enabled at one | ||
* level, then an optimized update is performed with a single metric update. If None is provided, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,13 +48,6 @@ import scala.collection._ | |
import scala.jdk.CollectionConverters._ | ||
|
||
/** | ||
* Dynamic broker configurations are stored in ZooKeeper and may be defined at two levels: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in kraft mode, the two levels are still existent. could you please revise it instead of removing it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Heya @m1a2st! I have a follow-up ask, while the two levels are still present they will no longer be under the path specified in this file since the path is ZK-specific. As long as we don't use this comment to generate documentation I am okay with this being removed in a subsequent PR. However, if we use this file to generate documentation could you change this as part of this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think those comments in DynamicBrokerConfig are not used to generate documentation, but it will be better to correct it - as least, the description about zk path must be removed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, then I will review once they have been changed 😊! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
* <ul> | ||
* <li>Per-broker configs persisted at <tt>/configs/brokers/{brokerId}</tt>: These can be described/altered | ||
* using AdminClient using the resource name brokerId.</li> | ||
* <li>Cluster-wide defaults persisted at <tt>/configs/brokers/<default></tt>: These can be described/altered | ||
* using AdminClient using an empty resource name.</li> | ||
* </ul> | ||
* The order of precedence for broker configs is: | ||
* <ol> | ||
* <li>STATIC_BROKER_CONFIG: properties that broker is started up with, typically from server.properties file</li> | ||
|
@@ -391,7 +384,7 @@ class DynamicBrokerConfig(private val kafkaConfig: KafkaConfig) extends Logging | |
error(s"$errorMessage: $invalidPropNames") | ||
} | ||
} | ||
removeInvalidProps(nonDynamicConfigs(props), "Non-dynamic configs configured in ZooKeeper will be ignored") | ||
removeInvalidProps(nonDynamicConfigs(props), "Non-dynamic configs will be ignored") | ||
removeInvalidProps(securityConfigsWithoutListenerPrefix(props), | ||
"Security configs can be dynamically updated only using listener prefix, base configs will be ignored") | ||
if (!perBrokerConfig) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,15 +18,19 @@ | |
package kafka.server.metadata | ||
|
||
import kafka.network.ConnectionQuotas | ||
import kafka.server.ClientQuotaManager | ||
import kafka.server.ClientQuotaManager.BaseUserEntity | ||
import kafka.server.QuotaFactory.QuotaManagers | ||
import kafka.server.metadata.ClientQuotaMetadataManager.transferToClientQuotaEntity | ||
import kafka.utils.Logging | ||
import org.apache.kafka.common.metrics.Quota | ||
import org.apache.kafka.common.quota.ClientQuotaEntity | ||
import org.apache.kafka.common.utils.Sanitizer | ||
import org.apache.kafka.server.quota.ClientQuotaEntity.{ConfigEntity => ClientQuotaConfigEntity} | ||
|
||
import java.net.{InetAddress, UnknownHostException} | ||
import org.apache.kafka.image.{ClientQuotaDelta, ClientQuotasDelta} | ||
import org.apache.kafka.server.config.{QuotaConfig, ZooKeeperInternals} | ||
import org.apache.kafka.server.config.QuotaConfig | ||
|
||
import scala.jdk.OptionConverters.RichOptionalDouble | ||
|
||
|
@@ -145,27 +149,43 @@ class ClientQuotaMetadataManager(private[metadata] val quotaManagers: QuotaManag | |
} | ||
|
||
// Convert entity into Options with sanitized values for QuotaManagers | ||
val (sanitizedUser, sanitizedClientId) = quotaEntity match { | ||
case UserEntity(user) => (Some(Sanitizer.sanitize(user)), None) | ||
case DefaultUserEntity => (Some(ZooKeeperInternals.DEFAULT_STRING), None) | ||
case ClientIdEntity(clientId) => (None, Some(Sanitizer.sanitize(clientId))) | ||
case DefaultClientIdEntity => (None, Some(ZooKeeperInternals.DEFAULT_STRING)) | ||
case ExplicitUserExplicitClientIdEntity(user, clientId) => (Some(Sanitizer.sanitize(user)), Some(Sanitizer.sanitize(clientId))) | ||
case ExplicitUserDefaultClientIdEntity(user) => (Some(Sanitizer.sanitize(user)), Some(ZooKeeperInternals.DEFAULT_STRING)) | ||
case DefaultUserExplicitClientIdEntity(clientId) => (Some(ZooKeeperInternals.DEFAULT_STRING), Some(Sanitizer.sanitize(clientId))) | ||
case DefaultUserDefaultClientIdEntity => (Some(ZooKeeperInternals.DEFAULT_STRING), Some(ZooKeeperInternals.DEFAULT_STRING)) | ||
case IpEntity(_) | DefaultIpEntity => throw new IllegalStateException("Should not see IP quota entities here") | ||
} | ||
val (sanitizedUser, sanitizedClientId) = transferToClientQuotaEntity(quotaEntity) | ||
|
||
val quotaValue = newValue.map(new Quota(_, true)) | ||
try { | ||
manager.updateQuota( | ||
sanitizedUser = sanitizedUser, | ||
clientId = sanitizedClientId.map(Sanitizer.desanitize), | ||
sanitizedClientId = sanitizedClientId, | ||
quota = quotaValue) | ||
quota = quotaValue | ||
) | ||
} catch { | ||
case t: Throwable => error(s"Failed to update user-client quota $quotaEntity", t) | ||
} | ||
} | ||
} | ||
|
||
object ClientQuotaMetadataManager { | ||
|
||
def transferToClientQuotaEntity(quotaEntity: QuotaEntity): (Option[BaseUserEntity], Option[ClientQuotaConfigEntity]) = { | ||
val (sanitizedUser, sanitizedClientId) = quotaEntity match { | ||
case UserEntity(user) => | ||
(Some(ClientQuotaManager.UserEntity(Sanitizer.sanitize(user))), None) | ||
case DefaultUserEntity => | ||
(Some(ClientQuotaManager.DefaultUserEntity), None) | ||
case ClientIdEntity(clientId) => | ||
(None, Some(ClientQuotaManager.ClientIdEntity(Sanitizer.sanitize(clientId)))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to sanitize data in kraft mode? the zk path is gone so we don't need to align the data for zk anymore. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After under discussion, We should remove this |
||
case DefaultClientIdEntity => | ||
(None, Some(ClientQuotaManager.DefaultClientIdEntity)) | ||
case ExplicitUserExplicitClientIdEntity(user, clientId) => | ||
(Some(ClientQuotaManager.UserEntity(Sanitizer.sanitize(user))), Some(ClientQuotaManager.ClientIdEntity(Sanitizer.sanitize(clientId)))) | ||
case ExplicitUserDefaultClientIdEntity(user) => | ||
(Some(ClientQuotaManager.UserEntity(Sanitizer.sanitize(user))), Some(ClientQuotaManager.DefaultClientIdEntity)) | ||
case DefaultUserExplicitClientIdEntity(clientId) => | ||
(Some(ClientQuotaManager.DefaultUserEntity), Some(ClientQuotaManager.ClientIdEntity(Sanitizer.sanitize(clientId)))) | ||
case DefaultUserDefaultClientIdEntity => | ||
(Some(ClientQuotaManager.DefaultUserEntity), Some(ClientQuotaManager.DefaultClientIdEntity)) | ||
case IpEntity(_) | DefaultIpEntity => throw new IllegalStateException("Should not see IP quota entities here") | ||
} | ||
(sanitizedUser, sanitizedClientId) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in kraft, we don't use
<default>
so this check is weird. If this is used to fix test, then maybe we should revise the test