You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
11 KiB
290 lines
11 KiB
import org.apache.kafka.clients.admin.AdminClient;
|
|
import org.apache.kafka.clients.admin.AdminClientConfig;
|
|
import org.apache.kafka.clients.admin.DescribeClusterResult;
|
|
import org.apache.kafka.common.Node;
|
|
|
|
import java.io.*;
|
|
import java.net.*;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.*;
|
|
import java.util.concurrent.ExecutionException;
|
|
|
|
public class AdminClientDebugger {
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
String broker = args.length > 0 ? args[0] : "localhost:9093";
|
|
|
|
System.out.println("=".repeat(80));
|
|
System.out.println("KAFKA ADMINCLIENT DEBUGGER");
|
|
System.out.println("=".repeat(80));
|
|
System.out.println("Target broker: " + broker);
|
|
|
|
// Test 1: Raw socket - capture exact bytes
|
|
System.out.println("\n" + "=".repeat(80));
|
|
System.out.println("TEST 1: Raw Socket - Capture ApiVersions Exchange");
|
|
System.out.println("=".repeat(80));
|
|
testRawSocket(broker);
|
|
|
|
// Test 2: AdminClient with detailed logging
|
|
System.out.println("\n" + "=".repeat(80));
|
|
System.out.println("TEST 2: AdminClient with Logging");
|
|
System.out.println("=".repeat(80));
|
|
testAdminClient(broker);
|
|
}
|
|
|
|
private static void testRawSocket(String broker) {
|
|
String[] parts = broker.split(":");
|
|
String host = parts[0];
|
|
int port = Integer.parseInt(parts[1]);
|
|
|
|
try (Socket socket = new Socket(host, port)) {
|
|
socket.setSoTimeout(10000);
|
|
|
|
InputStream in = socket.getInputStream();
|
|
OutputStream out = socket.getOutputStream();
|
|
|
|
System.out.println("Connected to " + broker);
|
|
|
|
// Build ApiVersions request (v4)
|
|
// Format:
|
|
// [Size][ApiKey=18][ApiVersion=4][CorrelationId=0][ClientId][TaggedFields]
|
|
ByteArrayOutputStream requestBody = new ByteArrayOutputStream();
|
|
|
|
// ApiKey (2 bytes) = 18
|
|
requestBody.write(0);
|
|
requestBody.write(18);
|
|
|
|
// ApiVersion (2 bytes) = 4
|
|
requestBody.write(0);
|
|
requestBody.write(4);
|
|
|
|
// CorrelationId (4 bytes) = 0
|
|
requestBody.write(new byte[] { 0, 0, 0, 0 });
|
|
|
|
// ClientId (compact string) = "debug-client"
|
|
String clientId = "debug-client";
|
|
writeCompactString(requestBody, clientId);
|
|
|
|
// Tagged fields (empty)
|
|
requestBody.write(0x00);
|
|
|
|
byte[] request = requestBody.toByteArray();
|
|
|
|
// Write size
|
|
ByteBuffer sizeBuffer = ByteBuffer.allocate(4);
|
|
sizeBuffer.putInt(request.length);
|
|
out.write(sizeBuffer.array());
|
|
|
|
// Write request
|
|
out.write(request);
|
|
out.flush();
|
|
|
|
System.out.println("\nSENT ApiVersions v4 Request:");
|
|
System.out.println(" Size: " + request.length + " bytes");
|
|
hexDump(" Request", request, Math.min(64, request.length));
|
|
|
|
// Read response size
|
|
byte[] sizeBytes = new byte[4];
|
|
int read = in.read(sizeBytes);
|
|
if (read != 4) {
|
|
System.out.println("Failed to read response size (got " + read + " bytes)");
|
|
return;
|
|
}
|
|
|
|
int responseSize = ByteBuffer.wrap(sizeBytes).getInt();
|
|
System.out.println("\nRECEIVED Response:");
|
|
System.out.println(" Size: " + responseSize + " bytes");
|
|
|
|
// Read response body
|
|
byte[] responseBytes = new byte[responseSize];
|
|
int totalRead = 0;
|
|
while (totalRead < responseSize) {
|
|
int n = in.read(responseBytes, totalRead, responseSize - totalRead);
|
|
if (n == -1) {
|
|
System.out.println("Unexpected EOF after " + totalRead + " bytes");
|
|
return;
|
|
}
|
|
totalRead += n;
|
|
}
|
|
|
|
System.out.println(" Read complete response: " + totalRead + " bytes");
|
|
|
|
// Decode response
|
|
System.out.println("\nRESPONSE STRUCTURE:");
|
|
decodeApiVersionsResponse(responseBytes);
|
|
|
|
// Try to read more (should timeout or get EOF)
|
|
System.out.println("\n⏱️ Waiting for any additional data (10s timeout)...");
|
|
socket.setSoTimeout(10000);
|
|
try {
|
|
int nextByte = in.read();
|
|
if (nextByte == -1) {
|
|
System.out.println(" Server closed connection (EOF)");
|
|
} else {
|
|
System.out.println(" Unexpected data: " + nextByte);
|
|
}
|
|
} catch (SocketTimeoutException e) {
|
|
System.out.println(" Timeout - no additional data");
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
System.out.println("Error: " + e.getMessage());
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
private static void testAdminClient(String broker) {
|
|
Properties props = new Properties();
|
|
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, broker);
|
|
props.put(AdminClientConfig.CLIENT_ID_CONFIG, "admin-client-debugger");
|
|
props.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, 10000);
|
|
props.put(AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG, 10000);
|
|
|
|
System.out.println("Creating AdminClient with config:");
|
|
props.forEach((k, v) -> System.out.println(" " + k + " = " + v));
|
|
|
|
try (AdminClient adminClient = AdminClient.create(props)) {
|
|
System.out.println("AdminClient created");
|
|
|
|
// Give the thread time to start
|
|
Thread.sleep(1000);
|
|
|
|
System.out.println("\nCalling describeCluster()...");
|
|
DescribeClusterResult result = adminClient.describeCluster();
|
|
|
|
System.out.println(" Waiting for nodes...");
|
|
Collection<Node> nodes = result.nodes().get();
|
|
|
|
System.out.println("Cluster description retrieved:");
|
|
System.out.println(" Nodes: " + nodes.size());
|
|
for (Node node : nodes) {
|
|
System.out.println(" - Node " + node.id() + ": " + node.host() + ":" + node.port());
|
|
}
|
|
|
|
System.out.println("\n Cluster ID: " + result.clusterId().get());
|
|
|
|
Node controller = result.controller().get();
|
|
if (controller != null) {
|
|
System.out.println(" Controller: Node " + controller.id());
|
|
}
|
|
|
|
} catch (ExecutionException e) {
|
|
System.out.println("Execution error: " + e.getCause().getMessage());
|
|
e.getCause().printStackTrace();
|
|
} catch (Exception e) {
|
|
System.out.println("Error: " + e.getMessage());
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
private static void decodeApiVersionsResponse(byte[] data) {
|
|
int offset = 0;
|
|
|
|
try {
|
|
// Correlation ID (4 bytes)
|
|
int correlationId = ByteBuffer.wrap(data, offset, 4).getInt();
|
|
System.out.println(" [Offset " + offset + "] Correlation ID: " + correlationId);
|
|
offset += 4;
|
|
|
|
// Header tagged fields (varint - should be 0x00 for flexible v3+)
|
|
int taggedFieldsLength = readUnsignedVarint(data, offset);
|
|
System.out.println(" [Offset " + offset + "] Header Tagged Fields Length: " + taggedFieldsLength);
|
|
offset += varintSize(data[offset]);
|
|
|
|
// Error code (2 bytes)
|
|
short errorCode = ByteBuffer.wrap(data, offset, 2).getShort();
|
|
System.out.println(" [Offset " + offset + "] Error Code: " + errorCode);
|
|
offset += 2;
|
|
|
|
// API Keys array (compact array - varint length)
|
|
int apiKeysLength = readUnsignedVarint(data, offset) - 1; // Compact array: length+1
|
|
System.out.println(" [Offset " + offset + "] API Keys Count: " + apiKeysLength);
|
|
offset += varintSize(data[offset]);
|
|
|
|
// Show first few API keys
|
|
System.out.println(" First 5 API Keys:");
|
|
for (int i = 0; i < Math.min(5, apiKeysLength); i++) {
|
|
short apiKey = ByteBuffer.wrap(data, offset, 2).getShort();
|
|
offset += 2;
|
|
short minVersion = ByteBuffer.wrap(data, offset, 2).getShort();
|
|
offset += 2;
|
|
short maxVersion = ByteBuffer.wrap(data, offset, 2).getShort();
|
|
offset += 2;
|
|
// Per-element tagged fields
|
|
int perElementTagged = readUnsignedVarint(data, offset);
|
|
offset += varintSize(data[offset]);
|
|
|
|
System.out.println(" " + (i + 1) + ". API " + apiKey + ": v" + minVersion + "-v" + maxVersion);
|
|
}
|
|
|
|
System.out.println(" ... (showing first 5 of " + apiKeysLength + " APIs)");
|
|
System.out.println(" Response structure is valid!");
|
|
|
|
// Hex dump of first 64 bytes
|
|
hexDump("\n First 64 bytes", data, Math.min(64, data.length));
|
|
|
|
} catch (Exception e) {
|
|
System.out.println(" Failed to decode at offset " + offset + ": " + e.getMessage());
|
|
hexDump(" Raw bytes", data, Math.min(128, data.length));
|
|
}
|
|
}
|
|
|
|
private static int readUnsignedVarint(byte[] data, int offset) {
|
|
int value = 0;
|
|
int shift = 0;
|
|
while (true) {
|
|
byte b = data[offset++];
|
|
value |= (b & 0x7F) << shift;
|
|
if ((b & 0x80) == 0)
|
|
break;
|
|
shift += 7;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
private static int varintSize(byte firstByte) {
|
|
int size = 1;
|
|
byte b = firstByte;
|
|
while ((b & 0x80) != 0) {
|
|
size++;
|
|
b = (byte) (b << 1);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
private static void writeCompactString(ByteArrayOutputStream out, String str) {
|
|
byte[] bytes = str.getBytes();
|
|
writeUnsignedVarint(out, bytes.length + 1); // Compact string: length+1
|
|
out.write(bytes, 0, bytes.length);
|
|
}
|
|
|
|
private static void writeUnsignedVarint(ByteArrayOutputStream out, int value) {
|
|
while ((value & ~0x7F) != 0) {
|
|
out.write((byte) ((value & 0x7F) | 0x80));
|
|
value >>>= 7;
|
|
}
|
|
out.write((byte) value);
|
|
}
|
|
|
|
private static void hexDump(String label, byte[] data, int length) {
|
|
System.out.println(label + " (hex dump):");
|
|
for (int i = 0; i < length; i += 16) {
|
|
System.out.printf(" %04x ", i);
|
|
for (int j = 0; j < 16; j++) {
|
|
if (i + j < length) {
|
|
System.out.printf("%02x ", data[i + j] & 0xFF);
|
|
} else {
|
|
System.out.print(" ");
|
|
}
|
|
if (j == 7)
|
|
System.out.print(" ");
|
|
}
|
|
System.out.print(" |");
|
|
for (int j = 0; j < 16 && i + j < length; j++) {
|
|
byte b = data[i + j];
|
|
System.out.print((b >= 32 && b < 127) ? (char) b : '.');
|
|
}
|
|
System.out.println("|");
|
|
}
|
|
}
|
|
}
|