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 | ||||
|     for (marker <- markers.asScala) { | ||||
|       val producerId = marker.producerId | ||||
|       val (goodPartitions, partitionsWithIncorrectMessageFormat) = marker.partitions.asScala.partition { partition => | ||||
|         replicaManager.getMagic(partition) match { | ||||
|           case Some(magic) if magic >= RecordBatch.MAGIC_VALUE_V2 => true | ||||
|           case _ => false | ||||
|         } | ||||
|       } | ||||
|       val partitionsWithCompatibleMessageFormat = new mutable.ArrayBuffer[TopicPartition] | ||||
| 
 | ||||
|       if (partitionsWithIncorrectMessageFormat.nonEmpty) { | ||||
|       val currentErrors = new ConcurrentHashMap[TopicPartition, Errors]() | ||||
|         partitionsWithIncorrectMessageFormat.foreach { partition => currentErrors.put(partition, Errors.UNSUPPORTED_FOR_MESSAGE_FORMAT) } | ||||
|         updateErrors(producerId, currentErrors) | ||||
|       marker.partitions.asScala.foreach { partition => | ||||
|         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() | ||||
|         skippedMarkers += 1 | ||||
|       } else { | ||||
|         val controlRecords = goodPartitions.map { partition => | ||||
|         val controlRecords = partitionsWithCompatibleMessageFormat.map { partition => | ||||
|           val controlRecordType = marker.transactionResult match { | ||||
|             case TransactionResult.COMMIT => ControlRecordType.COMMIT | ||||
|             case TransactionResult.ABORT => ControlRecordType.ABORT | ||||
|  |  | |||
|  | @ -139,6 +139,25 @@ class KafkaApisTest { | |||
|     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 | ||||
|   def shouldRespondWithUnsupportedMessageFormatForBadPartitionAndNoErrorsForGoodPartition(): Unit = { | ||||
|     val tp1 = new TopicPartition("t", 0) | ||||
|  | @ -177,6 +196,44 @@ class KafkaApisTest { | |||
|     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 | ||||
|   def shouldAppendToLogOnWriteTxnMarkersWhenCorrectMagicVersion(): Unit = { | ||||
|     val topicPartition = new TopicPartition("t", 0) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue