Skip to content
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

KAFKA-16518; Adding standalone argument for storage #16325

Closed
wants to merge 15 commits into from

Conversation

muralibasani
Copy link
Contributor

Resolves : https://issues.apache.org/jira/browse/KAFKA-16518

Adds a new argument "standalone" to kafka-storage.sh
If standalone mode, creates a checkpoint file in metadata dir ${kafkaConfig.metadataLogDir}/__cluster_metadata-0/

Committer Checklist (excluded from commit message)

  • Verify design and implementation
  • Verify test coverage and CI build status
  • Verify documentation (including upgrade notes)

@muralibasani muralibasani marked this pull request as ready for review June 13, 2024 16:52
@cmccabe cmccabe added the KIP-853 KRaft Controller Membership Changes label Jun 13, 2024
@jsancio jsancio added the kraft label Jun 14, 2024
@muralibasani
Copy link
Contributor Author

@jsancio Output looks like below.

./bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c config/kraft/server.properties -s
metaPropertiesEnsemble=MetaPropertiesEnsemble(metadataLogDir=Optional.empty, dirs={/tmp/kraft-combined-logs: EMPTY})
Snapshot written to /tmp/kraft-combined-logs/__cluster_metadata-0
Formatting /tmp/kraft-combined-logs with metadata.version 3.7-IV4.

% cat /tmp/kraft-combined-logs/__cluster_metadata-0/00000000000000000000-0000000000.checkpoint
�[�� �%�%��������������*
l�#�� �ﱃS�=��7
PLAINTEXT
localhost#�?)��� �% �% �������������%

Copy link
Member

@jsancio jsancio left a 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 @muralibasani . We should also implement --controller-quorum-voters in this PR as it will inform the signature needed to implement both flags.

core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/test/scala/unit/kafka/tools/StorageToolTest.scala Outdated Show resolved Hide resolved
@jsancio
Copy link
Member

jsancio commented Jun 17, 2024

cat /tmp/kraft-combined-logs/__cluster_metadata-0/00000000000000000000-0000000000.checkpoint

@muralibasani You can use bin/kafka-dump-log --cluster-metadata-decoder --files /tmp/kraft-combined-logs/__cluster_metadata-0/00000000000000000000-0000000000.checkpoint.

@jsancio jsancio changed the title KAFKA-16518 : Adding standalone argument for storage KAFKA-16518; Adding standalone argument for storage Jun 17, 2024
@jsancio jsancio self-assigned this Jun 17, 2024
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
@muralibasani
Copy link
Contributor Author

@jsancio , updated with controller option

./bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c config/kraft/server.properties -q 1@localhost:9093
metaPropertiesEnsemble=MetaPropertiesEnsemble(metadataLogDir=Optional.empty, dirs={/tmp/kraft-combined-logs: EMPTY})
Formatting /tmp/kraft-combined-logs with metadata.version 3.8-IV0.
Snapshot written to /tmp/kraft-combined-logs/__cluster_metadata


muralidharbasani@Muralidhars-MacBook-Pro kafka % ./bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c config/kraft/server.properties -s                 
metaPropertiesEnsemble=MetaPropertiesEnsemble(metadataLogDir=Optional.empty, dirs={/tmp/kraft-combined-logs: EMPTY})
Formatting /tmp/kraft-combined-logs with metadata.version 3.8-IV0.
Snapshot written to /tmp/kraft-combined-logs/__cluster_metadata


muralidharbasani@Muralidhars-MacBook-Pro kafka % ./bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c config/kraft/server.properties -q 1@localhost:9093 -s
Both --standalone and --controller-quorum-voters were set. Only one of the two flags can be set.

@muralibasani muralibasani requested review from jsancio and ahuang98 July 1, 2024 16:25
Copy link
Member

@jsancio jsancio left a 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.

if (!validateControllerQuorumVoters(controllersQuorumVoters)) {
throw new TerseFailure("Expected schema for --controller-quorum-voters is <replica-id>[-<replica-directory-id>]@<host>:<port>")
}
advertisedListenerEndpoints = config.effectiveAdvertisedControllerListeners
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct, right? We need to parse the string that you validated in validateControllerQuorumVoters(controllersQuorumVoters).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
core/src/main/scala/kafka/tools/StorageTool.scala Outdated Show resolved Hide resolved
Comment on lines 560 to 561
advertisedListenerEndpoints: scala.collection.Seq[kafka.cluster.EndPoint],
controllersQuorumVoters: String
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider making the input to this method just voters: VoterSet instead of advertisedListenerEndpoints and controllerQuorrumVoters.

When the --standalone flag is used the set of voters can be constructed from VoterSet.fromMap(createStandaloneVoterMap(nodeId, nodeDirectoryId, config.effectiveAdvertisedControllerListeners)).

When the --controller-quorum-voters flag is used the set of voters can be constructed from VoterSet.fromMap(parseVotersToMap(controllersQuorumVoters))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried considering but directoryId is derived only in format method. So updated format method with only listeners param.

}

