Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,9 @@ public void replay(FeatureLevelRecord record) {
"supports versions " + range);
}
if (record.name().equals(MetadataVersion.FEATURE_NAME)) {
log.info("Setting metadata.version to {}", record.featureLevel());
metadataVersion.set(MetadataVersion.fromFeatureLevel(record.featureLevel()));
MetadataVersion mv = MetadataVersion.fromFeatureLevel(record.featureLevel());
log.info("Setting metadata version to {}", mv);
metadataVersion.set(mv);
} else {
if (record.featureLevel() == 0) {
log.info("Removing feature {}", record.name());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
import org.apache.kafka.metadata.migration.ZkRecordConsumer;
import org.apache.kafka.metadata.placement.ReplicaPlacer;
import org.apache.kafka.metadata.placement.StripedReplicaPlacer;
import org.apache.kafka.metadata.util.RecordRedactor;
import org.apache.kafka.deferred.DeferredEventQueue;
import org.apache.kafka.deferred.DeferredEvent;
import org.apache.kafka.queue.EventQueue.EarliestDeadlineFunction;
Expand All @@ -106,6 +107,7 @@
import org.apache.kafka.server.policy.AlterConfigPolicy;
import org.apache.kafka.server.policy.CreateTopicPolicy;
import org.apache.kafka.snapshot.SnapshotReader;
import org.apache.kafka.snapshot.Snapshots;
import org.apache.kafka.timeline.SnapshotRegistry;
import org.slf4j.Logger;

Expand All @@ -130,7 +132,6 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
Expand Down Expand Up @@ -963,15 +964,8 @@ public void handleCommit(BatchReader<ApiMessageAndVersion> reader) {
// If the controller is a standby, replay the records that were
// created by the active controller.
if (log.isDebugEnabled()) {
if (log.isTraceEnabled()) {
log.trace("Replaying commits from the active node up to " +
"offset {} and epoch {}: {}.", offset, epoch, messages.stream()
.map(ApiMessageAndVersion::toString)
.collect(Collectors.joining(", ")));
} else {
log.debug("Replaying commits from the active node up to " +
"offset {} and epoch {}.", offset, epoch);
}
log.debug("Replaying commits from the active node up to " +
"offset {} and epoch {}.", offset, epoch);
}
int i = 1;
for (ApiMessageAndVersion message : messages) {
Expand Down Expand Up @@ -1011,13 +1005,13 @@ public void handleCommit(BatchReader<ApiMessageAndVersion> reader) {
public void handleSnapshot(SnapshotReader<ApiMessageAndVersion> reader) {
appendRaftEvent(String.format("handleSnapshot[snapshotId=%s]", reader.snapshotId()), () -> {
try {
String snapshotName = Snapshots.filenameFromSnapshotId(reader.snapshotId());
if (isActiveController()) {
throw fatalFaultHandler.handleFault(String.format("Asked to load snapshot " +
"(%s) when it is the active controller (%d)", reader.snapshotId(),
curClaimEpoch));
throw fatalFaultHandler.handleFault("Asked to load snapshot " + snapshotName +
", but we are the active controller at epoch " + curClaimEpoch);
}
log.info("Starting to replay snapshot ({}), from last commit offset ({}) and epoch ({})",
reader.snapshotId(), lastCommittedOffset, lastCommittedEpoch);
log.info("Starting to replay snapshot {}, from last commit offset {} and epoch {}",
snapshotName, lastCommittedOffset, lastCommittedEpoch);

resetToEmptyState();

Expand All @@ -1026,16 +1020,8 @@ public void handleSnapshot(SnapshotReader<ApiMessageAndVersion> reader) {
long offset = batch.lastOffset();
List<ApiMessageAndVersion> messages = batch.records();

if (log.isDebugEnabled()) {
if (log.isTraceEnabled()) {
log.trace("Replaying snapshot ({}) batch with last offset of {}: {}",
reader.snapshotId(), offset, messages.stream().map(ApiMessageAndVersion::toString).
collect(Collectors.joining(", ")));
} else {
log.debug("Replaying snapshot ({}) batch with last offset of {}",
reader.snapshotId(), offset);
}
}
log.debug("Replaying snapshot {} batch with last offset of {}",
snapshotName, offset);

int i = 1;
for (ApiMessageAndVersion message : messages) {
Expand All @@ -1052,6 +1038,7 @@ public void handleSnapshot(SnapshotReader<ApiMessageAndVersion> reader) {
i++;
}
}
log.info("Finished replaying snapshot {}", snapshotName);

updateLastCommittedState(
reader.lastContainedLogOffset(),
Expand Down Expand Up @@ -1509,6 +1496,16 @@ private void handleFeatureControlChange() {
* if this record is from a snapshot, this is used along with RegisterBrokerRecord
*/
private void replay(ApiMessage message, Optional<OffsetAndEpoch> snapshotId, long batchLastOffset) {
if (log.isTraceEnabled()) {
if (snapshotId.isPresent()) {
log.trace("Replaying snapshot {} record {}",
Snapshots.filenameFromSnapshotId(snapshotId.get()),
recordRedactor.toLoggableString(message));
} else {
log.trace("Replaying log record {} with batchLastOffset {}",
recordRedactor.toLoggableString(message), batchLastOffset);
}
}
logReplayTracker.replay(message);
MetadataRecordType type = MetadataRecordType.fromId(message.apiKey());
switch (type) {
Expand Down Expand Up @@ -1780,11 +1777,6 @@ private enum ImbalanceSchedule {
*/
private boolean noOpRecordScheduled = false;

/**
* Tracks if a snapshot generate was scheduled.
*/
private boolean generateSnapshotScheduled = false;

/**
* The bootstrap metadata to use for initialization if needed.
*/
Expand All @@ -1799,6 +1791,10 @@ private enum ImbalanceSchedule {
*/
private final int maxRecordsPerBatch;

/**
* Supports converting records to strings without disclosing passwords.
*/
private final RecordRedactor recordRedactor;

private QuorumController(
FaultHandler fatalFaultHandler,
Expand Down Expand Up @@ -1835,7 +1831,7 @@ private QuorumController(
this.time = time;
this.controllerMetrics = controllerMetrics;
this.snapshotRegistry = new SnapshotRegistry(logContext);
this.deferredEventQueue = new DeferredEventQueue();
this.deferredEventQueue = new DeferredEventQueue(logContext);
this.resourceExists = new ConfigResourceExistenceChecker();
this.configurationControl = new ConfigurationControlManager.Builder().
setLogContext(logContext).
Expand Down Expand Up @@ -1901,11 +1897,13 @@ private QuorumController(
this.needToCompleteAuthorizerLoad = authorizer.isPresent();
this.zkRecordConsumer = new MigrationRecordConsumer();
this.zkMigrationEnabled = zkMigrationEnabled;
this.recordRedactor = new RecordRedactor(configSchema);
updateWriteOffset(-1);

resetToEmptyState();

log.info("Creating new QuorumController with clusterId {}, authorizer {}.", clusterId, authorizer);
log.info("Creating new QuorumController with clusterId {}, authorizer {}.{}",
clusterId, authorizer, zkMigrationEnabled ? " ZK migration mode is enabled." : "");

this.raftClient.register(metaLogListener);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.kafka.metadata.util;

import org.apache.kafka.common.metadata.ConfigRecord;
import org.apache.kafka.common.metadata.MetadataRecordType;
import org.apache.kafka.common.metadata.UserScramCredentialRecord;
import org.apache.kafka.common.protocol.ApiMessage;
import org.apache.kafka.metadata.KafkaConfigSchema;


/**
* Converts a metadata record to a string suitable for logging to slf4j.
* This means that passwords and key material are omitted from the output.
*/
public final class RecordRedactor {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice. We can make use of this in KRaftMigrationDriver when we log the migrated records (in a separate PR)

private final KafkaConfigSchema configSchema;

public RecordRedactor(KafkaConfigSchema configSchema) {
this.configSchema = configSchema;
}

public String toLoggableString(ApiMessage message) {
MetadataRecordType type = MetadataRecordType.fromId(message.apiKey());
switch (type) {
case CONFIG_RECORD: {
if (!configSchema.isSensitive((ConfigRecord) message)) {
return message.toString();
}
ConfigRecord duplicate = ((ConfigRecord) message).duplicate();
duplicate.setValue("(redacted)");
return duplicate.toString();
}
case USER_SCRAM_CREDENTIAL_RECORD: {
UserScramCredentialRecord record = (UserScramCredentialRecord) message;
return "UserScramCredentialRecord("
+ "name=" + record.name()
+ ", mechanism=" + record.mechanism()
+ ", salt=(redacted)"
+ ", saltedPassword=(redacted)"
+ ", iterations=" + record.iterations()
+ ")";
}
default:
return message.toString();
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.kafka.metadata.util;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.kafka.common.Uuid;
import org.apache.kafka.common.config.ConfigDef;
import org.apache.kafka.common.config.ConfigResource;
import org.apache.kafka.common.metadata.ConfigRecord;
import org.apache.kafka.common.metadata.TopicRecord;
import org.apache.kafka.common.metadata.UserScramCredentialRecord;
import org.apache.kafka.metadata.KafkaConfigSchema;
import org.junit.jupiter.api.Test;

import static org.apache.kafka.common.config.ConfigResource.Type.BROKER;
import static org.junit.jupiter.api.Assertions.assertEquals;


final public class RecordRedactorTest {
public static final Map<ConfigResource.Type, ConfigDef> CONFIGS = new HashMap<>();

static {
CONFIGS.put(BROKER, new ConfigDef().
define("foobar", ConfigDef.Type.LIST, "1", ConfigDef.Importance.HIGH, "foo bar doc").
define("quux", ConfigDef.Type.PASSWORD, ConfigDef.Importance.HIGH, "quuux2 doc"));
}

private static final KafkaConfigSchema SCHEMA = new KafkaConfigSchema(CONFIGS, Collections.emptyMap());

private static final RecordRedactor REDACTOR = new RecordRedactor(SCHEMA);

@Test
public void testTopicRecordToString() {
assertEquals("TopicRecord(name='foo', topicId=UOovKkohSU6AGdYW33ZUNg)",
REDACTOR.toLoggableString(new TopicRecord().
setTopicId(Uuid.fromString("UOovKkohSU6AGdYW33ZUNg")).
setName("foo")));
}

@Test
public void testUserScramCredentialRecordToString() {
assertEquals("UserScramCredentialRecord(name=bob, mechanism=0, " +
"salt=(redacted), serverKey=(redacted), storedKey=(redacted), iterations=128)",
REDACTOR.toLoggableString(new UserScramCredentialRecord().
setName("bob").
setMechanism((byte) 0).
setSalt(new byte[512]).
setServerKey(new byte[128]).
setStoredKey(new byte[128]).
setIterations(128)));
}

@Test
public void testSensitiveConfigRecordToString() {
assertEquals("ConfigRecord(resourceType=4, resourceName='0', name='quux', " +
"value='(redacted)')",
REDACTOR.toLoggableString(new ConfigRecord().
setResourceType(BROKER.id()).
setResourceName("0").
setName("quux").
setValue("mysecret")));
}

@Test
public void testNonSensitiveConfigRecordToString() {
assertEquals("ConfigRecord(resourceType=4, resourceName='0', name='foobar', " +
"value='item1,item2')",
REDACTOR.toLoggableString(new ConfigRecord().
setResourceType(BROKER.id()).
setResourceName("0").
setName("foobar").
setValue("item1,item2")));
}
}
Loading