Skip to content

Commit af804b9

Browse files
authored
KAFKA-21074 follow-up: reject a multi-broker bootstrap in KafkaProtocolFaultProxy (#23515)
`KafkaProtocolFaultProx` supports a single broker. Follow-up for this comment #23438 (comment)
1 parent 869426f commit af804b9

2 files changed

Lines changed: 69 additions & 2 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.kafka.test.faultproxy;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import static org.junit.jupiter.api.Assertions.assertEquals;
22+
import static org.junit.jupiter.api.Assertions.assertThrows;
23+
import static org.junit.jupiter.api.Assertions.assertTrue;
24+
25+
public class KafkaProtocolFaultProxyTest {
26+
27+
@Test
28+
public void shouldRejectMultiBrokerBootstrap() {
29+
// The proxy rewrites all routing to itself and forwards to a single upstream broker, so a
30+
// multi-broker bootstrap must fail fast rather than silently proxy only the first broker.
31+
final IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
32+
() -> KafkaProtocolFaultProxy.inFrontOf("localhost:9092,localhost:9093,localhost:9094"));
33+
assertTrue(e.getMessage().contains("single broker"), e.getMessage());
34+
assertTrue(e.getMessage().contains("3 servers"), e.getMessage());
35+
}
36+
37+
@Test
38+
public void shouldAcceptSingleBrokerBootstrap() throws Exception {
39+
// A single host:port is accepted; bootstrapServers() exposes the proxy's own listening address.
40+
try (KafkaProtocolFaultProxy proxy = KafkaProtocolFaultProxy.inFrontOf("localhost:9092")) {
41+
final String bootstrap = proxy.bootstrapServers();
42+
assertTrue(bootstrap.startsWith("localhost:"), bootstrap);
43+
assertEquals(1, bootstrap.split(",").length, bootstrap);
44+
}
45+
}
46+
}

‎clients/src/testFixtures/java/org/apache/kafka/test/faultproxy/KafkaProtocolFaultProxy.java‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@
8282
* advertised address points back at itself, so a single-broker embedded cluster needs no special config
8383
* (its own ephemeral port is discovered from {@code bootstrapServers()}).
8484
*
85+
* <p><b>Single broker only.</b> Because every advertised broker and coordinator address is rewritten to this
86+
* one proxy — which forwards to a single upstream broker — the proxy supports exactly one broker. A
87+
* multi-broker bootstrap would silently funnel every partition and coordinator to the first broker, producing
88+
* {@code NOT_LEADER_OR_FOLLOWER}/{@code NOT_COORDINATOR} retry loops, so {@link #inFrontOf(String)} rejects a
89+
* bootstrap that lists more than one server. Front a single-broker {@code EmbeddedKafkaCluster}, or pass one
90+
* broker's address (e.g. {@code cluster.bootstrapServers().split(",")[0]}).
91+
*
8592
* <p>Determinism: {@code once()}/{@code onCall(n)}/{@code times(n)} are deterministic and safe for
8693
* assertions; {@code withProbability(p)} is chaos-mode only. The proxy never closes sockets unless a
8794
* {@code disconnectOn(...)} rule fires, so it is not itself a source of flakiness.
@@ -140,13 +147,27 @@ public final class KafkaProtocolFaultProxy implements AutoCloseable {
140147
private volatile int proxyPort;
141148

142149
private KafkaProtocolFaultProxy(final String targetBootstrap) {
143-
final String hostPort = targetBootstrap.split(",")[0].trim();
150+
final String[] servers = targetBootstrap.split(",");
151+
// Routing rewrites all addresses to this proxy (one upstream broker), so a multi-broker bootstrap
152+
// would funnel every partition and coordinator to the first broker
153+
// causing NOT_LEADER / NOT_COORDINATOR loops.
154+
if (servers.length > 1) {
155+
throw new IllegalArgumentException(
156+
"KafkaProtocolFaultProxy proxies a single broker, but the bootstrap '" + targetBootstrap
157+
+ "' lists " + servers.length + " servers");
158+
}
159+
final String hostPort = servers[0].trim();
144160
final int idx = hostPort.lastIndexOf(':');
145161
this.targetHost = hostPort.substring(0, idx);
146162
this.targetPort = Integer.parseInt(hostPort.substring(idx + 1));
147163
}
148164

149-
/** Create and start a proxy in front of the given broker bootstrap address. */
165+
/**
166+
* Create and start a proxy in front of the given broker bootstrap address.
167+
*
168+
* @param targetBootstrap a single broker's {@code host:port} — the proxy supports one broker only
169+
* @throws IllegalArgumentException if {@code targetBootstrap} lists more than one server
170+
*/
150171
public static KafkaProtocolFaultProxy inFrontOf(final String targetBootstrap) {
151172
final KafkaProtocolFaultProxy proxy = new KafkaProtocolFaultProxy(targetBootstrap);
152173
try {

0 commit comments

Comments
 (0)