if (standaloneMode) {
advertisedListenerEndpoints = config.effectiveAdvertisedBrokerListeners
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bootstrapping checkpoint should only be generated on controllers (config.processRoles.contains(ProcessRole.ControllerRole)). If the node is a broker only then the bootstrapping checkpoint should not get generated.

@muralibasani muralibasani requested a review from jsancio July 4, 2024 12:33
Comment on lines 121 to 129
var listeners: util.Map[ListenerName, InetSocketAddress] = new util.HashMap()
if (standaloneMode) {
advertisedListenerEndpoints = config.effectiveAdvertisedBrokerListeners
listeners = createStandaloneVoterMap(config)
} else if(controllersQuorumVoters != null) {
if (!validateControllerQuorumVoters(controllersQuorumVoters)) {
throw new TerseFailure("Expected schema for --controller-quorum-voters is <replica-id>[-<replica-directory-id>]@<host>:<port>")
}
advertisedListenerEndpoints = config.effectiveAdvertisedControllerListeners
val controllerQuorumVoterMap: util.Map[Integer, InetSocketAddress] = parseVoterConnections(Collections.singletonList(controllersQuorumVoters))
listeners = parseControllerQuorumVotersMap(controllerQuorumVoterMap, metaProperties, config)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look correct. VoterSet is basically a Map[Integer, (Uuid, Map[ListenerName, InetSocketAddress])] where Integer is the replica id and Uuid is the replica directory id. The type for listener doesn't contain all of the information needed to generate a VoterSet for all the possible configuration cases.

Note that the value for --controller-quorum-voters has the follow schema: <replica-id>-<replica-directory-id>@<host>:<port>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means we would have to update

public static Map<Integer, InetSocketAddress> parseVoterConnections(List<String> voterEntries) {
to return Uuid too ?

@@ -199,7 +200,7 @@ object StorageTool extends Logging {
val level: java.lang.Short = specifiedFeatures.getOrElse(feature.featureName, feature.defaultValue(metadataVersionForDefault))
// Only set feature records for levels greater than 0. 0 is assumed if there is no record. Throw an error if level < 0.
if (level != 0) {
allNonZeroFeaturesAndLevels.append(feature.fromFeatureLevel(level, unstableFeatureVersionsEnabled))
allNonZeroFeaturesAndLevels.append(feature.fromFeatureLevel(level, unstableFeatureVersionsEnabled))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we are missing a space.

) : UserScramCredentialRecord = {
mechanism: String,
config: String
) : UserScramCredentialRecord = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra space between ) and :. It should be ): User... = {

Comment on lines 313 to 316
val argMap = config.substring(1, config.length - 1)
.split(",")
.map(_.split("=(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"))
.map(args => args(0) -> args(1).replaceAll("\"", "")).toMap
.split(",")
.map(_.split("=(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"))
.map(args => args(0) -> args(1).replaceAll("\"", "")).toMap
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this is not the correct indentation.

Comment on lines 338 to 342
throw new TerseFailure(s"The 'iterations' value must be >= ${scramMechanism.minIterations()} for add-scram")
throw new TerseFailure(s"The 'iterations' value must be >= ${scramMechanism.minIterations()} for add-scram")
}
if (iterations > scramMechanism.maxIterations()) {
throw new TerseFailure(s"The 'iterations' value must be <= ${scramMechanism.maxIterations()} for add-scram")
throw new TerseFailure(s"The 'iterations' value must be <= ${scramMechanism.maxIterations()} for add-scram")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you have extra two spaces!

Comment on lines +676 to +678
private def parseControllerQuorumVotersMap(controllerQuorumVoterMap: util.Map[Integer, InetSocketAddress],
metaProperties: MetaProperties,
config: KafkaConfig): util.Map[ListenerName, InetSocketAddress] = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect indentation:

  private def parseControllerQuorumVotersMap(
    controllerQuorumVoterMap: util.Map[Integer, InetSocketAddress],
    metaProperties: MetaProperties,
    config: KafkaConfig
): util.Map[ListenerName, InetSocketAddress] = {

Aren't you losing information if you map from util.Map[Integer, InetSocketAddress] to util.Map[ListenerName, InetSocketAddress]? Why are you removing replicas that are not the local replica? The VoterSet must contain all of the voters in --controller-quorum-voters not just the local replica.

Why would Kafka require all of the voters in --controller-quorum-voters to only use the local voter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the kip description
"When the format command is executed with this option it will read the node.id configured in the properties file specified by the --config option and compare it against the specified in --controller-quorum-voters. If there is a match, it will write the specified to the directory.id property in the meta.properties for the metadata.log.dir directory."

I tried adding the if condition
if (metaProperties.nodeId().getAsInt == replicaId) )

in the method

May be am wrong. Can you pls suggest code maybe?

val listeners: util.Map[ListenerName, InetSocketAddress] = new util.HashMap()
controllerQuorumVoterMap.keySet().forEach(replicaId => {
if (metaProperties.nodeId().getAsInt == replicaId) {
val listenerNameOption = config.effectiveAdvertisedControllerListeners.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should assume that the listener name is the default listener name. The default listener name is the first listener in https://github.com/apache/kafka/blob/trunk/core/src/main/scala/kafka/server/KafkaConfig.scala#L730-L737.

There is also this code for an example of getting the first listener name: https://github.com/apache/kafka/blob/trunk/core/src/main/scala/kafka/raft/RaftManager.scala#L267

There is one validation that we should do for the local replica. The local replica's default listener (name, host and port) matches the entry specified in --controller-quorum-voters.

Comment on lines 345 to 344
"16 bytes of a base64-encoded UUID", assertThrows(classOf[TerseFailure],
() => StorageTool.buildMetadataProperties("invalid", config)).getMessage)
"16 bytes of a base64-encoded UUID", assertThrows(classOf[TerseFailure],
() => StorageTool.buildMetadataProperties("invalid", config)).getMessage)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation doesn't look correct.

Comment on lines 601 to 623
"SCRAM-SHA-256=[name=alice,salt=\"MWx2NHBkbnc0ZndxN25vdGN4bTB5eTFrN3E=\",saltedpassword=\"mT0yyUUxnlJaC99HXgRTSYlbuqa4FSGtJCJfTMvjYCE=\",iterations=8192]",
"-S",
"SCRAM-SHA-256=[name=george,salt=\"MWx2NHBkbnc0ZndxN25vdGN4bTB5eTFrN3E=\",saltedpassword=\"mT0yyUUxnlJaC99HXgRTSYlbuqa4FSGtJCJfTMvjYCE=\",iterations=8192]")
"SCRAM-SHA-256=[name=alice,salt=\"MWx2NHBkbnc0ZndxN25vdGN4bTB5eTFrN3E=\",saltedpassword=\"mT0yyUUxnlJaC99HXgRTSYlbuqa4FSGtJCJfTMvjYCE=\",iterations=8192]",
"-S",
"SCRAM-SHA-256=[name=george,salt=\"MWx2NHBkbnc0ZndxN25vdGN4bTB5eTFrN3E=\",saltedpassword=\"mT0yyUUxnlJaC99HXgRTSYlbuqa4FSGtJCJfTMvjYCE=\",iterations=8192]")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect indentation.

Comment on lines 716 to 736
assertEquals(0, exitStatus)
assertEquals(0, exitStatus)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect indentation!

@muralibasani
Copy link
Contributor Author

Closing this, as the StorageTool class will be refactored in a different PR.

@cmccabe
Copy link
Contributor

cmccabe commented Aug 2, 2024

We ended up doing this in: #16669

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
KIP-853 KRaft Controller Membership Changes kraft
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants