mirror of https://github.com/apache/kafka.git
KAFKA-5610; WriteTxnMarker handler should return UNKNOWN_TOPIC_OR_PARTITION if replica is not available
Before this patch, we would instead return the non-retriable `UNSUPPORTED_FOR_MESSAGE_FORMAT` causing markers to be lost. Author: Apurva Mehta <apurva@confluent.io> Reviewers: Ismael Juma <ismael@juma.me.uk>, Jason Gustafson <jason@confluent.io> Closes #3550 from apurvam/KAFKA-5610-handleWriteTxnMarker-should-handle-emigration
This commit is contained in:
parent
9b7a6ee2a9
commit
cdff011fe0
|
|
@ -1543,24 +1543,29 @@ class KafkaApis(val requestChannel: RequestChannel,
|
||||||
var skippedMarkers = 0
|
var skippedMarkers = 0
|
||||||
for (marker <- markers.asScala) {
|
for (marker <- markers.asScala) {
|
||||||
val producerId = marker.producerId
|
val producerId = marker.producerId
|
||||||
val (goodPartitions, partitionsWithIncorrectMessageFormat) = marker.partitions.asScala.partition { partition =>
|
val partitionsWithCompatibleMessageFormat = new mutable.ArrayBuffer[TopicPartition]
|
||||||
replicaManager.getMagic(partition) match {
|
|
||||||
case Some(magic) if magic >= RecordBatch.MAGIC_VALUE_V2 => true
|
|
||||||
case _ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (partitionsWithIncorrectMessageFormat.nonEmpty) {
|
|
||||||
val currentErrors = new ConcurrentHashMap[TopicPartition, Errors]()
|
val currentErrors = new ConcurrentHashMap[TopicPartition, Errors]()
|
||||||
partitionsWithIncorrectMessageFormat.foreach { partition => currentErrors.put(partition, Errors.UNSUPPORTED_FOR_MESSAGE_FORMAT) }
|
marker.partitions.asScala.foreach { partition =>
|
||||||
updateErrors(producerId, currentErrors)
|
replicaManager.getMagic(partition) match {
|
||||||
|
case Some(magic) =>
|
||||||
|
if (magic < RecordBatch.MAGIC_VALUE_V2)
|
||||||
|
currentErrors.put(partition, Errors.UNSUPPORTED_FOR_MESSAGE_FORMAT)
|
||||||
|
else
|
||||||
|
partitionsWithCompatibleMessageFormat += partition
|
||||||
|
case None =>
|
||||||
|
currentErrors.put(partition, Errors.UNKNOWN_TOPIC_OR_PARTITION)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (goodPartitions.isEmpty) {
|
if (!currentErrors.isEmpty)
|
||||||
|
updateErrors(producerId, currentErrors)
|
||||||
|
|
||||||
|
if (partitionsWithCompatibleMessageFormat.isEmpty) {
|
||||||
numAppends.decrementAndGet()
|
numAppends.decrementAndGet()
|
||||||
skippedMarkers += 1
|
skippedMarkers += 1
|
||||||
} else {
|
} else {
|
||||||
val controlRecords = goodPartitions.map { partition =>
|
val controlRecords = partitionsWithCompatibleMessageFormat.map { partition =>
|
||||||
val controlRecordType = marker.transactionResult match {
|
val controlRecordType = marker.transactionResult match {
|
||||||
case TransactionResult.COMMIT => ControlRecordType.COMMIT
|
case TransactionResult.COMMIT => ControlRecordType.COMMIT
|
||||||
case TransactionResult.ABORT => ControlRecordType.ABORT
|
case TransactionResult.ABORT => ControlRecordType.ABORT
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,25 @@ class KafkaApisTest {
|
||||||
assertEquals(expectedErrors, markersResponse.errors(1))
|
assertEquals(expectedErrors, markersResponse.errors(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def shouldRespondWithUnknownTopicWhenPartitionIsNotHosted(): Unit = {
|
||||||
|
val topicPartition = new TopicPartition("t", 0)
|
||||||
|
val (writeTxnMarkersRequest, request) = createWriteTxnMarkersRequest(Utils.mkList(topicPartition))
|
||||||
|
val expectedErrors = Map(topicPartition -> Errors.UNKNOWN_TOPIC_OR_PARTITION).asJava
|
||||||
|
val capturedResponse: Capture[RequestChannel.Response] = EasyMock.newCapture()
|
||||||
|
|
||||||
|
EasyMock.expect(replicaManager.getMagic(topicPartition))
|
||||||
|
.andReturn(None)
|
||||||
|
EasyMock.expect(requestChannel.sendResponse(EasyMock.capture(capturedResponse)))
|
||||||
|
EasyMock.replay(replicaManager, replicaQuotaManager, requestChannel)
|
||||||
|
|
||||||
|
createKafkaApis().handleWriteTxnMarkersRequest(request)
|
||||||
|
|
||||||
|
val markersResponse = readResponse(ApiKeys.WRITE_TXN_MARKERS, writeTxnMarkersRequest, capturedResponse)
|
||||||
|
.asInstanceOf[WriteTxnMarkersResponse]
|
||||||
|
assertEquals(expectedErrors, markersResponse.errors(1))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
def shouldRespondWithUnsupportedMessageFormatForBadPartitionAndNoErrorsForGoodPartition(): Unit = {
|
def shouldRespondWithUnsupportedMessageFormatForBadPartitionAndNoErrorsForGoodPartition(): Unit = {
|
||||||
val tp1 = new TopicPartition("t", 0)
|
val tp1 = new TopicPartition("t", 0)
|
||||||
|
|
@ -177,6 +196,44 @@ class KafkaApisTest {
|
||||||
EasyMock.verify(replicaManager)
|
EasyMock.verify(replicaManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def shouldRespondWithUnknownTopicOrPartitionForBadPartitionAndNoErrorsForGoodPartition(): Unit = {
|
||||||
|
val tp1 = new TopicPartition("t", 0)
|
||||||
|
val tp2 = new TopicPartition("t1", 0)
|
||||||
|
val (writeTxnMarkersRequest, request) = createWriteTxnMarkersRequest(Utils.mkList(tp1, tp2))
|
||||||
|
val expectedErrors = Map(tp1 -> Errors.UNKNOWN_TOPIC_OR_PARTITION, tp2 -> Errors.NONE).asJava
|
||||||
|
|
||||||
|
val capturedResponse: Capture[RequestChannel.Response] = EasyMock.newCapture()
|
||||||
|
val responseCallback: Capture[Map[TopicPartition, PartitionResponse] => Unit] = EasyMock.newCapture()
|
||||||
|
|
||||||
|
EasyMock.expect(replicaManager.getMagic(tp1))
|
||||||
|
.andReturn(None)
|
||||||
|
EasyMock.expect(replicaManager.getMagic(tp2))
|
||||||
|
.andReturn(Some(RecordBatch.MAGIC_VALUE_V2))
|
||||||
|
|
||||||
|
EasyMock.expect(replicaManager.appendRecords(EasyMock.anyLong(),
|
||||||
|
EasyMock.anyShort(),
|
||||||
|
EasyMock.eq(true),
|
||||||
|
EasyMock.eq(false),
|
||||||
|
EasyMock.anyObject(),
|
||||||
|
EasyMock.capture(responseCallback),
|
||||||
|
EasyMock.anyObject())).andAnswer(new IAnswer[Unit] {
|
||||||
|
override def answer(): Unit = {
|
||||||
|
responseCallback.getValue.apply(Map(tp2 -> new PartitionResponse(Errors.NONE)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
EasyMock.expect(requestChannel.sendResponse(EasyMock.capture(capturedResponse)))
|
||||||
|
EasyMock.replay(replicaManager, replicaQuotaManager, requestChannel)
|
||||||
|
|
||||||
|
createKafkaApis().handleWriteTxnMarkersRequest(request)
|
||||||
|
|
||||||
|
val markersResponse = readResponse(ApiKeys.WRITE_TXN_MARKERS, writeTxnMarkersRequest, capturedResponse)
|
||||||
|
.asInstanceOf[WriteTxnMarkersResponse]
|
||||||
|
assertEquals(expectedErrors, markersResponse.errors(1))
|
||||||
|
EasyMock.verify(replicaManager)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
def shouldAppendToLogOnWriteTxnMarkersWhenCorrectMagicVersion(): Unit = {
|
def shouldAppendToLogOnWriteTxnMarkersWhenCorrectMagicVersion(): Unit = {
|
||||||
val topicPartition = new TopicPartition("t", 0)
|
val topicPartition = new TopicPartition("t", 0)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue