@@ -20,6 +20,7 @@ package kafka.server
20
20
import java .util .Optional
21
21
import kafka .utils .TestUtils
22
22
import org .apache .kafka .common .Uuid
23
+ import org .apache .kafka .common .errors .UnsupportedVersionException
23
24
import org .apache .kafka .common .internals .Topic
24
25
import org .apache .kafka .common .protocol .Errors
25
26
import org .apache .kafka .common .requests .{MetadataRequest , MetadataResponse }
@@ -40,6 +41,14 @@ class MetadataRequestTest extends AbstractMetadataRequestTest {
40
41
doSetup(testInfo, createOffsetsTopic = false )
41
42
}
42
43
44
+ @ ParameterizedTest
45
+ @ ValueSource (strings = Array (" kraft" ))
46
+ def testClusterIdWithRequestVersion1 (quorum : String ): Unit = {
47
+ val v1MetadataResponse = sendMetadataRequest(MetadataRequest .Builder .allTopics.build(1 .toShort))
48
+ val v1ClusterId = v1MetadataResponse.clusterId
49
+ assertNull(v1ClusterId, s " v1 clusterId should be null " )
50
+ }
51
+
43
52
@ ParameterizedTest
44
53
@ ValueSource (strings = Array (" kraft" ))
45
54
def testClusterIdIsValid (quorum : String ): Unit = {
@@ -96,17 +105,27 @@ class MetadataRequestTest extends AbstractMetadataRequestTest {
96
105
def testAutoTopicCreation (quorum : String ): Unit = {
97
106
val topic1 = " t1"
98
107
val topic2 = " t2"
99
- val topic3 = " t4"
100
- val topic4 = " t5"
108
+ val topic3 = " t3"
109
+ val topic4 = " t4"
110
+ val topic5 = " t5"
101
111
createTopic(topic1)
102
112
103
113
val response1 = sendMetadataRequest(new MetadataRequest .Builder (Seq (topic1, topic2).asJava, true ).build())
104
114
assertNull(response1.errors.get(topic1))
105
115
checkAutoCreatedTopic(topic2, response1)
106
116
107
- val response2 = sendMetadataRequest(new MetadataRequest .Builder (Seq (topic3, topic4).asJava, false , 4 .toShort).build)
108
- assertEquals(Errors .UNKNOWN_TOPIC_OR_PARTITION , response2.errors.get(topic3))
109
- assertEquals(Errors .UNKNOWN_TOPIC_OR_PARTITION , response2.errors.get(topic4))
117
+ // The default behavior in old versions of the metadata API is to allow topic creation, so
118
+ // protocol downgrades should happen gracefully when auto-creation is explicitly requested.
119
+ val response2 = sendMetadataRequest(new MetadataRequest .Builder (Seq (topic3).asJava, true ).build(1 ))
120
+ checkAutoCreatedTopic(topic3, response2)
121
+
122
+ // V3 doesn't support a configurable allowAutoTopicCreation, so disabling auto-creation is not supported
123
+ assertThrows(classOf [UnsupportedVersionException ], () => sendMetadataRequest(new MetadataRequest (requestData(List (topic4), allowAutoTopicCreation = false ), 3 .toShort)))
124
+
125
+ // V4 and higher support a configurable allowAutoTopicCreation
126
+ val response3 = sendMetadataRequest(new MetadataRequest .Builder (Seq (topic4, topic5).asJava, false , 4 .toShort).build)
127
+ assertEquals(Errors .UNKNOWN_TOPIC_OR_PARTITION , response3.errors.get(topic4))
128
+ assertEquals(Errors .UNKNOWN_TOPIC_OR_PARTITION , response3.errors.get(topic5))
110
129
}
111
130
112
131
@ ParameterizedTest
@@ -132,10 +151,15 @@ class MetadataRequestTest extends AbstractMetadataRequestTest {
132
151
createTopic(" t1" , 3 , 2 )
133
152
createTopic(" t2" , 3 , 2 )
134
153
135
- // v4, Null represents all topics
136
- val metadataResponseV1 = sendMetadataRequest(MetadataRequest .Builder .allTopics.build(4 .toShort))
137
- assertTrue(metadataResponseV1.errors.isEmpty, " V4 Response should have no errors" )
138
- assertEquals(2 , metadataResponseV1.topicMetadata.size(), " V4 Response should have 2 (all) topics" )
154
+ // v0, Empty list represents all topics
155
+ val metadataResponseV0 = sendMetadataRequest(new MetadataRequest (requestData(List (), allowAutoTopicCreation = true ), 0 .toShort))
156
+ assertTrue(metadataResponseV0.errors.isEmpty, " V0 Response should have no errors" )
157
+ assertEquals(2 , metadataResponseV0.topicMetadata.size(), " V0 Response should have 2 (all) topics" )
158
+
159
+ // v1, Null represents all topics
160
+ val metadataResponseV1 = sendMetadataRequest(MetadataRequest .Builder .allTopics.build(1 .toShort))
161
+ assertTrue(metadataResponseV1.errors.isEmpty, " V1 Response should have no errors" )
162
+ assertEquals(2 , metadataResponseV1.topicMetadata.size(), " V1 Response should have 2 (all) topics" )
139
163
}
140
164
141
165
@ ParameterizedTest
@@ -217,15 +241,25 @@ class MetadataRequestTest extends AbstractMetadataRequestTest {
217
241
! response.brokers.asScala.exists(_.id == downNode.dataPlaneRequestProcessor.brokerId)
218
242
}, " Replica was not found down" , 50000 )
219
243
220
- // Validate version 4 returns unavailable replicas with no error
221
- val v4MetadataResponse = sendMetadataRequest(new MetadataRequest .Builder (List (replicaDownTopic).asJava, true ).build(4 ))
222
- val v4BrokerIds = v4MetadataResponse.brokers().asScala.map(_.id).toSeq
223
- assertTrue(v4MetadataResponse.errors.isEmpty, " Response should have no errors" )
224
- assertFalse(v4BrokerIds.contains(downNode.config.brokerId), s " The downed broker should not be in the brokers list " )
225
- assertEquals(1 , v4MetadataResponse.topicMetadata.size, " Response should have one topic" )
226
- val v4PartitionMetadata = v4MetadataResponse.topicMetadata.asScala.head.partitionMetadata.asScala.head
227
- assertEquals(Errors .NONE , v4PartitionMetadata.error, " PartitionMetadata should have no errors" )
228
- assertEquals(replicaCount, v4PartitionMetadata.replicaIds.size, s " Response should have $replicaCount replicas " )
244
+ // Validate version 0 still filters unavailable replicas and contains error
245
+ val v0MetadataResponse = sendMetadataRequest(new MetadataRequest (requestData(List (replicaDownTopic), allowAutoTopicCreation = true ), 0 .toShort))
246
+ val v0BrokerIds = v0MetadataResponse.brokers().asScala.map(_.id).toSeq
247
+ assertTrue(v0MetadataResponse.errors.isEmpty, " Response should have no errors" )
248
+ assertFalse(v0BrokerIds.contains(downNode.config.brokerId), s " The downed broker should not be in the brokers list " )
249
+ assertTrue(v0MetadataResponse.topicMetadata.size == 1 , " Response should have one topic" )
250
+ val v0PartitionMetadata = v0MetadataResponse.topicMetadata.asScala.head.partitionMetadata.asScala.head
251
+ assertTrue(v0PartitionMetadata.error == Errors .REPLICA_NOT_AVAILABLE , " PartitionMetadata should have an error" )
252
+ assertTrue(v0PartitionMetadata.replicaIds.size == replicaCount - 1 , s " Response should have ${replicaCount - 1 } replicas " )
253
+
254
+ // Validate version 1 returns unavailable replicas with no error
255
+ val v1MetadataResponse = sendMetadataRequest(new MetadataRequest .Builder (List (replicaDownTopic).asJava, true ).build(1 ))
256
+ val v1BrokerIds = v1MetadataResponse.brokers().asScala.map(_.id).toSeq
257
+ assertTrue(v1MetadataResponse.errors.isEmpty, " Response should have no errors" )
258
+ assertFalse(v1BrokerIds.contains(downNode.config.brokerId), s " The downed broker should not be in the brokers list " )
259
+ assertEquals(1 , v1MetadataResponse.topicMetadata.size, " Response should have one topic" )
260
+ val v1PartitionMetadata = v1MetadataResponse.topicMetadata.asScala.head.partitionMetadata.asScala.head
261
+ assertEquals(Errors .NONE , v1PartitionMetadata.error, " PartitionMetadata should have no errors" )
262
+ assertEquals(replicaCount, v1PartitionMetadata.replicaIds.size, s " Response should have $replicaCount replicas " )
229
263
}
230
264
231
265
@ ParameterizedTest
0 commit comments