Broker support for QuotaRecord and DescribeClientQuotas#477
Conversation
* Add broker metadata processor for QuotaRecord-s * Add a QuotaCache for fast lookups * Integration with KafkaApis
| .setEntries(entriesData.asJava))) | ||
| } else { | ||
| val result = quotaCache.describeClientQuotas(describeClientQuotasRequest.filter()) | ||
| val resultAsJava = new util.HashMap[ClientQuotaEntity, util.Map[String, java.lang.Double]](result.size) |
There was a problem hiding this comment.
These conversions are annoying, but I don't know how to avoid them (unless we just use Java classes in the QuotaCache code)
There was a problem hiding this comment.
It's not a hot path, so I personally don't see a real need to change the QuotaCache class -- though others may differ.
Ron Dagostino (rondagostino)
left a comment
There was a problem hiding this comment.
LGTM. Nothing major, just a few comments.
BTW, I'm seeing a spotbugs error on SocketServer.java:1458 (need to remove the trailing intValue() from QuotaConfigs.IP_CONNECTION_RATE_DEFAULT.intValue())
| .setThrottleTimeMs(requestThrottleMs) | ||
| .setEntries(entriesData.asJava))) | ||
| } else { | ||
| val result = quotaCache.describeClientQuotas(describeClientQuotasRequest.filter()) |
There was a problem hiding this comment.
I think we need an authorization check here. The above authorization check only occurs if we are in legacy mode. Maybe perform a single authorization check at the top before branching based on adminManager being null or not?
There was a problem hiding this comment.
It also occurs to me that we need tests for these KIP-500 states (this comment is a general comment, so probably unnecessary to add the test now -- just something we'll have to do before opening a PR to the apache repo)
There was a problem hiding this comment.
Yea, thanks I missed the authz thing. Will fix.
What kind of tests are you thinking? I think KafkaApis is mainly tested through integration/system tests.
There was a problem hiding this comment.
I could see a unit test that confirms unauthorized requests are denied (one version for legacy, one version for KIP-500). I could also see a KIP-500 unit test that confirms a mock QuotaCache is asked to handle the request. Then as long as QuotaCache is tested that should be enough.
| kafkaController, forwardingManager, zkClient, config.brokerId, config, metadataCache, metrics, authorizer, quotaManagers, | ||
| fetchManager, brokerTopicStats, _clusterId, time, tokenManager, brokerFeatures, featureCache, null) | ||
| fetchManager, brokerTopicStats, _clusterId, time, tokenManager, brokerFeatures, featureCache, null, | ||
| new QuotaCache()) |
There was a problem hiding this comment.
Should this be null since it isn't used in the legacy case? Not sure if we should change the parameter to be an Option[] and default it to None so we don't have to pass it here? Although we don't use Option[] for brokerMetadataListener and we pass in null here for that, so probably not.
There was a problem hiding this comment.
Yea we could use an Option here instead of an empty cache that never gets used. I'll change that.
As an aside: it's going to get pretty messy in this class as we add members which are optional (or nullable). Maybe we should try to extract an interface from LegacyAdminManager and use that rather than adding more members to KafkaApis for kip-500 functionality.
There was a problem hiding this comment.
I think at some point the ZooKeeper-related stuff will be removed -- that might be a good time to try to trim things down/clean it up a bit?
| .setEntries(entriesData.asJava))) | ||
| } else { | ||
| val result = quotaCache.describeClientQuotas(describeClientQuotasRequest.filter()) | ||
| val resultAsJava = new util.HashMap[ClientQuotaEntity, util.Map[String, java.lang.Double]](result.size) |
There was a problem hiding this comment.
It's not a hot path, so I personally don't see a real need to change the QuotaCache class -- though others may differ.
| private val replicaQuotaManager: ReplicationQuotaManager = EasyMock.createNiceMock(classOf[ReplicationQuotaManager]) | ||
| private val quotas = QuotaManagers(clientQuotaManager, clientQuotaManager, clientRequestQuotaManager, | ||
| clientControllerQuotaManager, replicaQuotaManager, replicaQuotaManager, replicaQuotaManager, None) | ||
| private val quotaCache = new QuotaCache() |
There was a problem hiding this comment.
Interestingly, it appears we don't have a test for handleDescribeClientQuotasRequest() at all. Just noting the fact -- nothing to really do unless you want to tackle adding one as part of this PR.
| case IpEntity(ip) => try { | ||
| Some(InetAddress.getByName(ip)) | ||
| } catch { | ||
| case _: UnknownHostException => throw new IllegalArgumentException(s"Unable to resolve address $ip") |
There was a problem hiding this comment.
We warn about unknown types/keys elsewhere, so maybe we should warn in this case as well? Otherwise nothing else will get processed from this message.
There was a problem hiding this comment.
Hmm, yea. It's actually a bit dubious to try and resolve the IP when the quota is set. What if there is a network issue at that moment? We'll miss the record and never update the quota manager.
| // TODO return empty or throw exception here? | ||
| return Map.empty |
There was a problem hiding this comment.
Does this code execute if a client tries to describe everything -- and is that legal to do? I'm not sure.
There was a problem hiding this comment.
The kafka-configs.sh script requires at least one entity type filter, so we can't reach this code via the CLI. However, a client could send an RPC message with no filters. Looks like the current ZK logic prohibits a describe without at least one entity type filter, so we'll do the same.
…a-processor-2 Conflicts: core/src/main/scala/kafka/server/Kip500Broker.scala
Ron Dagostino (rondagostino)
left a comment
There was a problem hiding this comment.
LGTM except for the outstanding question about throw new IllegalArgumentException(s"Unable to resolve address $ip") when processing an IP quota record. Maybe Colin Patrick McCabe (@cmccabe) has some thoughts on this.
| DescribeClientQuotasResponse.fromQuotaEntities(resultAsJava, requestThrottleMs) | ||
| ) | ||
| } else { | ||
| warn("Neither LegacyAdminManager nor QuotaCache were defined") |
There was a problem hiding this comment.
ERROR level since this should never happen?
José Armando García Sancio (jsancio)
left a comment
There was a problem hiding this comment.
Thanks for the PR. Some high-level comments.
| case DefaultIpEntity => | ||
| updateCacheIndexPartial(ipEntityIndex, DefaultIp) | ||
| } | ||
| } |
There was a problem hiding this comment.
Hmm. I think you can simplify this code if you instead have a function convert that knows how to convert a QuotaEntity to a List[(QuotaCacheIndex, CacheIndexKey)]. With this you can do something like:
convert(entity).foreach { (quotaCacheIndex, cacheIndexKey) =>
updateCacheIndex(entity, removeFromIndex, quotaCacheIndex, cacheIndexKey)
}…a-processor-2 Conflicts: core/src/main/scala/kafka/server/KafkaApis.scala core/src/main/scala/kafka/server/Kip500Broker.scala core/src/main/scala/kafka/server/LegacyBroker.scala core/src/main/scala/kafka/server/metadata/BrokerMetadataListener.scala core/src/test/scala/integration/kafka/server/Kip500ClusterTest.scala core/src/test/scala/unit/kafka/server/KafkaApisTest.scala
|
LGTM |
|
Like we discussed outside github, it's kind of unfortunate that we have several internal Scala types for client quotas. A public Java API type + a single internal Scala type should be enough, and that would cut down on the translations. But we should probably fix that later once 2.8 is out. Thanks, David Arthur (@mumrah) ! I'll commit once the conflict is fixed |
This PR adds support for handling QuotaRecord metadata in the broker. It also includes a new QuotaCache class for efficient lookups of the quotas.