@ -39,7 +39,6 @@ import org.apache.kafka.common.security.auth.KafkaPrincipal;
@@ -39,7 +39,6 @@ import org.apache.kafka.common.security.auth.KafkaPrincipal;
import org.apache.kafka.common.security.auth.SecurityProtocol ;
import org.apache.kafka.common.utils.LogContext ;
import org.apache.kafka.common.utils.MockTime ;
import org.apache.kafka.common.utils.Time ;
import org.apache.kafka.coordinator.group.assignor.AssignmentSpec ;
import org.apache.kafka.coordinator.group.assignor.GroupAssignment ;
import org.apache.kafka.coordinator.group.assignor.MemberAssignment ;
@ -62,7 +61,6 @@ import org.apache.kafka.coordinator.group.generated.ConsumerGroupTargetAssignmen
@@ -62,7 +61,6 @@ import org.apache.kafka.coordinator.group.generated.ConsumerGroupTargetAssignmen
import org.apache.kafka.coordinator.group.generated.ConsumerGroupTargetAssignmentMetadataKey ;
import org.apache.kafka.coordinator.group.generated.ConsumerGroupTargetAssignmentMetadataValue ;
import org.apache.kafka.coordinator.group.runtime.CoordinatorResult ;
import org.apache.kafka.coordinator.group.runtime.CoordinatorTimer ;
import org.apache.kafka.image.MetadataDelta ;
import org.apache.kafka.image.MetadataImage ;
import org.apache.kafka.image.MetadataProvenance ;
@ -83,16 +81,18 @@ import java.util.List;
@@ -83,16 +81,18 @@ import java.util.List;
import java.util.Map ;
import java.util.Objects ;
import java.util.Set ;
import java.util.concurrent.TimeUnit ;
import static org.apache.kafka.common.utils.Utils.mkSet ;
import static org.apache.kafka.coordinator.group.AssignmentTestUtil.mkAssignment ;
import static org.apache.kafka.coordinator.group.AssignmentTestUtil.mkTopicAssignment ;
import static org.apache.kafka.coordinator.group.GroupMetadataManager.consumerGroupRevocationTimeoutKey ;
import static org.apache.kafka.coordinator.group.GroupMetadataManager.consumerGroupSessionTimeoutKey ;
import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure ;
import static org.junit.jupiter.api.Assertions.assertEquals ;
import static org.junit.jupiter.api.Assertions.assertFalse ;
import static org.junit.jupiter.api.Assertions.assertNotEquals ;
import static org.junit.jupiter.api.Assertions.assertNotNull ;
import static org.junit.jupiter.api.Assertions.assertNull ;
import static org.junit.jupiter.api.Assertions.assertThrows ;
import static org.junit.jupiter.api.Assertions.assertTrue ;
import static org.mockito.ArgumentMatchers.any ;
@ -100,15 +100,6 @@ import static org.mockito.Mockito.mock;
@@ -100,15 +100,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when ;
public class GroupMetadataManagerTest {
// Timer is not used yet so an empty mock is fine for now.
static class MockCoordinatorTimer implements CoordinatorTimer < Record > {
@Override
public void schedule ( String key , long delay , TimeUnit unit , boolean retry , TimeoutOperation < Record > operation ) { }
@Override
public void cancel ( String key ) { }
}
static class MockPartitionAssignor implements PartitionAssignor {
private final String name ;
private GroupAssignment prepareGroupAssignment = null ;
@ -239,7 +230,8 @@ public class GroupMetadataManagerTest {
@@ -239,7 +230,8 @@ public class GroupMetadataManagerTest {
static class GroupMetadataManagerTestContext {
static class Builder {
final private Time time = new MockTime ( ) ;
final private MockTime time = new MockTime ( ) ;
final private MockCoordinatorTimer < Record > timer = new MockCoordinatorTimer < > ( time ) ;
final private LogContext logContext = new LogContext ( ) ;
final private SnapshotRegistry snapshotRegistry = new SnapshotRegistry ( logContext ) ;
private MetadataImage metadataImage ;
@ -279,14 +271,16 @@ public class GroupMetadataManagerTest {
@@ -279,14 +271,16 @@ public class GroupMetadataManagerTest {
GroupMetadataManagerTestContext context = new GroupMetadataManagerTestContext (
time ,
timer ,
snapshotRegistry ,
new GroupMetadataManager . Builder ( )
. withSnapshotRegistry ( snapshotRegistry )
. withLogContext ( logContext )
. withTime ( time )
. withTimer ( new MockCoordina torT imer( ) )
. withTimer ( timer )
. withMetadataImage ( metadataImage )
. withConsumerGroupHeartbeatInterval ( 5000 )
. withConsumerGroupSessionTimeout ( 45000 )
. withConsumerGroupMaxSize ( consumerGroupMaxSize )
. withAssignors ( assignors )
. withConsumerGroupMetadataRefreshIntervalMs ( consumerGroupMetadataRefreshIntervalMs )
@ -303,7 +297,8 @@ public class GroupMetadataManagerTest {
@@ -303,7 +297,8 @@ public class GroupMetadataManagerTest {
}
}
final Time time ;
final MockTime time ;
final MockCoordinatorTimer < Record > timer ;
final SnapshotRegistry snapshotRegistry ;
final GroupMetadataManager groupMetadataManager ;
@ -311,11 +306,13 @@ public class GroupMetadataManagerTest {
@@ -311,11 +306,13 @@ public class GroupMetadataManagerTest {
long lastWrittenOffset = 0L ;
public GroupMetadataManagerTestContext (
Time time ,
MockTime time ,
MockCoordinatorTimer < Record > timer ,
SnapshotRegistry snapshotRegistry ,
GroupMetadataManager groupMetadataManager
) {
this . time = time ;
this . timer = timer ;
this . snapshotRegistry = snapshotRegistry ;
this . groupMetadataManager = groupMetadataManager ;
}
@ -379,6 +376,55 @@ public class GroupMetadataManagerTest {
@@ -379,6 +376,55 @@ public class GroupMetadataManagerTest {
return result ;
}
public List < MockCoordinatorTimer . ExpiredTimeout < Record > > sleep ( long ms ) {
time . sleep ( ms ) ;
List < MockCoordinatorTimer . ExpiredTimeout < Record > > timeouts = timer . poll ( ) ;
timeouts . forEach ( timeout - > timeout . records . forEach ( this : : replay ) ) ;
return timeouts ;
}
public MockCoordinatorTimer . ScheduledTimeout < Record > assertSessionTimeout (
String groupId ,
String memberId ,
long delayMs
) {
MockCoordinatorTimer . ScheduledTimeout < Record > timeout =
timer . timeout ( consumerGroupSessionTimeoutKey ( groupId , memberId ) ) ;
assertNotNull ( timeout ) ;
assertEquals ( time . milliseconds ( ) + delayMs , timeout . deadlineMs ) ;
return timeout ;
}
public void assertNoSessionTimeout (
String groupId ,
String memberId
) {
MockCoordinatorTimer . ScheduledTimeout < Record > timeout =
timer . timeout ( consumerGroupSessionTimeoutKey ( groupId , memberId ) ) ;
assertNull ( timeout ) ;
}
public MockCoordinatorTimer . ScheduledTimeout < Record > assertRevocationTimeout (
String groupId ,
String memberId ,
long delayMs
) {
MockCoordinatorTimer . ScheduledTimeout < Record > timeout =
timer . timeout ( consumerGroupRevocationTimeoutKey ( groupId , memberId ) ) ;
assertNotNull ( timeout ) ;
assertEquals ( time . milliseconds ( ) + delayMs , timeout . deadlineMs ) ;
return timeout ;
}
public void assertNoRevocationTimeout (
String groupId ,
String memberId
) {
MockCoordinatorTimer . ScheduledTimeout < Record > timeout =
timer . timeout ( consumerGroupRevocationTimeoutKey ( groupId , memberId ) ) ;
assertNull ( timeout ) ;
}
private ApiMessage messageOrNull ( ApiMessageAndVersion apiMessageAndVersion ) {
if ( apiMessageAndVersion = = null ) {
return null ;
@ -2402,6 +2448,580 @@ public class GroupMetadataManagerTest {
@@ -2402,6 +2448,580 @@ public class GroupMetadataManagerTest {
assertEquals ( image , context . groupMetadataManager . image ( ) ) ;
}
@Test
public void testSessionTimeoutLifecycle ( ) {
String groupId = "fooup" ;
// Use a static member id as it makes the test easier.
String memberId = Uuid . randomUuid ( ) . toString ( ) ;
Uuid fooTopicId = Uuid . randomUuid ( ) ;
String fooTopicName = "foo" ;
MockPartitionAssignor assignor = new MockPartitionAssignor ( "range" ) ;
GroupMetadataManagerTestContext context = new GroupMetadataManagerTestContext . Builder ( )
. withAssignors ( Collections . singletonList ( assignor ) )
. withMetadataImage ( new MetadataImageBuilder ( )
. addTopic ( fooTopicId , fooTopicName , 6 )
. build ( ) )
. build ( ) ;
assignor . prepareGroupAssignment ( new GroupAssignment (
Collections . singletonMap ( memberId , new MemberAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 0 , 1 , 2 , 3 , 4 , 5 )
) ) )
) ) ;
// Session timer is scheduled on first heartbeat.
CoordinatorResult < ConsumerGroupHeartbeatResponseData , Record > result =
context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId )
. setMemberEpoch ( 0 )
. setRebalanceTimeoutMs ( 90000 )
. setSubscribedTopicNames ( Collections . singletonList ( "foo" ) )
. setTopicPartitions ( Collections . emptyList ( ) ) ) ;
assertEquals ( 1 , result . response ( ) . memberEpoch ( ) ) ;
// Verify that there is a session time.
context . assertSessionTimeout ( groupId , memberId , 45000 ) ;
// Advance time.
assertEquals (
Collections . emptyList ( ) ,
context . sleep ( result . response ( ) . heartbeatIntervalMs ( ) )
) ;
// Session timer is rescheduled on second heartbeat.
result = context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId )
. setMemberEpoch ( result . response ( ) . memberEpoch ( ) ) ) ;
assertEquals ( 1 , result . response ( ) . memberEpoch ( ) ) ;
// Verify that there is a session time.
context . assertSessionTimeout ( groupId , memberId , 45000 ) ;
// Advance time.
assertEquals (
Collections . emptyList ( ) ,
context . sleep ( result . response ( ) . heartbeatIntervalMs ( ) )
) ;
// Session timer is cancelled on leave.
result = context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId )
. setMemberEpoch ( - 1 ) ) ;
assertEquals ( - 1 , result . response ( ) . memberEpoch ( ) ) ;
// Verify that there are no timers.
context . assertNoSessionTimeout ( groupId , memberId ) ;
context . assertNoRevocationTimeout ( groupId , memberId ) ;
}
@Test
public void testSessionTimeoutExpiration ( ) {
String groupId = "fooup" ;
// Use a static member id as it makes the test easier.
String memberId = Uuid . randomUuid ( ) . toString ( ) ;
Uuid fooTopicId = Uuid . randomUuid ( ) ;
String fooTopicName = "foo" ;
MockPartitionAssignor assignor = new MockPartitionAssignor ( "range" ) ;
GroupMetadataManagerTestContext context = new GroupMetadataManagerTestContext . Builder ( )
. withAssignors ( Collections . singletonList ( assignor ) )
. withMetadataImage ( new MetadataImageBuilder ( )
. addTopic ( fooTopicId , fooTopicName , 6 )
. build ( ) )
. build ( ) ;
assignor . prepareGroupAssignment ( new GroupAssignment (
Collections . singletonMap ( memberId , new MemberAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 0 , 1 , 2 , 3 , 4 , 5 )
) ) )
) ) ;
// Session timer is scheduled on first heartbeat.
CoordinatorResult < ConsumerGroupHeartbeatResponseData , Record > result =
context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId )
. setMemberEpoch ( 0 )
. setRebalanceTimeoutMs ( 90000 )
. setSubscribedTopicNames ( Collections . singletonList ( "foo" ) )
. setTopicPartitions ( Collections . emptyList ( ) ) ) ;
assertEquals ( 1 , result . response ( ) . memberEpoch ( ) ) ;
// Verify that there is a session time.
context . assertSessionTimeout ( groupId , memberId , 45000 ) ;
// Advance time past the session timeout.
List < MockCoordinatorTimer . ExpiredTimeout < Record > > timeouts = context . sleep ( 45000 + 1 ) ;
// Verify the expired timeout.
assertEquals (
Collections . singletonList ( new MockCoordinatorTimer . ExpiredTimeout < Record > (
consumerGroupSessionTimeoutKey ( groupId , memberId ) ,
Arrays . asList (
RecordHelpers . newCurrentAssignmentTombstoneRecord ( groupId , memberId ) ,
RecordHelpers . newTargetAssignmentTombstoneRecord ( groupId , memberId ) ,
RecordHelpers . newMemberSubscriptionTombstoneRecord ( groupId , memberId ) ,
RecordHelpers . newGroupSubscriptionMetadataRecord ( groupId , Collections . emptyMap ( ) ) ,
RecordHelpers . newGroupEpochRecord ( groupId , 2 )
)
) ) ,
timeouts
) ;
// Verify that there are no timers.
context . assertNoSessionTimeout ( groupId , memberId ) ;
context . assertNoRevocationTimeout ( groupId , memberId ) ;
}
@Test
public void testRevocationTimeoutLifecycle ( ) {
String groupId = "fooup" ;
// Use a static member id as it makes the test easier.
String memberId1 = Uuid . randomUuid ( ) . toString ( ) ;
String memberId2 = Uuid . randomUuid ( ) . toString ( ) ;
String memberId3 = Uuid . randomUuid ( ) . toString ( ) ;
Uuid fooTopicId = Uuid . randomUuid ( ) ;
String fooTopicName = "foo" ;
MockPartitionAssignor assignor = new MockPartitionAssignor ( "range" ) ;
GroupMetadataManagerTestContext context = new GroupMetadataManagerTestContext . Builder ( )
. withAssignors ( Collections . singletonList ( assignor ) )
. withMetadataImage ( new MetadataImageBuilder ( )
. addTopic ( fooTopicId , fooTopicName , 3 )
. build ( ) )
. build ( ) ;
assignor . prepareGroupAssignment ( new GroupAssignment (
new HashMap < String , MemberAssignment > ( ) {
{
put ( memberId1 , new MemberAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 0 , 1 , 2 )
) ) ) ;
}
}
) ) ;
// Member 1 joins the group.
CoordinatorResult < ConsumerGroupHeartbeatResponseData , Record > result =
context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId1 )
. setMemberEpoch ( 0 )
. setRebalanceTimeoutMs ( 180000 )
. setSubscribedTopicNames ( Collections . singletonList ( "foo" ) )
. setTopicPartitions ( Collections . emptyList ( ) ) ) ;
assertResponseEquals (
new ConsumerGroupHeartbeatResponseData ( )
. setMemberId ( memberId1 )
. setMemberEpoch ( 1 )
. setHeartbeatIntervalMs ( 5000 )
. setAssignment ( new ConsumerGroupHeartbeatResponseData . Assignment ( )
. setAssignedTopicPartitions ( Arrays . asList (
new ConsumerGroupHeartbeatResponseData . TopicPartitions ( )
. setTopicId ( fooTopicId )
. setPartitions ( Arrays . asList ( 0 , 1 , 2 ) ) ) ) ) ,
result . response ( )
) ;
assertEquals (
Collections . emptyList ( ) ,
context . sleep ( result . response ( ) . heartbeatIntervalMs ( ) )
) ;
// Prepare next assignment.
assignor . prepareGroupAssignment ( new GroupAssignment (
new HashMap < String , MemberAssignment > ( ) {
{
put ( memberId1 , new MemberAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 0 , 1 )
) ) ) ;
put ( memberId2 , new MemberAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 2 )
) ) ) ;
}
}
) ) ;
// Member 2 joins the group.
result = context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId2 )
. setMemberEpoch ( 0 )
. setRebalanceTimeoutMs ( 90000 )
. setSubscribedTopicNames ( Collections . singletonList ( "foo" ) )
. setTopicPartitions ( Collections . emptyList ( ) ) ) ;
assertResponseEquals (
new ConsumerGroupHeartbeatResponseData ( )
. setMemberId ( memberId2 )
. setMemberEpoch ( 2 )
. setHeartbeatIntervalMs ( 5000 )
. setAssignment ( new ConsumerGroupHeartbeatResponseData . Assignment ( )
. setPendingTopicPartitions ( Arrays . asList (
new ConsumerGroupHeartbeatResponseData . TopicPartitions ( )
. setTopicId ( fooTopicId )
. setPartitions ( Arrays . asList ( 2 ) ) ) ) ) ,
result . response ( )
) ;
assertEquals (
Collections . emptyList ( ) ,
context . sleep ( result . response ( ) . heartbeatIntervalMs ( ) )
) ;
// Member 1 heartbeats and transitions to revoking. The revocation timeout
// is scheduled.
result = context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId1 )
. setMemberEpoch ( 1 )
. setRebalanceTimeoutMs ( 12000 )
. setSubscribedTopicNames ( Collections . singletonList ( "foo" ) ) ) ;
assertResponseEquals (
new ConsumerGroupHeartbeatResponseData ( )
. setMemberId ( memberId1 )
. setMemberEpoch ( 1 )
. setHeartbeatIntervalMs ( 5000 )
. setAssignment ( new ConsumerGroupHeartbeatResponseData . Assignment ( )
. setAssignedTopicPartitions ( Arrays . asList (
new ConsumerGroupHeartbeatResponseData . TopicPartitions ( )
. setTopicId ( fooTopicId )
. setPartitions ( Arrays . asList ( 0 , 1 ) ) ) ) ) ,
result . response ( )
) ;
// Verify that there is a revocation timeout.
context . assertRevocationTimeout ( groupId , memberId1 , 12000 ) ;
assertEquals (
Collections . emptyList ( ) ,
context . sleep ( result . response ( ) . heartbeatIntervalMs ( ) )
) ;
// Prepare next assignment.
assignor . prepareGroupAssignment ( new GroupAssignment (
new HashMap < String , MemberAssignment > ( ) {
{
put ( memberId1 , new MemberAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 0 )
) ) ) ;
put ( memberId2 , new MemberAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 2 )
) ) ) ;
put ( memberId3 , new MemberAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 1 )
) ) ) ;
}
}
) ) ;
// Member 3 joins the group.
result = context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId3 )
. setMemberEpoch ( 0 )
. setRebalanceTimeoutMs ( 90000 )
. setSubscribedTopicNames ( Collections . singletonList ( "foo" ) )
. setTopicPartitions ( Collections . emptyList ( ) ) ) ;
assertResponseEquals (
new ConsumerGroupHeartbeatResponseData ( )
. setMemberId ( memberId3 )
. setMemberEpoch ( 3 )
. setHeartbeatIntervalMs ( 5000 )
. setAssignment ( new ConsumerGroupHeartbeatResponseData . Assignment ( )
. setPendingTopicPartitions ( Arrays . asList (
new ConsumerGroupHeartbeatResponseData . TopicPartitions ( )
. setTopicId ( fooTopicId )
. setPartitions ( Arrays . asList ( 1 ) ) ) ) ) ,
result . response ( )
) ;
assertEquals (
Collections . emptyList ( ) ,
context . sleep ( result . response ( ) . heartbeatIntervalMs ( ) )
) ;
// Member 1 heartbeats and re-transitions to revoking. The revocation timeout
// is re-scheduled.
result = context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId1 )
. setMemberEpoch ( 1 )
. setRebalanceTimeoutMs ( 90000 )
. setSubscribedTopicNames ( Collections . singletonList ( "foo" ) ) ) ;
assertResponseEquals (
new ConsumerGroupHeartbeatResponseData ( )
. setMemberId ( memberId1 )
. setMemberEpoch ( 1 )
. setHeartbeatIntervalMs ( 5000 )
. setAssignment ( new ConsumerGroupHeartbeatResponseData . Assignment ( )
. setAssignedTopicPartitions ( Arrays . asList (
new ConsumerGroupHeartbeatResponseData . TopicPartitions ( )
. setTopicId ( fooTopicId )
. setPartitions ( Arrays . asList ( 0 ) ) ) ) ) ,
result . response ( )
) ;
// Verify that there is a revocation timeout. Keep a reference
// to the timeout for later.
MockCoordinatorTimer . ScheduledTimeout < Record > scheduledTimeout =
context . assertRevocationTimeout ( groupId , memberId1 , 90000 ) ;
assertEquals (
Collections . emptyList ( ) ,
context . sleep ( result . response ( ) . heartbeatIntervalMs ( ) )
) ;
// Member 1 acks the revocation. The revocation timeout is cancelled.
result = context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId1 )
. setMemberEpoch ( 1 )
. setTopicPartitions ( Collections . singletonList ( new ConsumerGroupHeartbeatRequestData . TopicPartitions ( )
. setTopicId ( fooTopicId )
. setPartitions ( Collections . singletonList ( 0 ) ) ) ) ) ;
assertResponseEquals (
new ConsumerGroupHeartbeatResponseData ( )
. setMemberId ( memberId1 )
. setMemberEpoch ( 3 )
. setHeartbeatIntervalMs ( 5000 )
. setAssignment ( new ConsumerGroupHeartbeatResponseData . Assignment ( )
. setAssignedTopicPartitions ( Arrays . asList (
new ConsumerGroupHeartbeatResponseData . TopicPartitions ( )
. setTopicId ( fooTopicId )
. setPartitions ( Arrays . asList ( 0 ) ) ) ) ) ,
result . response ( )
) ;
// Verify that there is not revocation timeout.
context . assertNoRevocationTimeout ( groupId , memberId1 ) ;
// Execute the scheduled revocation timeout captured earlier to simulate a
// stale timeout. This should be a no-op.
assertEquals ( Collections . emptyList ( ) , scheduledTimeout . operation . generateRecords ( ) ) ;
}
@Test
public void testRevocationTimeoutExpiration ( ) {
String groupId = "fooup" ;
// Use a static member id as it makes the test easier.
String memberId1 = Uuid . randomUuid ( ) . toString ( ) ;
String memberId2 = Uuid . randomUuid ( ) . toString ( ) ;
Uuid fooTopicId = Uuid . randomUuid ( ) ;
String fooTopicName = "foo" ;
MockPartitionAssignor assignor = new MockPartitionAssignor ( "range" ) ;
GroupMetadataManagerTestContext context = new GroupMetadataManagerTestContext . Builder ( )
. withAssignors ( Collections . singletonList ( assignor ) )
. withMetadataImage ( new MetadataImageBuilder ( )
. addTopic ( fooTopicId , fooTopicName , 3 )
. build ( ) )
. build ( ) ;
assignor . prepareGroupAssignment ( new GroupAssignment (
new HashMap < String , MemberAssignment > ( ) {
{
put ( memberId1 , new MemberAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 0 , 1 , 2 )
) ) ) ;
}
}
) ) ;
// Member 1 joins the group.
CoordinatorResult < ConsumerGroupHeartbeatResponseData , Record > result =
context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId1 )
. setMemberEpoch ( 0 )
. setRebalanceTimeoutMs ( 10000 ) // Use timeout smaller than session timeout.
. setSubscribedTopicNames ( Collections . singletonList ( "foo" ) )
. setTopicPartitions ( Collections . emptyList ( ) ) ) ;
assertResponseEquals (
new ConsumerGroupHeartbeatResponseData ( )
. setMemberId ( memberId1 )
. setMemberEpoch ( 1 )
. setHeartbeatIntervalMs ( 5000 )
. setAssignment ( new ConsumerGroupHeartbeatResponseData . Assignment ( )
. setAssignedTopicPartitions ( Arrays . asList (
new ConsumerGroupHeartbeatResponseData . TopicPartitions ( )
. setTopicId ( fooTopicId )
. setPartitions ( Arrays . asList ( 0 , 1 , 2 ) ) ) ) ) ,
result . response ( )
) ;
assertEquals (
Collections . emptyList ( ) ,
context . sleep ( result . response ( ) . heartbeatIntervalMs ( ) )
) ;
// Prepare next assignment.
assignor . prepareGroupAssignment ( new GroupAssignment (
new HashMap < String , MemberAssignment > ( ) {
{
put ( memberId1 , new MemberAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 0 , 1 )
) ) ) ;
put ( memberId2 , new MemberAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 2 )
) ) ) ;
}
}
) ) ;
// Member 2 joins the group.
result = context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId2 )
. setMemberEpoch ( 0 )
. setRebalanceTimeoutMs ( 10000 )
. setSubscribedTopicNames ( Collections . singletonList ( "foo" ) )
. setTopicPartitions ( Collections . emptyList ( ) ) ) ;
assertResponseEquals (
new ConsumerGroupHeartbeatResponseData ( )
. setMemberId ( memberId2 )
. setMemberEpoch ( 2 )
. setHeartbeatIntervalMs ( 5000 )
. setAssignment ( new ConsumerGroupHeartbeatResponseData . Assignment ( )
. setPendingTopicPartitions ( Arrays . asList (
new ConsumerGroupHeartbeatResponseData . TopicPartitions ( )
. setTopicId ( fooTopicId )
. setPartitions ( Arrays . asList ( 2 ) ) ) ) ) ,
result . response ( )
) ;
assertEquals (
Collections . emptyList ( ) ,
context . sleep ( result . response ( ) . heartbeatIntervalMs ( ) )
) ;
// Member 1 heartbeats and transitions to revoking. The revocation timeout
// is scheduled.
result = context . consumerGroupHeartbeat (
new ConsumerGroupHeartbeatRequestData ( )
. setGroupId ( groupId )
. setMemberId ( memberId1 )
. setMemberEpoch ( 1 ) ) ;
assertResponseEquals (
new ConsumerGroupHeartbeatResponseData ( )
. setMemberId ( memberId1 )
. setMemberEpoch ( 1 )
. setHeartbeatIntervalMs ( 5000 )
. setAssignment ( new ConsumerGroupHeartbeatResponseData . Assignment ( )
. setAssignedTopicPartitions ( Arrays . asList (
new ConsumerGroupHeartbeatResponseData . TopicPartitions ( )
. setTopicId ( fooTopicId )
. setPartitions ( Arrays . asList ( 0 , 1 ) ) ) ) ) ,
result . response ( )
) ;
// Advance time past the revocation timeout.
List < MockCoordinatorTimer . ExpiredTimeout < Record > > timeouts = context . sleep ( 10000 + 1 ) ;
// Verify the expired timeout.
assertEquals (
Collections . singletonList ( new MockCoordinatorTimer . ExpiredTimeout < Record > (
consumerGroupRevocationTimeoutKey ( groupId , memberId1 ) ,
Arrays . asList (
RecordHelpers . newCurrentAssignmentTombstoneRecord ( groupId , memberId1 ) ,
RecordHelpers . newTargetAssignmentTombstoneRecord ( groupId , memberId1 ) ,
RecordHelpers . newMemberSubscriptionTombstoneRecord ( groupId , memberId1 ) ,
RecordHelpers . newGroupEpochRecord ( groupId , 3 )
)
) ) ,
timeouts
) ;
// Verify that there are no timers.
context . assertNoSessionTimeout ( groupId , memberId1 ) ;
context . assertNoRevocationTimeout ( groupId , memberId1 ) ;
}
@Test
public void testOnLoaded ( ) {
Uuid fooTopicId = Uuid . randomUuid ( ) ;
String fooTopicName = "foo" ;
Uuid barTopicId = Uuid . randomUuid ( ) ;
String barTopicName = "bar" ;
GroupMetadataManagerTestContext context = new GroupMetadataManagerTestContext . Builder ( )
. withAssignors ( Collections . singletonList ( new MockPartitionAssignor ( "range" ) ) )
. withMetadataImage ( new MetadataImageBuilder ( )
. addTopic ( fooTopicId , fooTopicName , 6 )
. addTopic ( barTopicId , barTopicName , 3 )
. build ( ) )
. withConsumerGroup ( new ConsumerGroupBuilder ( "foo" , 10 )
. withMember ( new ConsumerGroupMember . Builder ( "foo-1" )
. setMemberEpoch ( 9 )
. setPreviousMemberEpoch ( 9 )
. setTargetMemberEpoch ( 10 )
. setClientId ( "client" )
. setClientHost ( "localhost/127.0.0.1" )
. setSubscribedTopicNames ( Arrays . asList ( "foo" ) )
. setServerAssignorName ( "range" )
. setAssignedPartitions ( mkAssignment (
mkTopicAssignment ( fooTopicId , 0 , 1 , 2 ) ) )
. setPartitionsPendingRevocation ( mkAssignment (
mkTopicAssignment ( fooTopicId , 3 , 4 , 5 ) ) )
. build ( ) )
. withMember ( new ConsumerGroupMember . Builder ( "foo-2" )
. setMemberEpoch ( 10 )
. setPreviousMemberEpoch ( 10 )
. setTargetMemberEpoch ( 10 )
. setClientId ( "client" )
. setClientHost ( "localhost/127.0.0.1" )
. setSubscribedTopicNames ( Arrays . asList ( "foo" ) )
. setServerAssignorName ( "range" )
. setPartitionsPendingAssignment ( mkAssignment (
mkTopicAssignment ( fooTopicId , 3 , 4 , 5 ) ) )
. build ( ) )
. withAssignment ( "foo-1" , mkAssignment (
mkTopicAssignment ( fooTopicId , 0 , 1 , 2 ) ) )
. withAssignment ( "foo-2" , mkAssignment (
mkTopicAssignment ( fooTopicId , 3 , 4 , 5 ) ) )
. withAssignmentEpoch ( 10 ) )
. build ( ) ;
// Let's assume that all the records have been replayed and now
// onLoaded is called to signal it.
context . groupMetadataManager . onLoaded ( ) ;
// All members should have a session timeout in place.
assertNotNull ( context . timer . timeout ( consumerGroupSessionTimeoutKey ( "foo" , "foo-1" ) ) ) ;
assertNotNull ( context . timer . timeout ( consumerGroupSessionTimeoutKey ( "foo" , "foo-2" ) ) ) ;
// foo-1 should also have a revocation timeout in place.
assertNotNull ( context . timer . timeout ( consumerGroupRevocationTimeoutKey ( "foo" , "foo-1" ) ) ) ;
}
private < T > void assertUnorderedListEquals (
List < T > expected ,
List < T > actual