|
57 | 57 | import io.kroxylicious.it.testplugins.RequestResponseMarkingFilterFactory; |
58 | 58 | import io.kroxylicious.it.testplugins.TopicIdToNameResponseStamper; |
59 | 59 | import io.kroxylicious.it.testplugins.TopicNameMetadataPrefixer; |
| 60 | +import io.kroxylicious.proxy.config.ConfigurationBuilder; |
60 | 61 | import io.kroxylicious.proxy.config.NamedFilterDefinition; |
61 | 62 | import io.kroxylicious.proxy.config.NamedFilterDefinitionBuilder; |
62 | 63 | import io.kroxylicious.proxy.filter.simpletransform.FetchResponseTransformation; |
63 | 64 | import io.kroxylicious.proxy.filter.simpletransform.ProduceRequestTransformation; |
| 65 | +import io.kroxylicious.proxy.internal.TopicNameRetriever; |
64 | 66 | import io.kroxylicious.test.Request; |
65 | 67 | import io.kroxylicious.test.Response; |
66 | 68 | import io.kroxylicious.test.ResponsePayload; |
@@ -168,14 +170,106 @@ void filtersCanLookUpTopicNames(KafkaCluster cluster, Topic topic1, Topic topic2 |
168 | 170 | message.unknownTaggedFields().add( |
169 | 171 | new RawTaggedField(TopicIdToNameResponseStamper.TOPIC_ID_TAG, (topic1Id + "," + topic2Id).getBytes(StandardCharsets.UTF_8))); |
170 | 172 | Response response = client.getSync(new Request(METADATA, METADATA.latestVersion(), "client", message)); |
171 | | - List<String> tags = unknownTaggedFieldsToStrings(response.payload().message(), TopicIdToNameResponseStamper.TOPIC_NAME_TAG).toList(); |
172 | | - assertThat(tags).hasSize(1); |
173 | | - String tag = tags.getFirst(); |
174 | | - String[] topicNames = tag.split(","); |
| 173 | + String[] topicNames = extractTopicNames(response); |
175 | 174 | assertThat(topicNames).containsExactlyInAnyOrder(topicNameMapping(topic1Id, topic1.name(), null), topicNameMapping(topic2Id, topic2.name(), null)); |
176 | 175 | } |
177 | 176 | } |
178 | 177 |
|
| 178 | + @Test |
| 179 | + void topicNamesAreCached() { |
| 180 | + NamedFilterDefinition namedFilterDefinition = new NamedFilterDefinitionBuilder(TOPIC_ID_LOOKUP_FILTER_NAME, |
| 181 | + TopicIdToNameResponseStamper.class.getName()) |
| 182 | + .build(); |
| 183 | + try (var tester = mockKafkaKroxyliciousTester(bootstrap -> { |
| 184 | + ConfigurationBuilder proxy = proxy(bootstrap); |
| 185 | + proxy.addToFilterDefinitions(namedFilterDefinition) |
| 186 | + .addToDefaultFilters(namedFilterDefinition.name()); |
| 187 | + return proxy; |
| 188 | + }); |
| 189 | + var client = tester.simpleTestClient()) { |
| 190 | + Uuid topic1Id = Uuid.randomUuid(); |
| 191 | + String topic1Name = "topic1"; |
| 192 | + Uuid topic2Id = Uuid.randomUuid(); |
| 193 | + String topic2Name = "topic2"; |
| 194 | + MetadataResponseData responseData = new MetadataResponseData(); |
| 195 | + responseData.topics().add(getResponseTopic(topic1Name, topic1Id)); |
| 196 | + responseData.topics().add(getResponseTopic(topic2Name, topic2Id)); |
| 197 | + tester.addMockResponseForApiKey(new ResponsePayload(METADATA, TopicNameRetriever.METADATA_API_VER_WITH_TOPIC_ID_SUPPORT, responseData)); |
| 198 | + tester.addMockResponseForApiKey(new ResponsePayload(API_VERSIONS, API_VERSIONS.latestVersion(), new ApiVersionsResponseData())); |
| 199 | + ApiVersionsRequestData message = new ApiVersionsRequestData(); |
| 200 | + message.unknownTaggedFields().add( |
| 201 | + new RawTaggedField(TopicIdToNameResponseStamper.TOPIC_ID_TAG, (topic1Id + "," + topic2Id).getBytes(StandardCharsets.UTF_8))); |
| 202 | + Response response = client.getSync(new Request(API_VERSIONS, API_VERSIONS.latestVersion(), "client", message)); |
| 203 | + String[] topicNames = extractTopicNames(response); |
| 204 | + assertThat(topicNames).containsExactlyInAnyOrder(topicNameMapping(topic1Id, topic1Name, null), topicNameMapping(topic2Id, topic2Name, null)); |
| 205 | + assertThat(tester.getRequestsForApiKey(METADATA)).hasSize(1); |
| 206 | + assertThat(tester.getRequestsForApiKey(API_VERSIONS)).hasSize(1); |
| 207 | + |
| 208 | + Response response2 = client.getSync(new Request(API_VERSIONS, API_VERSIONS.latestVersion(), "client", message)); |
| 209 | + String[] topicNames2 = extractTopicNames(response2); |
| 210 | + assertThat(topicNames2).containsExactlyInAnyOrder(topicNameMapping(topic1Id, topic1Name, null), topicNameMapping(topic2Id, topic2Name, null)); |
| 211 | + // we infer that the topic ids were cached as they have been augmented onto the response but the mock server |
| 212 | + // has received no further metadata requests |
| 213 | + assertThat(tester.getRequestsForApiKey(METADATA)).hasSize(1); |
| 214 | + assertThat(tester.getRequestsForApiKey(API_VERSIONS)).hasSize(2); |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + @Test |
| 219 | + void topicNamesCachingScopedToCluster() { |
| 220 | + NamedFilterDefinition namedFilterDefinition = new NamedFilterDefinitionBuilder(TOPIC_ID_LOOKUP_FILTER_NAME, |
| 221 | + TopicIdToNameResponseStamper.class.getName()) |
| 222 | + .build(); |
| 223 | + try (var tester = mockKafkaKroxyliciousTester(bootstrap -> { |
| 224 | + ConfigurationBuilder proxy = proxy(bootstrap); |
| 225 | + proxy.addToFilterDefinitions(namedFilterDefinition) |
| 226 | + .addToDefaultFilters(namedFilterDefinition.name()); |
| 227 | + return proxy; |
| 228 | + }); |
| 229 | + var client = tester.simpleTestClient(); |
| 230 | + var client2 = tester.simpleTestClient()) { |
| 231 | + Uuid topic1Id = Uuid.randomUuid(); |
| 232 | + String topic1Name = "topic1"; |
| 233 | + Uuid topic2Id = Uuid.randomUuid(); |
| 234 | + String topic2Name = "topic2"; |
| 235 | + MetadataResponseData responseData = new MetadataResponseData(); |
| 236 | + responseData.topics().add(getResponseTopic(topic1Name, topic1Id)); |
| 237 | + responseData.topics().add(getResponseTopic(topic2Name, topic2Id)); |
| 238 | + tester.addMockResponseForApiKey(new ResponsePayload(METADATA, TopicNameRetriever.METADATA_API_VER_WITH_TOPIC_ID_SUPPORT, responseData)); |
| 239 | + tester.addMockResponseForApiKey(new ResponsePayload(API_VERSIONS, API_VERSIONS.latestVersion(), new ApiVersionsResponseData())); |
| 240 | + ApiVersionsRequestData message = new ApiVersionsRequestData(); |
| 241 | + message.unknownTaggedFields().add( |
| 242 | + new RawTaggedField(TopicIdToNameResponseStamper.TOPIC_ID_TAG, (topic1Id + "," + topic2Id).getBytes(StandardCharsets.UTF_8))); |
| 243 | + Response response = client.getSync(new Request(API_VERSIONS, API_VERSIONS.latestVersion(), "client", message)); |
| 244 | + String[] topicNames = extractTopicNames(response); |
| 245 | + assertThat(topicNames).containsExactlyInAnyOrder(topicNameMapping(topic1Id, topic1Name, null), topicNameMapping(topic2Id, topic2Name, null)); |
| 246 | + assertThat(tester.getRequestsForApiKey(METADATA)).hasSize(1); |
| 247 | + assertThat(tester.getRequestsForApiKey(API_VERSIONS)).hasSize(1); |
| 248 | + |
| 249 | + // using a second client to prove that the cache scope is virtual-cluster wide |
| 250 | + Response response2 = client2.getSync(new Request(API_VERSIONS, API_VERSIONS.latestVersion(), "client", message)); |
| 251 | + String[] topicNames2 = extractTopicNames(response2); |
| 252 | + assertThat(topicNames2).containsExactlyInAnyOrder(topicNameMapping(topic1Id, topic1Name, null), topicNameMapping(topic2Id, topic2Name, null)); |
| 253 | + // we infer that the topic names were cached as a single metadata requests was sent to the mock |
| 254 | + assertThat(tester.getRequestsForApiKey(METADATA)).hasSize(1); |
| 255 | + assertThat(tester.getRequestsForApiKey(API_VERSIONS)).hasSize(2); |
| 256 | + } |
| 257 | + } |
| 258 | + |
| 259 | + private static String[] extractTopicNames(Response response) { |
| 260 | + List<String> tags = unknownTaggedFieldsToStrings(response.payload().message(), TopicIdToNameResponseStamper.TOPIC_NAME_TAG).toList(); |
| 261 | + assertThat(tags).hasSize(1); |
| 262 | + String tag = tags.getFirst(); |
| 263 | + return tag.split(","); |
| 264 | + } |
| 265 | + |
| 266 | + private static MetadataResponseData.MetadataResponseTopic getResponseTopic(String topic1Name, Uuid topic1Id) { |
| 267 | + MetadataResponseData.MetadataResponseTopic topic = new MetadataResponseData.MetadataResponseTopic(); |
| 268 | + topic.setName(topic1Name); |
| 269 | + topic.setTopicId(topic1Id); |
| 270 | + return topic; |
| 271 | + } |
| 272 | + |
179 | 273 | @Test |
180 | 274 | void filtersCanLookUpNonExistentTopicNames(KafkaCluster cluster) { |
181 | 275 | NamedFilterDefinition namedFilterDefinition = new NamedFilterDefinitionBuilder(TOPIC_ID_LOOKUP_FILTER_NAME, |
@@ -216,10 +310,7 @@ void filtersCanLookUpPartiallyExistingTopics(KafkaCluster cluster, Topic topic1) |
216 | 310 | message.unknownTaggedFields().add( |
217 | 311 | new RawTaggedField(TopicIdToNameResponseStamper.TOPIC_ID_TAG, (nonexistentTopic + "," + topic1Id).getBytes(StandardCharsets.UTF_8))); |
218 | 312 | Response response = client.getSync(new Request(METADATA, METADATA.latestVersion(), "client", message)); |
219 | | - List<String> tags = unknownTaggedFieldsToStrings(response.payload().message(), TopicIdToNameResponseStamper.TOPIC_NAME_TAG).toList(); |
220 | | - assertThat(tags).hasSize(1); |
221 | | - String tag = tags.getFirst(); |
222 | | - String[] topicNames = tag.split(","); |
| 313 | + String[] topicNames = extractTopicNames(response); |
223 | 314 | assertThat(topicNames).containsExactlyInAnyOrder(topicNameMapping(topic1Id, topic1.name(), null), |
224 | 315 | topicNameMapping(nonexistentTopic, null, "UNKNOWN_TOPIC_ID")); |
225 | 316 | } |
@@ -261,10 +352,7 @@ private static void assertTopicStampingFilterObservesTopicNames(KafkaCluster clu |
261 | 352 | message.unknownTaggedFields().add( |
262 | 353 | new RawTaggedField(TopicIdToNameResponseStamper.TOPIC_ID_TAG, (topic1Id + "," + topic2Id).getBytes(StandardCharsets.UTF_8))); |
263 | 354 | Response response = client.getSync(new Request(METADATA, METADATA.latestVersion(), "client", message)); |
264 | | - List<String> tags = unknownTaggedFieldsToStrings(response.payload().message(), TopicIdToNameResponseStamper.TOPIC_NAME_TAG).toList(); |
265 | | - assertThat(tags).hasSize(1); |
266 | | - String tag = tags.getFirst(); |
267 | | - String[] topicNames = tag.split(","); |
| 355 | + String[] topicNames = extractTopicNames(response); |
268 | 356 | assertThat(topicNames).containsExactlyInAnyOrder(topicNameMapping(topic1Id, expectedNameForTopic1, null), |
269 | 357 | topicNameMapping(topic2Id, expectedNameForTopic2, null)); |
270 | 358 | } |
|
0 commit comments