Skip to content
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

Add Kafka quota enforcer #53

Merged

Conversation

danielfritsch
Copy link
Contributor

Enables programmatic configuration of Kafka quotas to restrict monopolization of Kafka broker resources caused by bad acting clients.

https://kafka.apache.org/documentation/#design_quotas

Copy link
Collaborator

@shrijeet-tesla shrijeet-tesla left a comment

Choose a reason for hiding this comment

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

Looking great, made some minor comments.

Time.SYSTEM,
"kafka.server", // default used in the client
"SessionExpireListener" // default used in the client
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

A general comment, its a good practice to declare the variable as final as much as possible in many cases it doesn't have an obvious benefit, but in multi-threaded code, it can protect you from bugs


@Override
protected Enforcer<ConfiguredQuota> initEnforcer() {
Map<String, Object> zookeeperConfig = zookeeperConfig();
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: rename this variable, it conflicts with the method name

this.adminClient = adminClient;
}

enum Type {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you add some docs here explaining what each of these mean?

Copy link
Collaborator

@shrijeet-tesla shrijeet-tesla left a comment

Choose a reason for hiding this comment

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

+1. Get Jesse's 👀 too.

Copy link
Contributor

@jyates jyates left a comment

Choose a reason for hiding this comment

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

Some nits and testing improvements, but overall looks great!

bazel build //kafka_quota_enforcer/src/main/java/com/tesla/data/quota/enforcer:Main_deploy.jar
```

### Verify
Copy link
Contributor

Choose a reason for hiding this comment

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

Its not clear what to expect from these commands


* A quota applies to a cluster if that cluster is present under quota's `clusters`
attribute. If no changes to base config are desired, specify empty dict `{}`
* Any quota value can be overridden. Quota 'principal' or 'client' properties should not be overridden.
Copy link
Contributor

Choose a reason for hiding this comment

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

i would expect the enforcer to fail in that case (and thus the comment would read "If the 'principal' or 'client' properties are overridden, enforcing will fail")... but i assume this is not possible? In which case how about "'principal' or 'client' are not recommended to be overridden.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, don't think it's currently supported to restrict specific fields from being overridden. Will add in "not recommended"

* Map (dictionary) properties are merged in a way that preserves base settings


Note: `quotasFile` is supported for multi-cluster configuration too.
Copy link
Contributor

Choose a reason for hiding this comment

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

Note: quotasFile supports multi-cluster configuration as well.

You can read more about it here: https://kafka.apache.org/documentation/#design_quotas

To specify a default quota, simply use `<default>` as the value in the quota's `principal` or `client` config fields.
For example, to set a default quota for clients groups that have unique principals, here's a sample `quotasFile.yaml`:
Copy link
Contributor

Choose a reason for hiding this comment

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

For example, to set the default quota for each unique principal - regardless of the client - you could use:

public void setup() {
quotaService = mock(QuotaService.class);
enforcer = new QuotaEnforcer(emptyList(), quotaService, true, true);
quotas = Arrays.asList(
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just make static, final and set above?

configuration file is expected
```

## Configuration
Copy link
Contributor

Choose a reason for hiding this comment

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

There is no discussion of precedence of quotas for client/principal combinations, or at least a pointer to the kafka docs describing the hierarchy. Can lead to unexpected outcomes and/or confusion for users

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks, forgot about that

* cluster default
* default

# Setting 'default' quotas
Copy link
Contributor

Choose a reason for hiding this comment

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

How about calling a "low water mark"... really anything besides default :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed to "entity-default" - it seems that referencing 'default' in the title is important as that's what users will be looking for in the docs, and "entity-default" would help to relate that back to the kafka-configs command usage

Integer requestPercentage = quota.get(REQUEST_PERCENTAGE) != null ?
Integer.parseInt(quota.getProperty(REQUEST_PERCENTAGE)) : null;

switch (quotaType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

isn't this an entityType?

Copy link
Contributor

Choose a reason for hiding this comment

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

No change?

this.adminClient = adminClient;
}

enum Type {
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't EntityType more closely describe what 'type' means here?

}

@Override
public boolean equals(Object o) {
Copy link
Contributor

Choose a reason for hiding this comment

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

minor style nit: generally equals/hashcode/toString are at the bottom of the class

Copy link
Contributor

Choose a reason for hiding this comment

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

generally hashcode goes next to equals - they should be logically the same

Copy link
Contributor

@jyates jyates left a comment

Choose a reason for hiding this comment

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

Some more nits, but lgtm otherwise

}

@Override
public boolean equals(Object o) {
Copy link
Contributor

Choose a reason for hiding this comment

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

generally hashcode goes next to equals - they should be logically the same


enum Type {
// quota applied solely on a per principal/user basis, ex: principal=user1
USER,
Copy link
Contributor

Choose a reason for hiding this comment

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

Everything in ZK and Kafka says principal - it is strictly different than a 'user' https://stackoverflow.com/questions/4989063/what-is-the-meaning-and-difference-between-subject-user-and-principal

Integer requestPercentage = quota.get(REQUEST_PERCENTAGE) != null ?
Integer.parseInt(quota.getProperty(REQUEST_PERCENTAGE)) : null;

switch (quotaType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

No change?

case CLIENT:
return new ConfiguredQuota(null, quotaEntry.getKey(), producerByteRate, consumerByteRate, requestPercentage);
case COMBINATION:
// when both user + client are configured, the full name looks like {configuredUser}/clients/{configuredClient}
Copy link
Contributor

Choose a reason for hiding this comment

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

You can name the groups as well and then reference them by name, for instance "(<principal>.*)/clients/(<client>.*)" then you can do new ConfiguredQuota(matcher.group("principal"), matcher.group("client") which is very explicit

public void testCreate() {
QuotaEnforcer enforcer = new QuotaEnforcer(emptyList(), quotaService, true, false);
enforcer.create(quotas);
verify(quotaService, times(1)).create(quotas);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: times(1) is the default with verify(), you can just drop the times parameter

… quotas to restrict monopolization of Kafka broker resources caused by bad acting clients.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants