DUE TO SPAM, SIGN-UP IS DISABLED. Goto Selfserve wiki signup and request an account.
...
However, the essential information for these operations – the "directory UUID" and the "endpoints" – can be obtained through the Admin API.
...
active controller in-memory state and ClusterImage.
By leveraging these APIs, we can simplify the process of adding/removing voters, allowing the command to be executed without direct access to the target node's metadata directory.
...
The kafka-metadata-quorum.sh tool introduces a new option —-controller-id for the add-controller subcommand.
Adding a controller
...
For adding a controller, only --controller-id is needed option.
| Code Block | ||||
|---|---|---|---|---|
| ||||
bin/kafka-metadata-quorum.sh --bootstrap-server localhost:9092 add-controller --controller-id <id> |
| Code Block | ||||
|---|---|---|---|---|
| ||||
bin/kafka-metadata-quorum.sh --bootstrap-controller localhost:9093 add-controller --controller-id <id> |
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
bin/kafka-metadata-quorum.sh --bootstrap-controller localhost:9093 remove-controller --controller-id <id> |
Public APIs
Admin.java
| Code Block | ||||
|---|---|---|---|---|
| ||||
/**
* Add a new voter node to the KRaft metadata quorum.
* Note that this is a convenient method for simple deployment, and not idempotent.
* For a complicated scenario, e.g., Node Disk Failure, there might have
* different directory uuid with same node id, in this scenario, please
* go with {@link #addRaftVoter(int, Uuid, Set)}.
*
* @param voterId The node ID of the voter.
*/
default AddRaftVoterResult addRaftVoter(int voterId) {
return addRaftVoter(voterId, UUID.zero_uuid, null, new AddRaftVoterOptions());
}
/**
* Remove a voter node from the KRaft metadata quorum.
*
* @param voterId The node ID of the voter.
*/
default RemoveRaftVoterResult removeRaftVoter(int voterId) {
return removeRaftVoter(voterId, UUID.zero_uuid, new RemoveRaftVoterOptions());
} |
RPC Changes
AddRaftVoterRequest.json
| Code Block | ||
|---|---|---|
| ||
diff --git a/clients/src/main/resources/common/message/AddRaftVoterRequest.json b/clients/src/main/resources/common/message/AddRaftVoterRequest.json
index 74b7638ea2..27a6e5face 100644
--- a/clients/src/main/resources/common/message/AddRaftVoterRequest.json
+++ b/clients/src/main/resources/common/message/AddRaftVoterRequest.json
@@ -18,7 +18,7 @@
"type": "request",
"listeners": ["controller", "broker"],
"name": "AddRaftVoterRequest",
- "validVersions": "0",
+ "validVersions": "0-2",
"flexibleVersions": "0+",
"fields": [
{ "name": "ClusterId", "type": "string", "versions": "0+", "nullableVersions": "0+",
@@ -27,9 +27,9 @@
"about": "The maximum time to wait for the request to complete before returning."},
{ "name": "VoterId", "type": "int32", "versions": "0+",
"about": "The replica id of the voter getting added to the topic partition." },
- { "name": "VoterDirectoryId", "type": "uuid", "versions": "0+",
+ { "name": "VoterDirectoryId", "type": "uuid", "versions": "0+", "nullableVersions": "1+",
"about": "The directory id of the voter getting added to the topic partition." },
- { "name": "Listeners", "type": "[]Listener", "versions": "0+",
+ { "name": "Listeners", "type": "[]Listener", "versions": "0+", "nullableVersions": "1+",
"about": "The endpoints that can be used to communicate with the voter.", "fields": [
{ "name": "Name", "type": "string", "versions": "0+", "mapKey": true,
"about": "The name of the endpoint." }, |
RemoveRaftVoterRequest.json
| Code Block | ||
|---|---|---|
| ||
diff --git a/clients/src/main/resources/common/message/RemoveRaftVoterRequest.json b/clients/src/main/resources/common/message/RemoveRaftVoterRequest.json
index 7d11086e53..2181ecd9ff 100644
--- a/clients/src/main/resources/common/message/RemoveRaftVoterRequest.json
+++ b/clients/src/main/resources/common/message/RemoveRaftVoterRequest.json
@@ -18,14 +18,14 @@
"type": "request",
"listeners": ["controller", "broker"],
"name": "RemoveRaftVoterRequest",
- "validVersions": "0",
+ "validVersions": "0-1",
"flexibleVersions": "0+",
"fields": [
{ "name": "ClusterId", "type": "string", "versions": "0+", "nullableVersions": "0+",
"about": "The cluster id of the request."},
{ "name": "VoterId", "type": "int32", "versions": "0+",
"about": "The replica id of the voter getting removed from the topic partition." },
- { "name": "VoterDirectoryId", "type": "uuid", "versions": "0+",
+ { "name": "VoterDirectoryId", "type": "uuid", "versions": "0+", "nullableVersions": "1+",
"about": "The directory id of the voter getting removed from the topic partition." }
]
} |
Proposed Changes
Make both controller Directory UUID and Endpoints optional
We’ll handle both fields on the server side since the information already exists:
Directory ID: derive from in-memory
LeaderState.Controller endpoints: derive from the
ClusterImage.
Because the ClusterImage may lag behind, using it for endpoints is not strictly idempotent. To reflect that, Admin.java introduces two convenience methods for add/remove controller that include prominent warnings in javadoc so users understand the risk and only use them when appropriate.
Upon server-side changes live in KafkaRaftClient.java:
handleAddVoterRequesthandleRemoveVoterRequest
Error handling
During add raft voter, detecting multiple observers sharing the same node id — the request is rejected with a clear error IllegalStateException indicating the duplicate node id and instructing the user to resolve the conflict before retrying.
MetadataQuorumCommand add-controller changes
Add a new option —-controller-id to add-controller subcommand.
| Code Block | ||||
|---|---|---|---|---|
| ||||
addControllerParser
.addArgument("--controller-id", "-i")
.help("The id of the controller to add. This option should be used with bootstrap controller.")
.type(Integer.class)
.action(Arguments.store()); |
- When using -If
—-controller-idwith--bootstrap-controller, the MetadataQuorumCommand will:Retrieve the directory UUID via
Admin#describeMetadataQuorum.Fetch the required endpoints via
Admin#describeConfigsmethod (usingbootstrap.controller)
If
—-command-configis provided, fallback to the existing behavior, and—-controller-idoption will be ignored.- is provided, invoke new method Admin#addRaftVoter(int)
If
—-command-configand—-controller-idare both provided, the config file provided by --command-config will only be applied in Admin client initialization.- the description for --command-config will be changed to "Property file containing configs to be passed to Admin Client. For add-controller, the file is used to specify the controller properties as well unless --controller-id is provided."
If neither --If neither command-config nor controller-id with bootstrap-controller is provided, an exception will be thrown:
throw new TerseException("You must supply the configuration file of the controller you are adding when using add-controller, or either using controller id with bootstrap controller use --command-config or --controller-id option to add a controller.");
MetadataQuorumCommand remove-controller changes
| Code Block | ||||
|---|---|---|---|---|
| ||||
diff --git a/tools/src/main/java/org/apache/kafka/tools/MetadataQuorumCommand.java b/tools/src/main/java/org/apache/kafka/tools/MetadataQuorumCommand.java
index dba7951aa4..f3bdbbeffa 100644
--- a/tools/src/main/java/org/apache/kafka/tools/MetadataQuorumCommand.java
+++ b/tools/src/main/java/org/apache/kafka/tools/MetadataQuorumCommand.java
@@ -471,7 +471,6 @@ public class MetadataQuorumCommand {
removeControllerParser
.addArgument("--controller-directory-id", "-d")
.help("The directory ID of the controller to remove.")
- .required(true)
.action(Arguments.store()); |
The
—-controller_-directory-idis no longer required. We can useAdmin#describeMetadataQuorummethod to get the controller directory UUID., we can leverage on the new method Admin#removeRaftVoter(int)If
—-controller_-directory-idis explicitly provided, it will be used directly, andAdmin#describeMetadataQuorumwill not be called.invoke Admin#removeRaftVoter(int, Uuid)
Compatibility, Deprecation, and Migration Plan
...
The
—-command-configoption remains available inadd-controller.The
--controller-directory-idoption inremove-controlleris now optional but still supported.
This KIP introduces new methods in Admin.java and introduce nullable fields for 2 RPCs with no breaking change.
Test Plan
New test cases will be added to MetadataQuorumCommandTest.java to validate:
Adding a controller with
--controller-id.Removing a controller without explicitly providing
--controller-directory-id.
Will also add new integration tests for the two new methods in Admin.java.
Rejected Alternatives
- Deprecate
—-command-configoption inadd-controllerand--controller-directory-idoption inremove-controller.The main reason not to deprecate these two parameters is that they were only just introduced in 4.0, so deprecating them in a 4.x release feels a bit too soon. Also, the
--command-configcan be used in a different user scenario, where the user can still provide the configuration file toadd-controllerif they already have it locally. Use admin APIs to get directory UUID and controller endpoints, this brings extra network communication overhead.
- The
Admin#describeMetadataQuorummethod can provide the directory UUID. The
Admin#describeConfigsmethod, utilizing thebootstrap.controlleraddress, can be used to retrieve the necessary endpoints.
- The