-
Notifications
You must be signed in to change notification settings - Fork 14.7k
KAFKA-19719: --no-initial-controllers should not assume kraft.version=1 #20551
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Conversation
…ing setting kraft version
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we consider deprecating --no-initial-controllers
? It seems redundant and has no effect for either dynamic or static voter configurations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to @brandboat
After this patch, it seems that the --no-initial-controller flag doesn't provide too much value.
@kevin-wu24 thanks for this patch.
Excuse me, what is the purpose of having both controller.quorum.voters and --no-initial-controllers when starting a static quorum cluster? I assumed --no-initial-controllers is used only in building a dynamic quorum cluster. |
Thanks for the reviews @chia7712 @brandboat @frankvicky. Hopefully I can clear up the intended semantics of
No, we should not deprecate Actually, when we specify
|
…me, cleanup of kafka cluster test kit
StringBuilder quorumVoterStringBuilder = new StringBuilder(); | ||
String prefix = ""; | ||
for (int nodeId : nodes.controllerNodes().keySet()) { | ||
quorumVoterStringBuilder.append(prefix). | ||
append(nodeId). | ||
append("@"). | ||
append("localhost"). | ||
append(":"). | ||
append(socketFactoryManager.getOrCreatePortForListener(nodeId, controllerListenerName)); | ||
prefix = ","; | ||
if (!standalone && initialVoterSet.isEmpty()) { | ||
StringBuilder quorumVoterStringBuilder = new StringBuilder(); | ||
String prefix = ""; | ||
for (int nodeId : nodes.controllerNodes().keySet()) { | ||
quorumVoterStringBuilder.append(prefix). | ||
append(nodeId). | ||
append("@"). | ||
append("localhost"). | ||
append(":"). | ||
append(socketFactoryManager.getOrCreatePortForListener(nodeId, controllerListenerName)); | ||
prefix = ","; | ||
} | ||
props.put(QuorumConfig.QUORUM_VOTERS_CONFIG, quorumVoterStringBuilder.toString()); | ||
} else { | ||
StringBuilder bootstrapServersStringBuilder = new StringBuilder(); | ||
String prefix = ""; | ||
for (int nodeId : nodes.controllerNodes().keySet()) { | ||
bootstrapServersStringBuilder.append(prefix). | ||
append("localhost"). | ||
append(":"). | ||
append(socketFactoryManager.getOrCreatePortForListener(nodeId, controllerListenerName)); | ||
prefix = ","; | ||
} | ||
props.put(QuorumConfig.QUORUM_BOOTSTRAP_SERVERS_CONFIG, bootstrapServersStringBuilder.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the integration test framework to better mirror a real cluster for dynamic quorum (i.e. voters config should not be specified and we should use bootstrap servers)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for fixing this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kevin-wu24 thanks for this patch. Should we backport this fix to 4.0 and 4.1?
(Option(initialControllers).isDefined || isStandalone)) { | ||
val staticVotersEmpty = config.quorumConfig.voters().isEmpty | ||
formatter.setHasDynamicQuorum(staticVotersEmpty) | ||
if (!staticVotersEmpty && (Option(initialControllers).isDefined || isStandalone)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still feel it is weird that users configure controller.quorum.voters
with --no-initial-controllers
, but you are right that we should keep the compatibility.
Should we add another warning to remind users that --no-initial-controllers
should not be used with static voters even though it is no-op?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add another warning to remind users that --no-initial-controllers should not be used with static voters even though it is no-op?
I can add it to the flag's help output.
new TestKitNodes.Builder(). | ||
setNumBrokerNodes(1). | ||
setNumControllerNodes(1). | ||
setFeature(KRaftVersion.FEATURE_NAME, KRaftVersion.KRAFT_VERSION_1.featureLevel()). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the method setFeature
is useless now. Could you please remove it?
); | ||
formatter.setInitialControllers(DynamicVoters.parse(dynamicVotersBuilder.toString())); | ||
} | ||
formatter.setHasDynamicQuorum(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add a clarifying comment to explain the actual case here? For example, the nodeId != CONTROLLER_ID_OFFSET
means the controller nodes are using --no-initial-controllers
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't under the added comment. This execute when standalone. Irrespective of the node id. Doesn't that mean --standalone and --no-initial-controllers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's what the comment is saying. One node formats with --standalone and the others are formatting with --no-initial-controllers. For both arguments we need to call formatter.setHasDynamicQuorum(true);
for this configuration.
Thanks for the review @chia7712.
Yeah, we should backport it since it affects both versions. |
I send a message to 4.0.1 RC thread (https://lists.apache.org/thread/4gsgcdgybyrnwg3hodnlyxg33ckj30wy). wait for the response from @clolov |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the fix. Leave some minor comments.
"--no-initial-controllers." | ||
"Cannot set kraft.version to " + configuredKRaftVersionLevel.get() + | ||
" unless controller.quorum.voters is empty and one of the flags --standalone, " + | ||
"--initial-controllers, or -no-initial-controllers is used. " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: -no-initial-controllers
-> --no-initial-controllers
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, will fix
"--no-initial-controllers is used. For dynamic controllers support, try using one of " + | ||
"--standalone, --initial-controllers, or --no-initial-controllers.", | ||
"Cannot set kraft.version to 1 unless controller.quorum.voters is empty and " + | ||
"one of the flags --standalone, --initial-controllers, or -no-initial-controllers is used. " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
"--no-initial-controllers is used. For dynamic controllers support, try using one of " + | ||
"--standalone, --initial-controllers, or --no-initial-controllers.", | ||
"Cannot set kraft.version to 1 unless controller.quorum.voters is empty and " + | ||
"one of the flags --standalone, --initial-controllers, or -no-initial-controllers is used. " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the changes @kevin-wu24
if (config.quorumConfig.voters().isEmpty && formatter.initialVoters().isEmpty) { | ||
if (!namespace.getBoolean("no_initial_controllers") && | ||
config.processRoles.contains(ProcessRole.ControllerRole) && | ||
staticVotersEmpty && formatter.initialVoters().isEmpty) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add a newline after &&
and before formatter.initialVoters().isEmpt
.
|
||
reconfigurableQuorumOptions.addArgument("--no-initial-controllers", "-N") | ||
.help("Used to initialize a server without a dynamic quorum topology.") | ||
.help("Used to initialize a server without a dynamic quorum topology. When setting this flag, " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove the word topology. It matches the phrase used for --standalone.
.action(storeTrue()) | ||
|
||
reconfigurableQuorumOptions.addArgument("--initial-controllers", "-I") | ||
.help("Used to initialize a server with a specific dynamic quorum topology. The argument " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. Do you mind removing the word topology?
public Formatter setHasDynamicQuorum(boolean staticVotersEmpty) { | ||
this.hasDynamicQuorum = staticVotersEmpty; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parameter name should be something like hasDynamicQuorum
.
"--no-initial-controllers." | ||
"Cannot set kraft.version to " + configuredKRaftVersionLevel.get() + | ||
" unless controller.quorum.voters is empty and one of the flags --standalone, " + | ||
"--initial-controllers, or --no-initial-controllers is used. " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra space after --no-initial-controllers
FormatterContext formatter1 = testEnv.newFormatter(); | ||
formatter1.formatter.setReleaseVersion(MetadataVersion.IBP_3_8_IV0); | ||
// This MV does not support kraft.version = 1 | ||
formatter1.formatter.setHasDynamicQuorum(emptyStaticVoters); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit odd. Why not name the test parameter hasDynamicQuorum
?
if (emptyStaticVoters) { | ||
assertEquals((short) 1, formatter1.formatter.featureLevels.getOrDefault("kraft.version", (short) 0)); | ||
} else { | ||
assertEquals((short) 0, formatter1.formatter.featureLevels.getOrDefault("kraft.version", (short) 1)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you want to differentiate between missing value and the default in the test? Why not use featureLevels.get("kraft.version")
? This comment applies to a few places in this file.
StringBuilder quorumVoterStringBuilder = new StringBuilder(); | ||
String prefix = ""; | ||
for (int nodeId : nodes.controllerNodes().keySet()) { | ||
quorumVoterStringBuilder.append(prefix). | ||
append(nodeId). | ||
append("@"). | ||
append("localhost"). | ||
append(":"). | ||
append(socketFactoryManager.getOrCreatePortForListener(nodeId, controllerListenerName)); | ||
prefix = ","; | ||
if (!standalone && initialVoterSet.isEmpty()) { | ||
StringBuilder quorumVoterStringBuilder = new StringBuilder(); | ||
String prefix = ""; | ||
for (int nodeId : nodes.controllerNodes().keySet()) { | ||
quorumVoterStringBuilder.append(prefix). | ||
append(nodeId). | ||
append("@"). | ||
append("localhost"). | ||
append(":"). | ||
append(socketFactoryManager.getOrCreatePortForListener(nodeId, controllerListenerName)); | ||
prefix = ","; | ||
} | ||
props.put(QuorumConfig.QUORUM_VOTERS_CONFIG, quorumVoterStringBuilder.toString()); | ||
} else { | ||
StringBuilder bootstrapServersStringBuilder = new StringBuilder(); | ||
String prefix = ""; | ||
for (int nodeId : nodes.controllerNodes().keySet()) { | ||
bootstrapServersStringBuilder.append(prefix). | ||
append("localhost"). | ||
append(":"). | ||
append(socketFactoryManager.getOrCreatePortForListener(nodeId, controllerListenerName)); | ||
prefix = ","; | ||
} | ||
props.put(QuorumConfig.QUORUM_BOOTSTRAP_SERVERS_CONFIG, bootstrapServersStringBuilder.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for fixing this.
); | ||
formatter.setInitialControllers(DynamicVoters.parse(dynamicVotersBuilder.toString())); | ||
} | ||
formatter.setHasDynamicQuorum(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't under the added comment. This execute when standalone. Irrespective of the node id. Doesn't that mean --standalone and --no-initial-controllers?
public Builder setFeature(String featureName, short level) { | ||
this.bootstrapMetadata = bootstrapMetadata.copyWithFeatureRecord(featureName, level); | ||
return this; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for finally removing this configuration.
.help("Used to initialize a server without a dynamic quorum topology. When setting this flag, " + | ||
.help("Used to initialize a controller without specifying a dynamic quorum. When setting this flag, " + | ||
"the controller.quorum.voters config should not be set, and controller.quorum.bootstrap.servers is set instead.") | ||
.action(storeTrue()) | ||
|
||
reconfigurableQuorumOptions.addArgument("--initial-controllers", "-I") | ||
.help("Used to initialize a server with a specific dynamic quorum topology. The argument " + | ||
.help("Used to initialize a controller with the specified dynamic quorum. The argument " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--standalone can only be used on the controller node but --no-initial-controllers and --initial-controllers can be used in the controller and broker nodes.
I.e. you can change the message back to using the word server instead of the word controller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kevin-wu24 thanks for this patch. just a couple of small comments left
Option(initialControllers). | ||
foreach(v => formatter.setInitialControllers(DynamicVoters.parse(v))) | ||
if (isStandalone) { | ||
formatter.setInitialControllers(createStandaloneDynamicVoters(config)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that --initial-controllers
becomes a no-op when --standalone
is defined. Should we add a warning for this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look at the dynamic quorum arguments, you cannot specify multiple at the same time:
val reconfigurableQuorumOptions = formatParser.addMutuallyExclusiveGroup()
configuredKRaftVersionLevel.get() + | ||
" if one of the flags --standalone, --initial-controllers, or --no-initial-controllers is used. " + | ||
"For dynamic controllers support, try removing the --feature flag for kraft.version." | ||
"Cannot set kraft.version to " + configuredKRaftVersionLevel.get() + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind using 0
instead of configuredKRaftVersionLevel.get()
? for example:
throw new FormatterException(
"Cannot set kraft.version to 0 if controller.quorum.voters is empty and one of the flags --standalone, " +
"--initial-controllers, or --no-initial-controllers is used. For dynamic controllers support, " +
"try removing the --feature flag for kraft.version."
);
private static final int DEFAULT_NODE_ID = 1; | ||
|
||
private static final Uuid DEFAULT_CLUSTER_ID = Uuid.fromString("b3dGE68sQQKzfk80C_aLZw"); | ||
private static final String KRAFT_VERSION = "kraft.version"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could it be replaced by KRaftVersion.FEATURE_NAME
?
StringBuilder dynamicVotersBuilder = new StringBuilder(); | ||
String prefix = ""; | ||
if (standalone) { | ||
if (nodeId == TestKitDefaults.CONTROLLER_ID_OFFSET) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The true port of the standalone controller is TestKitDefaults.BROKER_ID_OFFSET + TestKitDefaults.CONTROLLER_ID_OFFSET
. We will likely not change the value of BROKER_ID_OFFSET
in the future, but should we still use BROKER_ID_OFFSET + CONTROLLER_ID_OFFSET
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'll change it just in case we ever change BROKER_ID_OFFSET
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kevin-wu24 thanks for updates. LGTM
@jsancio Did you get around to reviewing it?
Just because a controller node sets
--no-initial-controllers
flag doesnot mean it is necessarily running kraft.version=1. The more precise
meaning is that the controller node being formatted does not know what
kraft version the cluster should be in, and therefore it is only safe to
assume kraft.version=0. Only by setting
--standalone
,--initial-controllers
, or--no-initial-controllers
AND not specifying the
controller.quorum.voters
static config, is itknown
kraft.version > 0
.For example, it is a valid configuration (although confusing) to run a
static quorum definedby
controller.quorum.voters
but have all thecontrollers format with
--no-initial-controllers
. In this case,specifying
--no-initial-controllers
alongside a metadata version thatdoes not support
kraft.version=1
causes formatting to fail, which isa regression.
Additionally, the formatter should not check the kraft.version against
the release version, since kraft.version does not actually depend on any
release version. It should only check the kraft.version against the
static voters config/format arguments.
This PR also cleans up the integration test framework to match the
semantics of formatting an actual cluster.
Reviewers: TengYao Chi kitingiao@gmail.com, Kuan-Po Tseng
brandboat@gmail.com, Chia-Ping Tsai chia7712@gmail.com