Skip to content

KAFKA-7609: Add Protocol Generator for Kafka#5893

Merged
hachikuji merged 14 commits into
apache:trunkfrom
cmccabe:KAFKA-7609
Jan 12, 2019
Merged

KAFKA-7609: Add Protocol Generator for Kafka#5893
hachikuji merged 14 commits into
apache:trunkfrom
cmccabe:KAFKA-7609

Conversation

@cmccabe

@cmccabe cmccabe commented Nov 8, 2018

Copy link
Copy Markdown
Contributor

No description provided.

@cmccabe

cmccabe commented Nov 8, 2018

Copy link
Copy Markdown
Contributor Author

Overview:

buildSrc/src
The message generator code is here. This code is automatically re-run by gradle when one of the schema files changes. The entire directory is processed at once to minimize the number of times we have to start a new JVM. We use Jackson to translate the JSON files into Java objects.

clients/src/main/java/org/apache/kafka/common/protocol/Message.java
This is the interface implemented by all automatically generated messages.

clients/src/main/java/org/apache/kafka/common/protocol/MessageUtil.java
Some utility functions used by the generated message code.

clients/src/main/java/org/apache/kafka/common/protocol/Readable.java, Writable.java, ByteBufferAccessor.java
The generated message code uses these classes for writing to a buffer.

clients/src/main/message/README.md
This README file explains how the JSON schemas work.

clients/src/main/message/*.json
The JSON files in this directory implement every supported version of every Kafka API. The unit tests automatically validate that the generated schemas match the hand-written schemas in our code. Additionally, there are some things like request and response headers that have schemas here.

clients/src/main/java/org/apache/kafka/common/utils/ImplicitLinkedHashSet.java
I added an optimization here for empty sets. This is useful here because I want all messages to start with empty sets by default prior to being loaded with data. This is similar to the "empty list" optimizations in the java.util.ArrayList class

core/src, connect/src, clients/src/main/java/org/apache/kafka/clients/admin, etc.
These changes are related to switching CreateTopicsRequest and CreateTopicsResponse to using the automatically generate classes rather than the hand-written ones.

Comment thread buildSrc/src/main/java/org/apache/kafka/message/JsonUtil.java Outdated
Comment thread clients/src/main/java/org/apache/kafka/common/protocol/types/Struct.java Outdated

@bob-barrett bob-barrett left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks good to me, Colin. I left a couple minor comments.

As a more general comment, I think we should include the docstrings in the schema files, so that we can eventually generate the protocol docs off of them. And if we're going to do that, we may actually want to be more precise about not changing field names from the current manual schemas, so that people don't get thrown off by changed names. I don't think either of these things necessarily has to happen in this patch, though.

Comment thread core/src/main/scala/kafka/server/KafkaApis.scala Outdated
Comment thread core/src/main/scala/kafka/server/KafkaApis.scala Outdated
Comment thread clients/src/main/java/org/apache/kafka/common/requests/CreateTopicsResponse.java Outdated
@cmccabe cmccabe force-pushed the KAFKA-7609 branch 2 times, most recently from 497b56e to 14d5e20 Compare November 24, 2018 19:16
@cmccabe cmccabe force-pushed the KAFKA-7609 branch 2 times, most recently from ad3de1a to 2aba254 Compare November 29, 2018 18:17
@cmccabe

cmccabe commented Nov 29, 2018

Copy link
Copy Markdown
Contributor Author

As a more general comment, I think we should include the docstrings in the schema files, so that we can eventually generate the protocol docs off of them.

That's a good point. I added documentation to all JSON schema files, based on the original Struct documentation. In cases where this documentation was missing, I created protocol documentation for the fields. I also ported over the comments explaining the purpose of each protocol version change.

And if we're going to do that, we may actually want to be more precise about not changing field names from the current manual schemas, so that people don't get thrown off by changed names. I don't think either of these things necessarily has to happen in this patch, though.

I think it's best to decide about field names when doing the conversion for each RPC.

Are we no longer concerned about duplicate topics? Is that handled during deserialization?

I fixed this so that we handle duplicates the same way we do now: by setting an INVALID_REQUEST error for them. This is actually specified in the KIP that added CreateTopics.

In order to make this possible, deserialization needs to be able to represent duplicate topics in the set. I handled this by using multisets instead of sets for data received over the wire. I think this is the correct approach in general because it's flexible and will allow us to covert over the existing code more easily. Throwing a deserialization error when encountering duplicates would be very unfriendly-- deserialization errors just result in the broker disconnecting without an error message.

To make this easier to review, I have split the CreateTopics changes off into a separate PR, #5972 . I think with that stuff split out, this is a very low-impact change since it's just adding a code generator, and not modifying any existing broker code path.

@bob-barrett bob-barrett left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks Colin! I agree about handling field names when we do the conversion. I added a couple more comments.

Comment thread clients/src/main/java/org/apache/kafka/common/utils/ImplicitLinkedHashSet.java Outdated
Comment thread buildSrc/src/main/java/org/apache/kafka/message/CodeBuffer.java Outdated
@cmccabe

cmccabe commented Dec 5, 2018

Copy link
Copy Markdown
Contributor Author
  • Rebase on trunk

  • Update message schemas for KIP-380

@cmccabe

cmccabe commented Dec 7, 2018

Copy link
Copy Markdown
Contributor Author

retest this please

@hachikuji hachikuji left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, this looks great! I left a few minor comments and some questions.

Comment thread buildSrc/src/main/java/org/apache/kafka/message/FieldSpec.java Outdated
Comment thread buildSrc/src/main/java/org/apache/kafka/message/HeaderGenerator.java Outdated
Comment thread buildSrc/src/main/java/org/apache/kafka/message/FieldType.java Outdated
Comment thread buildSrc/src/main/java/org/apache/kafka/message/CodeBuffer.java Outdated
Comment thread buildSrc/src/main/java/org/apache/kafka/message/CodeBuffer.java Outdated
Comment thread clients/src/main/resources/message/FetchResponse.json Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if there should be an enum type in this schema. Looking at the generated code, this field is just represented as a byte, but it would be nice to have something friendlier to work with. Otherwise we'll just create the enum manually and do some conversion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, enums would be a good addition. It's a bit trickier than it seems, though. For one thing, we already have a lot of public/stable enum types, and we can't break them. enums can't inherit from other classes, either, so that escape hatch is closed.

Perhaps a simple way to make it work would be generating simple constants

public static int NONE = 0;
public static int OFFSET_OUT_OF_RANGE = 1;
... etc. etc. ...

Then the existing public/stable enums could be retrofitted to grab the values from these declarations. We could also generate classes in cases where there was no existing public thing.

Comment thread buildSrc/src/main/java/org/apache/kafka/message/FieldSpec.java Outdated
@cmccabe cmccabe force-pushed the KAFKA-7609 branch 2 times, most recently from 47cb19c to b585449 Compare January 4, 2019 22:13
@cmccabe

cmccabe commented Jan 5, 2019

Copy link
Copy Markdown
Contributor Author

The test failure was kafka.api.CustomQuotaCallbackTest.testCustomQuotaCallback, which is unrelated.

@cmccabe

cmccabe commented Jan 5, 2019

Copy link
Copy Markdown
Contributor Author

retest this please

@hachikuji

Copy link
Copy Markdown
Contributor

Mentioned offline, but we need to move the few test cases under clients/src/test/java/org/apache/kafka/common/message/ to clients/src/test/java/org/apache/kafka/message/.

@hachikuji hachikuji left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A few more small comments.

this.ignorable = ignorable;
this.about = about == null ? "" : about;
if (!this.struct.fields().isEmpty()) {
if (!this.type.isArray()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Couldn't we make this check stricter? Would we ever expect the type to be something other than STRUCT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I refactored this code to clean it up a bit. Not all FieldSpecs describe arrays-- only some of them do. We should have a toStruct() method to make it convenient, but not embed a StructSpec unconditionally into the FieldSpec.

import java.util.Optional;

public interface FieldType {
static final String STRUCT_PREFIX = "[]";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: static final is redundant since this is an interface

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed, thanks

this.imports.add(newImport);
}

public void generate() throws Exception {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: Exception not raised. A bunch of these in MessageDataGenerator and MessageFactoryGenerator as well. Probably fall out from removing the throws on printf.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point. I was able to remove quite a few of these.

return true;
}

private boolean generateNonNullCheck(Versions prevVersions, FieldSpec field) throws Exception {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is unused.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed

/**
* Returns the API key of this message, or -1 if there is none.
*/
short apiKey();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's a little annoying that we have to expose this for all of the nested types. Have you considered having something like an ApiMessage which extends this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That is a fair point. I will separate out Message as a subtype of the ApiMessage interface.


public void registerMessageType(MessageSpec spec) {
if (spec.type() == MessageSpecType.REQUEST) {
if (requestApis.containsKey(spec.apiKey())) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is it worth having any validation that apiKey is not -1? I was even considering whether MessageSpec.apiKey could return Optional<Short>.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I can use Optional there.

}
this.fields = Collections.unmodifiableList(fields == null ?
Collections.emptyList() : new ArrayList<>(fields));
this.hasKeys = fields == null ? false : fields.stream().anyMatch(f -> f.mapKey());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: fields != null && fields.stream().anyMatch(f -> f.mapKey())

Comment thread buildSrc/src/main/java/org/apache/kafka/message/Versions.java
// limitations under the License.

{
"apiKey": -1,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why was this needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It shouldn't be needed-- I'll remove it.

In the long term, we'll want to generate other things besides ApiMessage from JSON files (enums, common structures, etc.) but we can hold off on that for now.

/**
* A Message which is part of the top-level Kafka API.
*/
public interface ApiMessage extends Message {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it make any sense to move the version fields into ApiMessage?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm... I think it makes sense to leave them in Message. All Messages have minimum and maximum supported versions, not just ApiMessages.

@hachikuji

Copy link
Copy Markdown
Contributor

retest this please

@hachikuji hachikuji left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @cmccabe, LGTM. This is a major improvement. I'll merge after the builds complete.

@cmccabe

cmccabe commented Jan 12, 2019

Copy link
Copy Markdown
Contributor Author

Failing test is kafka.api.UserQuotaTest.testThrottledProducerConsumer, which is definitely unrelated to this PR

@junrao

junrao commented Jan 22, 2019

Copy link
Copy Markdown
Contributor

@cmccabe : I could find clients/src/main/message/README.md mentioned in the PR. Does that still exist?

@cmccabe

cmccabe commented Jan 22, 2019

Copy link
Copy Markdown
Contributor Author

It was moved to ./clients/src/main/resources/common/message/README.md

"about": "The result offsets." },
{ "name": "Timestamp", "type": "int64", "versions": "1+" },
{ "name": "Offset", "type": "int64", "versions": "1+",
"about": "The timestamp associated with the returned offset." },

@junrao junrao Jan 25, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This comment is for Timestamp, not for Offset?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. I have filed a PR to fix this: #6214

@cmccabe cmccabe deleted the KAFKA-7609 branch May 20, 2019 19:04
pengxiaolong pushed a commit to pengxiaolong/kafka that referenced this pull request Jun 14, 2019
This patch adds a framework to automatically generate the request/response classes for Kafka's protocol. The code will be updated to use the generated classes in follow-up patches. Below is a brief summary of the included components:

**buildSrc/src**
The message generator code is here.  This code is automatically re-run by gradle when one of the schema files changes.  The entire directory is processed at once to minimize the number of times we have to start a new JVM.  We use Jackson to translate the JSON files into Java objects.

**clients/src/main/java/org/apache/kafka/common/protocol/Message.java**
This is the interface implemented by all automatically generated messages.

**clients/src/main/java/org/apache/kafka/common/protocol/MessageUtil.java**
Some utility functions used by the generated message code.

**clients/src/main/java/org/apache/kafka/common/protocol/Readable.java, Writable.java, ByteBufferAccessor.java**
The generated message code uses these classes for writing to a buffer.

**clients/src/main/message/README.md**
This README file explains how the JSON schemas work.

**clients/src/main/message/\*.json**
The JSON files in this directory implement every supported version of every Kafka API.  The unit tests automatically validate that the generated schemas match the hand-written schemas in our code.  Additionally, there are some things like request and response headers that have schemas here.

**clients/src/main/java/org/apache/kafka/common/utils/ImplicitLinkedHashSet.java**
I added an optimization here for empty sets.  This is useful here because I want all messages to start with empty sets by default prior to being loaded with data.  This is similar to the "empty list" optimizations in the `java.util.ArrayList` class.

Reviewers: Stanislav Kozlovski <stanislav_kozlovski@outlook.com>, Ismael Juma <ismael@juma.me.uk>, Bob Barrett <bob.barrett@outlook.com>, Jason Gustafson <jason@confluent.io>
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.

6 participants