Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions checkstyle/import-control.xml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@
<allow pkg="org.apache.kafka.common.metadata" />
<allow pkg="org.apache.kafka.common.network" />
<allow pkg="org.apache.kafka.common.protocol" />
<allow pkg="org.apache.kafka.common.record" />
<allow pkg="org.apache.kafka.common.requests" />
<allow pkg="org.apache.kafka.coordinator.group" />
<allow pkg="org.apache.kafka.deferred" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* 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.coordinator.group;

import org.apache.kafka.common.record.RecordBatch;
import org.apache.kafka.common.requests.OffsetCommitRequest;
import org.apache.kafka.coordinator.group.generated.OffsetCommitValue;

import java.util.Objects;
import java.util.OptionalInt;
import java.util.OptionalLong;

/**
* Represents a committed offset with its metadata.
Comment thread
dajac marked this conversation as resolved.
*/
public class OffsetAndMetadata {
public static final String NO_METADATA = "";
Comment thread
dajac marked this conversation as resolved.

/**
* The committed offset.
*/
public final long offset;

/**
* The leader epoch in use when the offset was committed.
*/
public final OptionalInt leaderEpoch;
Comment thread
dajac marked this conversation as resolved.

/**
* The committed metadata.
*/
public final String metadata;

/**
* The commit timestamp in milliseconds.
*/
public final long commitTimestampMs;

/**
* The expire timestamp in milliseconds.
*/
public final OptionalLong expireTimestampMs;

public OffsetAndMetadata(
long offset,
OptionalInt leaderEpoch,
String metadata,
long commitTimestampMs,
OptionalLong expireTimestampMs
) {
this.offset = offset;
this.leaderEpoch = Objects.requireNonNull(leaderEpoch);
this.metadata = Objects.requireNonNull(metadata);
this.commitTimestampMs = commitTimestampMs;
this.expireTimestampMs = Objects.requireNonNull(expireTimestampMs);
}

@Override
public String toString() {
return "OffsetAndMetadata(offset=" + offset +
", leaderEpoch=" + leaderEpoch +
", metadata=" + metadata +
", commitTimestampMs=" + commitTimestampMs +
", expireTimestampMs=" + expireTimestampMs +
')';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

OffsetAndMetadata that = (OffsetAndMetadata) o;

if (offset != that.offset) return false;
if (commitTimestampMs != that.commitTimestampMs) return false;
if (!leaderEpoch.equals(that.leaderEpoch)) return false;
if (!metadata.equals(that.metadata)) return false;
return expireTimestampMs.equals(that.expireTimestampMs);
}

@Override
public int hashCode() {
int result = (int) (offset ^ (offset >>> 32));
result = 31 * result + leaderEpoch.hashCode();
result = 31 * result + metadata.hashCode();
result = 31 * result + (int) (commitTimestampMs ^ (commitTimestampMs >>> 32));
result = 31 * result + expireTimestampMs.hashCode();
return result;
}

/**
* @return An OffsetAndMetadata created from a OffsetCommitValue record.
*/
public static OffsetAndMetadata fromRecord(
OffsetCommitValue record
) {
return new OffsetAndMetadata(
record.offset(),
record.leaderEpoch() == RecordBatch.NO_PARTITION_LEADER_EPOCH ?
OptionalInt.empty() : OptionalInt.of(record.leaderEpoch()),
record.metadata(),
record.commitTimestamp(),
record.expireTimestamp() == OffsetCommitRequest.DEFAULT_TIMESTAMP ?
OptionalLong.empty() : OptionalLong.of(record.expireTimestamp())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package org.apache.kafka.coordinator.group;

import org.apache.kafka.common.Uuid;
import org.apache.kafka.common.record.RecordBatch;
import org.apache.kafka.common.requests.OffsetCommitRequest;
import org.apache.kafka.coordinator.group.consumer.ConsumerGroupMember;
import org.apache.kafka.coordinator.group.consumer.TopicMetadata;
import org.apache.kafka.coordinator.group.generated.ConsumerGroupCurrentMemberAssignmentKey;
Expand All @@ -33,6 +35,8 @@
import org.apache.kafka.coordinator.group.generated.ConsumerGroupTargetAssignmentMetadataValue;
import org.apache.kafka.coordinator.group.generated.GroupMetadataKey;
import org.apache.kafka.coordinator.group.generated.GroupMetadataValue;
import org.apache.kafka.coordinator.group.generated.OffsetCommitKey;
import org.apache.kafka.coordinator.group.generated.OffsetCommitValue;
import org.apache.kafka.coordinator.group.generic.GenericGroup;
import org.apache.kafka.server.common.ApiMessageAndVersion;
import org.apache.kafka.server.common.MetadataVersion;
Expand Down Expand Up @@ -467,6 +471,72 @@ public static Record newEmptyGroupMetadataRecord(
);
}

/**
* Creates an OffsetCommit record.
*
* @param groupId The group id.
* @param topic The topic name.
* @param partitionId The partition id.
* @param offsetAndMetadata The offset and metadata.
* @param metadataVersion The metadata version.
* @return The record.
*/
public static Record newOffsetCommitRecord(
String groupId,
String topic,
int partitionId,
OffsetAndMetadata offsetAndMetadata,
MetadataVersion metadataVersion
) {
short version = offsetAndMetadata.expireTimestampMs.isPresent() ?
(short) 1 : metadataVersion.offsetCommitValueVersion();
Comment thread
dajac marked this conversation as resolved.
Outdated

return new Record(
new ApiMessageAndVersion(
new OffsetCommitKey()
.setGroup(groupId)
.setTopic(topic)
.setPartition(partitionId),
(short) 1
Comment thread
dajac marked this conversation as resolved.
),
new ApiMessageAndVersion(
new OffsetCommitValue()
.setOffset(offsetAndMetadata.offset)
.setLeaderEpoch(offsetAndMetadata.leaderEpoch.orElse(RecordBatch.NO_PARTITION_LEADER_EPOCH))
.setMetadata(offsetAndMetadata.metadata)
.setCommitTimestamp(offsetAndMetadata.commitTimestampMs)
// Version 1 has a non-empty expireTimestamp field
.setExpireTimestamp(offsetAndMetadata.expireTimestampMs.orElse(OffsetCommitRequest.DEFAULT_TIMESTAMP)),
Comment thread
dajac marked this conversation as resolved.
version
)
);
}

/**
* Creates an OffsetCommit tombstone record.
*
* @param groupId The group id.
* @param topic The topic name.
* @param partitionId The partition id.
* @return The record.
*/
public static Record newOffsetCommitTombstoneRecord(
String groupId,
String topic,
int partitionId
) {
return new Record(
new ApiMessageAndVersion(
new OffsetCommitKey()
.setGroup(groupId)
.setTopic(topic)
.setPartition(partitionId),
(short) 1
),
null
);
}

private static List<ConsumerGroupCurrentMemberAssignmentValue.TopicPartitions> toTopicPartitions(
Map<Uuid, Set<Integer>> topicPartitions
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.coordinator.group;

import org.apache.kafka.coordinator.group.generated.OffsetCommitValue;
import org.junit.jupiter.api.Test;

import java.util.OptionalInt;
import java.util.OptionalLong;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class OffsetAndMetadataTest {
@Test
public void testAttributes() {
OffsetAndMetadata offsetAndMetadata = new OffsetAndMetadata(
100L,
OptionalInt.of(10),
"metadata",
1234L,
OptionalLong.of(5678L)
);

assertEquals(100L, offsetAndMetadata.offset);
assertEquals(OptionalInt.of(10), offsetAndMetadata.leaderEpoch);
assertEquals("metadata", offsetAndMetadata.metadata);
assertEquals(1234L, offsetAndMetadata.commitTimestampMs);
assertEquals(OptionalLong.of(5678L), offsetAndMetadata.expireTimestampMs);
}

@Test
public void testFromRecord() {
OffsetCommitValue record = new OffsetCommitValue()
.setOffset(100L)
.setLeaderEpoch(-1)
.setMetadata("metadata")
.setCommitTimestamp(1234L)
.setExpireTimestamp(-1L);

assertEquals(new OffsetAndMetadata(
100L,
OptionalInt.empty(),
"metadata",
1234L,
OptionalLong.empty()
), OffsetAndMetadata.fromRecord(record));

record
.setLeaderEpoch(12)
.setExpireTimestamp(5678L);

assertEquals(new OffsetAndMetadata(
100L,
OptionalInt.of(12),
"metadata",
1234L,
OptionalLong.of(5678L)
), OffsetAndMetadata.fromRecord(record));
}
}
Loading