KAFKA-13888: Addition of Information in DescribeQuorumResponse about Voter Lag#12206
Conversation
| } | ||
|
|
||
| /** | ||
| * Describe the state of the raft quorum |
There was a problem hiding this comment.
How about "metadata quorum" or "kraft quorum"? Also nit: missing period.
| observers.add(new ReplicaState(entry.getKey(), entry.getValue())); | ||
| } | ||
| QuorumInfo info = new QuorumInfo(topicName, leaderId, voters, observers); | ||
| return info; |
There was a problem hiding this comment.
nit: this looks misaligned
| private final long lastFetchTimeMs; | ||
| private final long lastCaughtUpTimeMs; | ||
|
|
||
| public ReplicaState(int replicaId, long logEndOffset) { |
There was a problem hiding this comment.
Do we need to expose this?
There was a problem hiding this comment.
Sorry, I did not get what you meant. We need access to it in KafkaAdminClient.java. Are you suggesting it be protected? or have a builder instead of exposing the constructor? Or building it through the QuorumInfo itself?
| return logEndOffset; | ||
| } | ||
|
|
||
| public long lastFetchTimeMs() { |
There was a problem hiding this comment.
Can we document the result for older versions of DescribeQuorum? I am tempted to suggest that we change the type to OptionalLong to make it clear that the value may be absent for older versions.
There was a problem hiding this comment.
OptionalLong makes sense. Let me make that change.
| /** | ||
| * The name of the internal raft metadata topic | ||
| */ | ||
| private static final String METADATA_TOPIC_NAME = "__cluster_metadata"; |
There was a problem hiding this comment.
Hmm, I hadn't really been thinking about the fact that we would have to expose this to the client. I guess that is the consequence of having such a general DescribeQuorum API. This makes me wonder if we ought to be more forward looking with the naming here. Suppose that we ultimately decide to use raft for partition replication as well. Then we might want to be able to use DescribeQuorum for user partitions as well, but we haven't given ourselves a lot of room for extension in the describeQuorum API. Would it make sense to make the new API more specific to the metadata quorum?
public DescribeMetadataQuorumResult describeMetadataQuorum(DescribeMetadataQuorumOptions options)It is more verbose, but it is also clearer.
We should also move this constant to org.apache.kafka.common.internals.Topic.
There was a problem hiding this comment.
This is a great point! I think it makes sense to give ourselves wiggle room and call this MetadataQuorum.
Re: moving the constant -- I was actually looking for a common place to put it so that both the clients and the server code can use the same value. Is there any class that they share? Right now the name for the topic on the server side is defined in KafkaRaftServer.
| /** | ||
| * This is used to describe per-partition state in the DescribeQuorumResponse. | ||
| */ | ||
| public class QuorumInfo { |
There was a problem hiding this comment.
Would it make sense to implement equals and hashCode? That is often useful for testing. Also it would be nice to have a good toString implementations.
|
|
||
| @Timeout(120) | ||
| @Tag("integration") | ||
| class DescribeQuorumTest { |
There was a problem hiding this comment.
We have another class DescribeQuorumRequesetTest. Would it make sense to move this test there? Perhaps we could generalize it DescribeQuorumIntegrationTest or something like that.
| if (quorumResponse.data().errorCode() == Errors.NONE.code()) { | ||
| future.complete(createQuorumResult(quorumResponse)); | ||
| } else { | ||
| future.completeExceptionally(Errors.forCode(quorumResponse.data().errorCode()).exception()); |
There was a problem hiding this comment.
It would be helpful to have a couple unit tests in KafkaAdminClientTest covering failure and success cases.
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| public static String nodesToVoterConnections(List<Node> nodes) { |
There was a problem hiding this comment.
Seems like we're not using this anywhere. Included by mistake perhaps?
There was a problem hiding this comment.
It was included by mistake.. thanks for pointing out.
| * @param options The options to use when describing the quorum. | ||
| * @return The DescribeQuorumResult. | ||
| */ | ||
| DescribeQuorumResult describeQuorum(DescribeQuorumOptions options); |
There was a problem hiding this comment.
By the way, usually these APIs have a default option which leaves out the XOptions class. For example, see describeFeatures() above?
Also nit: can we not split up the describeFeatures APIs?
|
cc @dengziming for reviews |
|
Published another revision addressing @hachikuji's comments. |
dajac
left a comment
There was a problem hiding this comment.
@niket-goel Thanks for the PR. I left a few comments.
|
|
||
| private final KafkaFuture<QuorumInfo> quorumInfo; | ||
|
|
||
| public DescribeMetadataQuorumResult(KafkaFuture<QuorumInfo> quorumInfo) { |
There was a problem hiding this comment.
nit: Could we keep it package private?
| String topicName = response.getTopicNameByIndex(partition); | ||
| Integer leaderId = response.getPartitionLeaderId(topicName, partition); | ||
| List<ReplicaState> voters = new ArrayList<>(); | ||
| for (Map.Entry<Integer, Long> entry: response.getVoterOffsets(topicName, partition).entrySet()) { |
There was a problem hiding this comment.
nit: We could use response.getVoterOffsets(topicName, partition).forEach((replicaId, logEndOffset) ->. That makes the code a bit more readable in my opinion.
| Integer leaderId = response.getPartitionLeaderId(topicName, partition); | ||
| List<ReplicaState> voters = new ArrayList<>(); | ||
| for (Map.Entry<Integer, Long> entry: response.getVoterOffsets(topicName, partition).entrySet()) { | ||
| voters.add(new ReplicaState(entry.getKey(), entry.getValue(), OptionalLong.empty(), OptionalLong.empty())); |
There was a problem hiding this comment.
hm.. OptionalLong.empty() seems wrong here. Don't you need to put the last fetch time and the last caughtup time?
| } | ||
| List<ReplicaState> observers = new ArrayList<>(); | ||
| for (Map.Entry<Integer, Long> entry: response.getObserverOffsets(topicName, partition).entrySet()) { | ||
| observers.add(new ReplicaState(entry.getKey(), entry.getValue(), OptionalLong.empty(), OptionalLong.empty())); |
|
|
||
| @Override | ||
| void handleFailure(Throwable throwable) { | ||
| completeAllExceptionally(Collections.singletonList(future), throwable); |
There was a problem hiding this comment.
nit: Why not directly calling future.completeExceptionally?
| } | ||
| return new DescribeQuorumResponse( | ||
| new DescribeQuorumResponseData() | ||
| .setErrorCode(error.code())); |
| 0, 0, 0, | ||
| singletonList(new DescribeQuorumResponseData.ReplicaState()), | ||
| singletonList(new DescribeQuorumResponseData.ReplicaState())) |
There was a problem hiding this comment.
It would be better to fully populate the response here. That would have caught the issue that I mentioned earlier.
| final ExecutionException e = assertThrows(ExecutionException.class, future::get); | ||
| assertEquals(e.getCause().getClass(), Errors.INVALID_REQUEST.exception().getClass()); |
There was a problem hiding this comment.
nit: You could use TestUtils.assertFutureThrows.
| } | ||
|
|
||
| @ClusterTest | ||
| def testDescribeQuorumRequestToBrokers() = { |
There was a problem hiding this comment.
Looking at the other tests, this test suite does not seem to be the right place for this test. I wonder if KRaftClusterTest would be more appropriate for instance but I don't feel strong about this.
There was a problem hiding this comment.
I actually had this test in a separate suite akin to KRaftClusterTest before, but moved it here because Jason pointed out that a test file for this API already existed. I think I am going to move the test to where you suggested. It does make more sense to me there.
| val log = LoggerFactory.getLogger(classOf[DescribeQuorumIntegrationTest]) | ||
|
|
||
| @ClusterTest(clusterType = Type.ZK) | ||
| def testDescribeQuorumNotSupportedByZkBrokers(): Unit = { |
There was a problem hiding this comment.
Should we extend testDescribeQuorum to verify that new fields are set when v1 is used and not set when v0 is used?
There was a problem hiding this comment.
Thanks for the detailed review @dajac. Some of the misses were me working with multiple versions of the code. Appreciate you catching those. I will udpate the next version with all of your comments addressed. One quick question about this comment though:
Is there an example of this somewhere that I can look at? Not sure how to change and verify the version of the request.
There was a problem hiding this comment.
Another issue is that the new fields are defaulted to -1 when not set, but also -1 when the value for a particualr voter or observer is unknown. Thinking if there is a way test this reliably.
dengziming
left a comment
There was a problem hiding this comment.
Thanks for this work @niket-goel , I have 2 suggestions before going into details. Firstly, we can add a test in PlaintextAdminIntegrationTest for this change, the second one is less important, it's recommended to use AdminApiHandler instead of using runnable.call directly, you can refer to FenceProducersHandler.java as a simple example.
There was a problem hiding this comment.
It is very common for API details to change during implementation. Once we're satisfied and ready to merge, we should update the KIP. Typically we would also send a message to the vote thread in case there are any concerns.
There was a problem hiding this comment.
I will update the KIP and the thread once this PR is approved to reflect the changes here.
There was a problem hiding this comment.
nit: conventionally (if perhaps not always consistently), we prefer parenthesis for toString implementations
There was a problem hiding this comment.
nit: do we need to expose this? Usually we only expose the minimum necessary constructors since we're often stuck with what we expose for a long time.
There was a problem hiding this comment.
I need the constructor access in DescribeQuorumResponse.java. I can sort of work around that by returning the fields raw or encapsulated in a different object, but i thought this was simpler. If we want to prioritize hiding the constructor more, I can change it so that the getVoterInfo and getObserverInfo return raw fields. Do you have any thoughts on this?
There was a problem hiding this comment.
Never mind. I think I was just being a little stupid :)
There was a problem hiding this comment.
nit: getVoterInfo and getObserverInfo seem basically the same. Feels like we can factor out the common logic.
There was a problem hiding this comment.
Hmm.. We're checking the top-level error code, but the response also has a partition-level error to check.
There was a problem hiding this comment.
I'm a little confused what happened here, but it looks like we should get rid of this class and rename DescribeQuorumTest to DescribeQuorumIntegrationTest. Also, did we lose the admin integration test you added?
There was a problem hiding this comment.
I changed it so that the new test resides in KRaftClusterTest. This was Daivd's suggestion and I felt it made more sense there, especially because that new test used a different test framework than these ones.
The change in the name got included by mistake and I will revert it what it was.
dengziming
left a comment
There was a problem hiding this comment.
Thanks for the update @niket-goel , left some comments.
There was a problem hiding this comment.
The class name and file name are inconsistent.
There was a problem hiding this comment.
Ditto, The class name and file name are inconsistent.
There was a problem hiding this comment.
response.getTopicNameByIndex(partition) is a little confusing here, how can we get a topic by partition, I think we should rename partition to topicIndex or use response.getTopicNameByIndex(0) directly.
There was a problem hiding this comment.
I wonder if we can send it to controllerSockerServer directly? for example, we set bootstrap.server=localhost:9093, we should prevent a client from doing this.
There was a problem hiding this comment.
It's worth checking partitionData.observers() here too.
There was a problem hiding this comment.
ApiKeys.DESCRIBE_QUORUM.id, DESCRIBE_QUORUM.highestSupportVersion, DESCRIBE_QUORUM.lowestSupportVersion may be better here.
|
Thanks for review @dengziming . A few comments:
I looked at the file and the tests there look similar to the what was added to
I looked at the class and it looks like a wrapper for the runnable call. Do you mind if we make that improvement in a subsequent change (just trying to get this PR under control :) )
A couple of things here -- This code existed before this change (I just moved it). I would love to improve it though. I just did not understand what exactly your comment meant. Could you please elaborate? Apart from the above questions I think I have addressed all the other concerns everyone raised in the latest version. |
|
@niket-goel Thanks for the reply, I left 2 minor comments. I still find the class name is For the last problem, I mean we can also get quorum directly from the controller, I can give you an example: For a single node KRaft cluster, we can get quorum info from BrokerServer: We can also connect to ControllerServer: We can leave this problem to a separate PR since it's unrelated here. |
There was a problem hiding this comment.
we can also check observers here.
There was a problem hiding this comment.
nit: add : Unit after method
|
Thanks @dengziming . I have pushed another version where all tests are passing locally. Appreciate your help with the review. |
hachikuji
left a comment
There was a problem hiding this comment.
Thanks for the updates. Left a few more comments.
There was a problem hiding this comment.
nit: these are all misaligned
There was a problem hiding this comment.
nit: the idiomatic way to write this is
voterData.foreach { state =>
...
}There was a problem hiding this comment.
nit: we should use assertEquals. The advantage is that we can see what the actual value was in the failure message, which is sometimes useful to understand the failure.
There was a problem hiding this comment.
How about this?
for (version <- ApiKeys.DESCRIBE_QUORUM.allVersions.asScala) {| private QuorumInfo createQuorumResult(final DescribeQuorumResponse response) { | ||
| Integer partition = 0; | ||
| String topicName = response.getTopicNameByIndex(partition); | ||
| Integer leaderId = response.getPartitionLeaderId(topicName, partition); |
There was a problem hiding this comment.
@niket-goel any comment here? I think this part still reads awkward. Converting to an intermediate map is conventional. An alternative would be to do a quick validation of the response. We can structure the checks like this:
- Check top-level error code
- Verify only one topic in the response which matches metadata topic
- Verify only one partition in the response with id 0
- Check partition-level error code.
This is similar to how we handle the request in KafkaRaftClient.handleDescribeQuorumRequest.
There was a problem hiding this comment.
Could we have a test case with a partition-level error?
There was a problem hiding this comment.
I wonder why there isn't an observer, we should have 4 observers since we have 4 brokers, this may also not be related to this PR, I will spend some time investigating.
There was a problem hiding this comment.
I find the cause here, We set nodeId=-1 if it's a broker so observers.size==0
kafka/core/src/main/scala/kafka/raft/RaftManager.scala
Lines 185 to 189 in 4c9eeef
I changed it to val nodeId = OptionalInt.of(config.nodeId), then observers.size==4
There was a problem hiding this comment.
Do we need to do something about this in this PR?
There was a problem hiding this comment.
This comment has not been addressed.
There was a problem hiding this comment.
@hachikuji Is it the expected behavior to have zero observers?
b58f7df to
5a08355
Compare
dajac
left a comment
There was a problem hiding this comment.
@niket-goel Thanks for the update. I made another pass on it and left some comments.
There was a problem hiding this comment.
nit: There is an extra space before with.
There was a problem hiding this comment.
nit: We could remove this empty line.
There was a problem hiding this comment.
nit: We could remove this empty line.
There was a problem hiding this comment.
nit: Should we say Returns a future containing the quorum info.?
There was a problem hiding this comment.
- We should add a message in every exception provided back to the user. If we do this, we can log as debug instead of error.
- Instead of throwing the exception, we usually complete the future and return.
There was a problem hiding this comment.
Good point about adding the message to the exception being returned.
The behavior of the code block is equivalent to completing the future and returning. I just didn't want to repeat the future completion code. Is there a well known convention to handle code blocks like this in JAVA?
There was a problem hiding this comment.
Could we verify anything if version is > 0?
There was a problem hiding this comment.
It is curious that we have the topic but not the partition. Is it on purpose, perhaps because we assume only one partition anyway?
There was a problem hiding this comment.
I think It's better to add partition here because we are making way for multi-raft.
There was a problem hiding this comment.
The intention was to keep it simple for now (given we are not using the system as multi-raft yet). We could add in the partition information here as well, but then how far do we want to go. Do we want this structure to mirror the TopicData and PartitionData types in the DescribeQuorumResponseData, or do we just want to do something simple (like adding in a partition ID) for now?
There was a problem hiding this comment.
@niket-goel Maybe we had better leave topic/partition out of this object? I don't think there's any reason to expose __cluster_metadata to users of the admin client.
There was a problem hiding this comment.
Is the intent to retry here? For some errors, such as auth failures, we probably would rather fail fast.
There was a problem hiding this comment.
The reason I added this block is because I noticed a gradle warning which suggested that (with the addition of the general Exception catch block), some runtime exceptions might get hidden. A little reading on this suggested that a best practice is to catch and re-throw runtime exceptions. I guess your comment here is that it is best to just do the same thing with runtime exceptions as we are doing with other exceptions and complete the future with an error. Am I understanding that correctly?
There was a problem hiding this comment.
Which exceptions are we catching here?
There was a problem hiding this comment.
So this block is me trying to have a single future.completeExceptionally() call in this code block. We are catching UnknownServerException and any exception that might be returned by the server in its response. The generic catch handler can be avoided by not throwing the exceptions returned by the server, and instead just completing exceptionally within the block above. I just find it more maintainable to have single calls to completions etc. If this is creating some other issues in the code, i can change this.
There was a problem hiding this comment.
UnknownServerException extends KafkaException, which extends RuntimeException. So I think all the errors that we are raising above get re-thrown in the previous catch.
There was a problem hiding this comment.
nit: are we using this anymore?
There was a problem hiding this comment.
no we are not. Thanks for the catch!
There was a problem hiding this comment.
nit: we usually add parenthesis for mutators
There was a problem hiding this comment.
When lastFetchTimestamp or lastCaughtUpTimestamp are not provided (equals to -1), don't we want to return an empty option instead of returning an option containing -1?
There was a problem hiding this comment.
I went back and forth between that and ended up returning a -1 optional here. I now remember that the original intention was to have an empty optional. Will address this.
There was a problem hiding this comment.
Maybe we should check if size is not equal to 1 here and below. I guess an empty list is also possible.
There was a problem hiding this comment.
Could we extract this bloc into an helper method and share it with the voters part?
There was a problem hiding this comment.
Do we really think this is worth another method.?
There was a problem hiding this comment.
Yes. We should avoid code duplication. Btw, you could also use the stream api here.: partition.observers().stream.map(function).collect(Collectors.toList()). I leave this one up to you.
There was a problem hiding this comment.
Should we check all the data in quorum info in this integration test? Just checking the leader and the number of voters/observers seems weak to me.
There was a problem hiding this comment.
I think I missed replying to this comment earlier. Apologies about that. I am not sure if we can check anything for versions greater than zero at the moment. As of this PR we are not setting the fields in the response and so we cannot verify that. Was there something you had in mind that we should verify for versions > 0?
There was a problem hiding this comment.
In this case, should we just remove that version check and verify all versions? My understanding is that they should all be the same at the moment. We can update the test when we implement the server side. What do you think?
There was a problem hiding this comment.
Should we also assert logEndOffset?
…Voter Lag This commit adds an Admin API handler for DescribeQuorum Request and also adds in two new fields LastFetchTimestamp and LastCaughtUpTimestamp to the DescribeQuorumResponse as described by KIP-836. This commit does not implement the newly added fields. Those will be added in a subsequent commit.
Changes: Added some unit tests to KafkaAdminClientTest Merged multiple DescribeQuorumTest files Some other refactoring
Changes include: * Fixing fields not being correctly set when processing API response in Admin Client * Modified test to cover the above case * Test refactoring * Minor fixes in naming and indentation
…ng minor typos and missing comments
* Removed explicit exception handling from DescribeMetadataQuorum::handleResponse. * Removed an unused method * Fixed Api handler to return OptionalEmpty for unknown fields.
6a7575b to
51f7dd2
Compare
dajac
left a comment
There was a problem hiding this comment.
@niket-goel Thanks for the update. I left a few more minor comments. There are also a few previous comments which have not been addressed.
There was a problem hiding this comment.
Yes. We should avoid code duplication. Btw, you could also use the stream api here.: partition.observers().stream.map(function).collect(Collectors.toList()). I leave this one up to you.
There was a problem hiding this comment.
This comment may have been missed.
| ReplicaState(int replicaId, long logEndOffset, | ||
| OptionalLong lastFetchTimeMs, OptionalLong lastCaughtUpTimeMs) { |
There was a problem hiding this comment.
nit: The code format seems a bit off here. I think that we would format like this:
ReplicaState(
int replicaId,
long logEndOffset,
OptionalLong lastFetchTimeMs,
OptionalLong lastCaughtUpTimeMs
) {
| public static DescribeQuorumResponse parse(ByteBuffer buffer, short version) { | ||
| return new DescribeQuorumResponse(new DescribeQuorumResponseData(new ByteBufferAccessor(buffer), version)); | ||
| } | ||
|
|
There was a problem hiding this comment.
nit: This empty line could be removed.
| val quorumState = admin.describeMetadataQuorum(new DescribeMetadataQuorumOptions) | ||
| val quorumInfo = quorumState.quorumInfo().get() | ||
|
|
||
| assertEquals(3000, quorumInfo.leaderId()) |
There was a problem hiding this comment.
nit: We usually omit parenthesis for getters. There are a few other cases in this file.
There was a problem hiding this comment.
This comment has not been addressed.
| assertEquals(3000, quorumInfo.leaderId()) | ||
| assertEquals(0, quorumInfo.observers.size()) | ||
| assertEquals(3, quorumInfo.voters.size()) | ||
| quorumInfo.voters().forEach( voter => { |
There was a problem hiding this comment.
nit: We usually format block as follow: quorumInfo.voters.forEach { voter =>.
There was a problem hiding this comment.
In this case, should we just remove that version check and verify all versions? My understanding is that they should all be the same at the moment. We can update the test when we implement the server side. What do you think?
dajac
left a comment
There was a problem hiding this comment.
@niket-goel Thanks for the update. Overall, LGTM. I left a few nits and one question.
| int replicaId, | ||
| long logEndOffset, | ||
| OptionalLong lastFetchTimeMs, | ||
| OptionalLong lastCaughtUpTimeMs |
There was a problem hiding this comment.
nit: We usually use 4 spaces indentation here.
| val quorumInfo = quorumState.quorumInfo.get() | ||
|
|
||
| assertEquals(0, quorumInfo.observers.size()) | ||
| assertEquals(3, quorumInfo.voters.size()) |
There was a problem hiding this comment.
nit: We could omit parenthesis after size. The same for the previous line.
| .getOrElse(throw new AssertionError("Failed to find leader among current voter states")) | ||
| assertTrue(leaderState.logEndOffset > 0) | ||
|
|
||
| val voterData = partitionData.currentVoters().asScala |
There was a problem hiding this comment.
nit: We could omit parenthesis after currentVoters. The same for the next line.
| observerData.foreach { state => | ||
| assertTrue(0 < state.replicaId) | ||
| assertTrue(0 < state.logEndOffset()) | ||
| assertEquals(-1, state.lastFetchTimestamp()) | ||
| assertEquals(-1, state.lastCaughtUpTimestamp()) | ||
| } |
There was a problem hiding this comment.
nit: Should we just remove this block if assertEquals(0, observerData.size)?
There was a problem hiding this comment.
@hachikuji Is it the expected behavior to have zero observers?
| ", voters=" + voters.toString() + | ||
| ", observers=" + observers.toString() + |
There was a problem hiding this comment.
nit: I suppose that we could remove the toString as they as implicit.
| assertTrue(0 < state.logEndOffset()) | ||
| assertEquals(-1, state.lastFetchTimestamp()) | ||
| assertEquals(-1, state.lastCaughtUpTimestamp()) |
There was a problem hiding this comment.
nit: We can also omit parenthesis for those..
|
Thanks for the in-depth review @dajac @hachikuji and @dengziming. I have updated the PR with the final NITs from @dajac fixed. Are we good to merge with 1 LGTM or do we need 1 more?
@dengziming also raised the same question in a previous comment, and we decided to tackle that in a separate PR. I will cut a JIRA for this. |
dajac
left a comment
There was a problem hiding this comment.
LGTM. I will merge it when the build completes.
@niket-goel Could you update the KIP and notify the vote thread with the changes? Thanks.
|
@dajac I think this is the reason there are no observers: https://github.com/apache/kafka/blob/trunk/core/src/main/scala/kafka/raft/RaftManager.scala#L188. We should just use |
hachikuji
left a comment
There was a problem hiding this comment.
Thanks for all the iterations. LGTM.
|
Thanks @hachikuji . I have created https://issues.apache.org/jira/browse/KAFKA-13986 for the observer count issue and will raise a PR once this one is merged |
|
@niket-goel It looks like we are running into https://issues.apache.org/jira/browse/KAFKA-13940, which is causing the new integration test to be flaky. I think our options are either to handle this error code in the client or to change the raft implementation to throw a retriable error (probably either |
I lean towards the server side change to be consistent with other APIs. |
|
I agree with you guys. I have pushed a commit to remove the flaky test for now. How about we merge this PR as is (without the failing test) and then follow up on KAFKA-13940 to enable it back. |
57b48ee to
739b400
Compare
…Voter Lag (apache#12206) This commit adds an Admin API handler for DescribeQuorum Request and also adds in two new fields LastFetchTimestamp and LastCaughtUpTimestamp to the DescribeQuorumResponse as described by KIP-836. This commit does not implement the newly added fields. Those will be added in a subsequent commit. Reviewers: dengziming <dengziming1993@gmail.com>, David Jacot <djacot@confluent.io>, Jason Gustafson <jason@confluent.io>
This commit adds an Admin API handler for DescribeQuorum Request and also
adds in two new fields LastFetchTimestamp and LastCaughtUpTimestamp to
the DescribeQuorumResponse as described by KIP-836.
This commit does not implement the newly added fields. Those will be
added in a subsequent commit.
More detailed description of your change,
if necessary. The PR title and PR message become
the squashed commit message, so use a separate
comment to ping reviewers.
Summary of testing strategy (including rationale)
for the feature or bug fix. Unit and/or integration
tests are expected for any behaviour change and
system tests should be considered for larger changes.
Committer Checklist (excluded from commit message)