Skip to content

Broker support for QuotaRecord and DescribeClientQuotas#477

Merged
Colin Patrick McCabe (cmccabe) merged 16 commits into
kip-500from
kip-500-quota-processor-2
Jan 29, 2021
Merged

Broker support for QuotaRecord and DescribeClientQuotas#477
Colin Patrick McCabe (cmccabe) merged 16 commits into
kip-500from
kip-500-quota-processor-2

Conversation

@mumrah

Copy link
Copy Markdown

This PR adds support for handling QuotaRecord metadata in the broker. It also includes a new QuotaCache class for efficient lookups of the quotas.

* Add broker metadata processor for QuotaRecord-s
* Add a QuotaCache for fast lookups
* Integration with KafkaApis
Comment thread clients/src/main/java/org/apache/kafka/common/protocol/ApiKeys.java Outdated
.setEntries(entriesData.asJava)))
} else {
val result = quotaCache.describeClientQuotas(describeClientQuotasRequest.filter())
val resultAsJava = new util.HashMap[ClientQuotaEntity, util.Map[String, java.lang.Double]](result.size)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These conversions are annoying, but I don't know how to avoid them (unless we just use Java classes in the QuotaCache code)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a hot path, so I personally don't see a real need to change the QuotaCache class -- though others may differ.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread core/src/main/scala/kafka/server/LegacyBroker.scala Outdated
.setEntries(entriesData.asJava)))
} else {
val result = quotaCache.describeClientQuotas(describeClientQuotasRequest.filter())
val resultAsJava = new util.HashMap[ClientQuotaEntity, util.Map[String, java.lang.Double]](result.size)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread core/src/main/scala/kafka/server/metadata/QuotaMetadataProcessor.scala Outdated
Comment thread core/src/main/scala/kafka/server/metadata/QuotaMetadataProcessor.scala Outdated
case IpEntity(ip) => try {
Some(InetAddress.getByName(ip))
} catch {
case _: UnknownHostException => throw new IllegalArgumentException(s"Unable to resolve address $ip")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread core/src/main/scala/kafka/server/metadata/QuotaMetadataProcessor.scala Outdated
Comment on lines +93 to +94
// TODO return empty or throw exception here?
return Map.empty

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this code execute if a client tries to describe everything -- and is that legal to do? I'm not sure.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ERROR level since this should never happen?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. Some high-level comments.

Comment thread core/src/main/scala/kafka/server/metadata/QuotaCache.scala Outdated
Comment thread core/src/main/scala/kafka/server/metadata/QuotaCache.scala Outdated
case DefaultIpEntity =>
updateCacheIndexPartial(ipEntityIndex, DefaultIp)
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
}

Comment thread core/src/main/scala/kafka/server/metadata/QuotaMetadataProcessor.scala Outdated
Comment thread core/src/main/scala/kafka/server/Kip500Broker.scala
Comment thread core/src/main/scala/kafka/server/metadata/QuotaCache.scala
Comment thread core/src/main/scala/kafka/server/metadata/QuotaMetadataProcessor.scala Outdated
Comment thread core/src/main/scala/kafka/server/metadata/QuotaMetadataProcessor.scala Outdated
…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
@cmccabe

Copy link
Copy Markdown

LGTM

@cmccabe

Copy link
Copy Markdown

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants