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 a "create topics" action #297

Merged
merged 14 commits into from
Oct 15, 2018

Conversation

jbruggem
Copy link
Collaborator

@jbruggem jbruggem commented Jul 18, 2018

TODO:

A new server version was added because CreateTopics was added in 0.10.1 (see also the release notes).

Closes #168

@sourcelevel-bot
Copy link

Hello, @jbruggem! This is your first Pull Request that will be reviewed by Ebert, an automatic Code Review service. It will leave comments on this diff with potential issues and style violations found in the code as you push new commits. You can also see all the issues found on this Pull Request on its review page. Please check our documentation for more information.

@joshuawscott
Copy link
Member

@jbruggem are you ready for review on this yet? Feel free to ping me when you are.

@jbruggem
Copy link
Collaborator Author

jbruggem commented Oct 3, 2018

Thanks for offering!

It's actually probably relevant to get a first review. There's probably already a lot of stuff you can point out that will help push this through.

@jbruggem
Copy link
Collaborator Author

jbruggem commented Oct 3, 2018

I should probably rebase first, though ?

@joshuawscott
Copy link
Member

Rebasing would be ideal, yes. I can go ahead and look it over once the conflict is resolved.

Thanks!

@jbruggem
Copy link
Collaborator Author

jbruggem commented Oct 3, 2018

I've rebased it.

I'm not sure if I've managed to follow the spirit of the existing code correctly in all circumstances. In particular, I had to add the notion of API version in the parsing of metadata and it felt like I didn't do it cleanly enough.

thanks again for the review.

Copy link
Member

@bjhaid bjhaid left a comment

Choose a reason for hiding this comment

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

Thanks for this! Can you confirm if 0.9.0 supports create topic if it does then you probably don't need a new server type, also you need to add something like this:

def kafka_server_offset_fetch(_, _state), do: raise "Offset Fetch is not supported in 0.8.0 version of kafka"

to all the servers not supporting create_topic, so a user gets a useful error message if they try using it for a wrongly configured server

lib/kafka_ex/config.ex Outdated Show resolved Hide resolved

def update_metadata(state, api_version) do
{correlation_id, metadata} = retrieve_metadata_with_version(state.brokers, state.correlation_id, config_sync_timeout(), api_version)
metadata_brokers = metadata.brokers |> Enum.map(&(%{&1 | is_controller: &1.node_id == metadata.controller_id}))
Copy link
Member

Choose a reason for hiding this comment

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

is this change necessary for you PR can you indicate why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We had to add the api_version argument to functions handling metadata because in the Metadata API response object version 0 does not provide the controller_id value, which is needed to create a topic.

Therefore, it was necessary to allow support for the version 1 of the Metadata API, hence the need to specify the version in the function call.

@jbruggem
Copy link
Collaborator Author

jbruggem commented Oct 3, 2018

@bjhaid

Can you confirm if 0.9.0 supports create topic if it does then you probably don't need a new server type

A new server version was added because CreateTopics exists only since 0.10.1 (see also the release notes).

add something like this [...] to all the servers not supporting create_topic,

I already did that, but maybe not the way I should have ?

@bjhaid
Copy link
Member

bjhaid commented Oct 3, 2018

I already did that, but maybe not the way I should have ?

Sorry my bad, you did the right thing!

@@ -2,6 +2,8 @@ defmodule KafkaEx.Protocol.Metadata do
alias KafkaEx.Protocol
import KafkaEx.Protocol.Common

@default_api_version 0
Copy link
Member

Choose a reason for hiding this comment

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

After your previous comment wondering if we should create a new metadata_v1 module? I have not thought deeply about this just throwing it out as I imagine the more versions of things we support to more clunky the constructing request and parsing of response may become...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it's a relevant question. That's indeed the kind of question that require a deep understanding of this project, which I'm not sure I have yet.

Especially since 0.10.0.0, the client is supposed to ask the broker which API versions are supported (see "Retrieving Supported API versions"). The "broker returns its full list of supported ApiKeys and versions", meaning KafkaEx should use that information to decide which API message and version to use (and allow)

This means that for version >= 0.10.0.0, KafkaEx's behaviour should be quite different than the current hard-coded mapping of versions -> code.

Copy link
Member

Choose a reason for hiding this comment

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

I'll leave the decision on how to proceed to @joshuawscott and @dantswain both of whom have been more involved in the project more recently than I have, however I think we should have different version modules and after looking up the supported api version either reject the api-version provided by the user or pick the newest one we have a module for and use the module... last time I looked I think there are a couple of versions of the requests that will require some amount of work to implement them, we need as much help as we can get :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm wondering if maybe this discussion should happen in another issue, so that we can move forward with this merge request. It seems the scale is much bigger than just adding support for a new message in the protocol. I'd be happy to help, of course !

Would that be OK ?

Copy link
Member

Choose a reason for hiding this comment

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

@dantswain @joshuawscott what do yall think?

Copy link
Member

Choose a reason for hiding this comment

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

So, it wouldn't be accurate to have a metadata_v1 module, since the API version is per individual API message. For example, CreateTopics could be v3, but the same server could accept SyncGroup v2. Each of the message types needs to be able to generate messages based on the API version(s) that the broker indicates are available.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@joshuawscott agreed.

@jbruggem
Copy link
Collaborator Author

jbruggem commented Oct 8, 2018

All tests pass locally (142 tests, 0 failures -- I've run them many times), but not on the CI. I suppose there are race conditions which become visible in that context. I tried to increase the number of retries, to no avail.

@jbruggem jbruggem changed the title Add a "create topics" action (work in progress) Add a "create topics" action Oct 8, 2018
@sourcelevel-bot
Copy link

Ebert has finished reviewing this Pull Request and has found:

  • 3 possible new issues (including those that may have been commented here).

You can see more details about this review at https://ebertapp.io/github/kafkaex/kafka_ex/pulls/297.

@joshuawscott
Copy link
Member

I restarted the two tests that failed (elixir 1.1 & elixir 1.5)

@jbruggem
Copy link
Collaborator Author

jbruggem commented Oct 9, 2018

Thanks !

Copy link
Member

@joshuawscott joshuawscott left a comment

Choose a reason for hiding this comment

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

I left some comments, but I did not get all the way through the PR unfortunately. I might not be able to touch this for a couple of days, but feel free to disagree with any of the points I raised.
Cheers!

.travis.yml Outdated Show resolved Hide resolved
lib/kafka_ex/protocol/create_topics.ex Outdated Show resolved Hide resolved
end

@spec encode_nullable_string(String.t) :: binary
defp encode_nullable_string(text) do
Copy link
Member

Choose a reason for hiding this comment

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

nullable string could be a nil, but the spec doesn't allow that. This function could use pattern matching instead of an if as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nullable string could be a nil, but the spec doesn't allow that.

So I should leave it as it is ?

This function could use pattern matching instead of an if as well.

I'll replace the if with pattern matching.

Copy link
Member

Choose a reason for hiding this comment

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

by 'spec' I meant the typespec of the function: encode_nullable_string should take nil | String.t

@@ -2,6 +2,8 @@ defmodule KafkaEx.Protocol.Metadata do
alias KafkaEx.Protocol
import KafkaEx.Protocol.Common

@default_api_version 0
Copy link
Member

Choose a reason for hiding this comment

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

So, it wouldn't be accurate to have a metadata_v1 module, since the API version is per individual API message. For example, CreateTopics could be v3, but the same server could accept SyncGroup v2. Each of the message types needs to be able to generate messages based on the API version(s) that the broker indicates are available.

@@ -93,18 +97,44 @@ defmodule KafkaEx.Protocol.Metadata do
}
end

def create_request(correlation_id, client_id, ""), do: KafkaEx.Protocol.create_request(:metadata, correlation_id, client_id) <> << 0 :: 32-signed >>
def valid_api_version(v) do
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure that we need this function, since we could just have the api_version arguments default to @default_api_version

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok, I'll fix that

lib/kafka_ex/protocol/produce.ex Outdated Show resolved Hide resolved
@@ -113,13 +143,55 @@ defmodule KafkaEx.Protocol.Metadata do
parse_brokers(brokers_size - 1, rest, [%Broker{node_id: node_id, host: host, port: port} | brokers])
end

defp parse_brokers_v1(0, rest, brokers), do: {brokers, rest}
Copy link
Member

Choose a reason for hiding this comment

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

Attaching version numbers to the functions will become cumbersome, I think. Many of the APIs have several versions, but those version might not have any differences, which might lend itself better to having one a function parameter that is the API version.

For example, Metadata Response has no differences in the brokers field from versions 1-6.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Indeed. I think it's going to become cumbersome either way, to be honest :).

I've switched to using a function parameter instead. Tell me what you think !

lib/kafka_ex/server.ex Show resolved Hide resolved
@@ -378,17 +396,27 @@ defmodule KafkaEx.Server do

# credo:disable-for-next-line Credo.Check.Refactor.FunctionArity
def retrieve_metadata(brokers, correlation_id, sync_timeout, topic \\ []), do: retrieve_metadata(brokers, correlation_id, sync_timeout, topic, @retry_count, 0)

# credo:disable-for-next-line Credo.Check.Refactor.FunctionArity
def retrieve_metadata_with_version(brokers, correlation_id, sync_timeout, api_version, topic \\ []), do: retrieve_metadata(brokers, correlation_id, sync_timeout, topic, @retry_count, api_version, 0)
Copy link
Member

Choose a reason for hiding this comment

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

We should just add api_version as the last argument to retrieve_metadata and default it to @default_api_version (0). No need to add another function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done.

# credo:disable-for-next-line Credo.Check.Refactor.FunctionArity
def retrieve_metadata(_, correlation_id, _sync_timeout, topic, 0, error_code) do
Logger.log(:error, "Metadata request for topic #{inspect topic} failed with error_code #{inspect error_code}")
{correlation_id, %Metadata.Response{}}
end

# credo:disable-for-next-line Credo.Check.Refactor.FunctionArity
def retrieve_metadata(brokers, correlation_id, sync_timeout, topic, retry, error_code) do
Copy link
Member

Choose a reason for hiding this comment

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

this could be removed by using a default parameter as suggested above

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done.

@jbruggem
Copy link
Collaborator Author

jbruggem commented Oct 10, 2018

Ok, I addressed all comments the best I could. Tests pass locally (not always on Travis).

Copy link
Member

@joshuawscott joshuawscott left a comment

Choose a reason for hiding this comment

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

Just a couple more minor things that I see, and then it looks good

Thanks so much for taking this on!


resp = KafkaEx.create_topics([request], timeout: 2000)
# error = TOPIC_ALREADY_EXISTS
assert {36, name} == parse_create_topic_resp(resp)
Copy link
Member

Choose a reason for hiding this comment

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

We should be able to add 36 to the @error_map in lib/kafka_ex/protocol.ex and then refer to this as the error code (:topic_already_exists) rather than 36

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea, thanks ! I've added a few error codes, might as well :).

lib/kafka_ex/protocol/metadata.ex Outdated Show resolved Hide resolved
@jbruggem
Copy link
Collaborator Author

OK, I think this is now ready for merge !

@joshuawscott
Copy link
Member

I'm going to wait until the end of the day to let @bjhaid or @dantswain weigh in if they wish, then I'll merge.

Thanks again!

@jbruggem
Copy link
Collaborator Author

Thanks again!

My pleasure! We were happy to be able to contribute to a nice open-source project, and it was very useful for us to have this feature implemented. I'm open to the idea of further contributions of course.

@joshuawscott joshuawscott merged commit 10699a4 into kafkaex:master Oct 15, 2018
@jbruggem jbruggem deleted the add_create_topic_action branch October 15, 2018 14:32
@jbruggem
Copy link
Collaborator Author

awesome 🎉. Thanks for all your help !

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.

3 participants