Skip to content

Commit

Permalink
Cleaned up Leadership code
Browse files Browse the repository at this point in the history
  • Loading branch information
cer committed Oct 16, 2023
1 parent 950c681 commit 91200c7
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,32 @@
import io.eventuate.util.test.async.Eventually;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public abstract class AbstractLeadershipTest <SELECTOR extends EventuateLeaderSelector> {

private LeadershipController leadershipController;

@Test
public void testThatCallbackInvokedOnce1() {
LeaderSelectorTestingWrap<SELECTOR> leaderSelectorTestingWrap = createAndStartLeaderSelector();
SelectorUnderTest<SELECTOR> selector = createAndStartLeaderSelector();

leaderSelectorTestingWrap.eventuallyAssertIsLeaderAndCallbackIsInvokedOnce();
leaderSelectorTestingWrap.stop();
leaderSelectorTestingWrap.eventuallyAssertIsNotLeaderAndCallbackIsInvokedOnce();
selector.eventuallyAssertIsLeaderAndCallbackIsInvokedOnce();
selector.stop();
selector.eventuallyAssertIsNotLeaderAndCallbackIsInvokedOnce();
}

@Test
public void testThatLeaderIsChangedWhenStopped() {
LeaderSelectorTestingWrap<SELECTOR> leaderSelectorTestingWrap1 = createAndStartLeaderSelector();
LeaderSelectorTestingWrap<SELECTOR> leaderSelectorTestingWrap2 = createAndStartLeaderSelector();
SelectorUnderTest<SELECTOR> selector1 = createAndStartLeaderSelector();
SelectorUnderTest<SELECTOR> selector2 = createAndStartLeaderSelector();

eventuallyAssertLeadershipIsAssignedOnlyForOneSelector(leaderSelectorTestingWrap1, leaderSelectorTestingWrap2);
eventuallyAssertLeadershipIsAssignedOnlyForOneSelector(selector1, selector2);

LeaderSelectorTestingWrap<SELECTOR> instanceWhichBecameLeaderFirst =
leaderSelectorTestingWrap1.isLeader() ? leaderSelectorTestingWrap1 : leaderSelectorTestingWrap2;
SelectorUnderTest<SELECTOR> instanceWhichBecameLeaderFirst =
selector1.isLeader() ? selector1 : selector2;

LeaderSelectorTestingWrap<SELECTOR> instanceWhichBecameLeaderLast =
leaderSelectorTestingWrap2.isLeader() ? leaderSelectorTestingWrap1 : leaderSelectorTestingWrap2;
SelectorUnderTest<SELECTOR> instanceWhichBecameLeaderLast =
selector2.isLeader() ? selector1 : selector2;

instanceWhichBecameLeaderFirst.stop();

Expand All @@ -46,70 +42,42 @@ public void testThatLeaderIsChangedWhenStopped() {

@Test
public void testThatOnlyOneLeaderIsWorkingInTheSameTime() throws Exception {
LeaderSelectorTestingWrap<SELECTOR> leaderSelectorTestingWrap1 = createAndStartLeaderSelector();
LeaderSelectorTestingWrap<SELECTOR> leaderSelectorTestingWrap2 = createAndStartLeaderSelector();
SelectorUnderTest<SELECTOR> selector1 = createAndStartLeaderSelector();
SelectorUnderTest<SELECTOR> selector2 = createAndStartLeaderSelector();

eventuallyAssertLeadershipIsAssignedOnlyForOneSelector(leaderSelectorTestingWrap1, leaderSelectorTestingWrap2);
eventuallyAssertLeadershipIsAssignedOnlyForOneSelector(selector1, selector2);

Thread.sleep(3000);

eventuallyAssertLeadershipIsAssignedOnlyForOneSelector(leaderSelectorTestingWrap1, leaderSelectorTestingWrap2);
eventuallyAssertLeadershipIsAssignedOnlyForOneSelector(selector1, selector2);

leaderSelectorTestingWrap1.stop();
leaderSelectorTestingWrap2.stop();
selector1.stop();
selector2.stop();
}

@Test
public void testRestart() {
LeaderSelectedCallback leaderSelectedCallback = Mockito.mock(LeaderSelectedCallback.class);
Runnable leaderRemovedCallback = Mockito.mock(Runnable.class);

Mockito.doAnswer(invocation -> {
leadershipController = (LeadershipController)invocation.getArguments()[0];
return null;
}).when(leaderSelectedCallback).run(Mockito.any());

SELECTOR leaderSelector = createLeaderSelector(leaderSelectedCallback, leaderRemovedCallback);

leaderSelector.start();

Eventually.eventually(() -> Mockito.verify(leaderSelectedCallback).run(Mockito.any()));

Mockito.verifyNoInteractions(leaderRemovedCallback);
leadershipController.stop();
SelectorUnderTest<SELECTOR> selector = createAndStartLeaderSelector();

Eventually.eventually(() -> {
Mockito.verify(leaderRemovedCallback).run();
Mockito.verify(leaderSelectedCallback, Mockito.times(2)).run(Mockito.any());
});
selector.eventuallyAssertIsLeaderAndCallbackIsInvokedOnce();
selector.relinquish();
selector.eventuallyAssertIsLeaderAndCallbackIsInvokedTwice();
}

protected void eventuallyAssertLeadershipIsAssignedOnlyForOneSelector(LeaderSelectorTestingWrap<SELECTOR> selectorLeaderSelectorTestingWrap1,
LeaderSelectorTestingWrap<SELECTOR> selectorLeaderSelectorTestingWrap2) {

protected void eventuallyAssertLeadershipIsAssignedOnlyForOneSelector(SelectorUnderTest<SELECTOR> selector1,
SelectorUnderTest<SELECTOR> selector2) {
Eventually.eventually(() -> {
boolean leader1Condition = selectorLeaderSelectorTestingWrap1.isLeader() && !selectorLeaderSelectorTestingWrap2.isLeader();
boolean leader2Condition = selectorLeaderSelectorTestingWrap2.isLeader() && !selectorLeaderSelectorTestingWrap1.isLeader();
boolean leader1Condition = selector1.isLeader() && !selector2.isLeader();
boolean leader2Condition = selector2.isLeader() && !selector1.isLeader();
Assert.assertTrue(leader1Condition || leader2Condition);
});
}

protected LeaderSelectorTestingWrap<SELECTOR> createAndStartLeaderSelector() {
AtomicInteger invocationCounter = new AtomicInteger(0);
AtomicBoolean leaderFlag = new AtomicBoolean(false);

SELECTOR selector = createLeaderSelector((leadershipController) -> {
leaderFlag.set(true);
invocationCounter.incrementAndGet();
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, () -> leaderFlag.set(false));

protected SelectorUnderTest<SELECTOR> createAndStartLeaderSelector() {
SelectorUnderTest<SELECTOR> selector = new SelectorUnderTest<>(this::createLeaderSelector);
selector.start();

return new LeaderSelectorTestingWrap<>(selector, invocationCounter, leaderFlag);
return selector;
}

protected abstract SELECTOR createLeaderSelector(LeaderSelectedCallback leaderSelectedCallback, Runnable leaderRemovedCallback);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.eventuate.coordination.leadership.tests;

import io.eventuate.coordination.leadership.EventuateLeaderSelector;
import io.eventuate.coordination.leadership.LeaderSelectedCallback;
import io.eventuate.coordination.leadership.LeadershipController;
import io.eventuate.util.test.async.Eventually;
import org.junit.Assert;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;

public class SelectorUnderTest<SELECTOR extends EventuateLeaderSelector> {
private SELECTOR selector;
private final AtomicInteger invocationCounter = new AtomicInteger(0);;
private final AtomicBoolean leaderFlag = new AtomicBoolean(false);
private LeadershipController leadershipController;

private CountDownLatch sleepLatch;

public SelectorUnderTest(BiFunction<LeaderSelectedCallback , Runnable, SELECTOR> factory) {
this.selector = factory.apply(this::leadershipSelectedCallback, this::leadershipRemovedCallback);
}

private void leadershipRemovedCallback() {
leaderFlag.set(false);
}

private void leadershipSelectedCallback(LeadershipController leadershipController) {
this.leadershipController = leadershipController;
leaderFlag.set(true);
invocationCounter.incrementAndGet();
sleepLatch= new CountDownLatch(1);
try {
sleepLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

public void eventuallyAssertIsLeaderAndCallbackIsInvokedOnce() {
Eventually.eventually(() -> {
Assert.assertTrue("should be leader", isLeader());
Assert.assertEquals(1, getInvocationCount());
});
}

public void eventuallyAssertIsNotLeaderAndCallbackIsInvokedOnce() {
Eventually.eventually(() -> {
Assert.assertFalse("should not be leader", isLeader());
Assert.assertEquals(1, getInvocationCount());
});
}

public int getInvocationCount() {
return invocationCounter.get();
}

public boolean isLeader() {
return leaderFlag.get();
}

public SELECTOR getSelector() {
return selector;
}

public void start() {
selector.start();
}

public void stop() {
selector.stop();
}

public void relinquish() {
Assert.assertTrue("should be leader", isLeader());
sleepLatch.countDown();
leadershipController.stop();
}

public void eventuallyAssertIsLeaderAndCallbackIsInvokedTwice() {
Eventually.eventually(() -> {
Assert.assertTrue("should be leader", isLeader());
Assert.assertEquals(2, getInvocationCount());
});

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ dependencies {
compile "org.slf4j:slf4j-api:1.7.18"

testCompile project(":eventuate-common-coordination-leadership-tests")
testCompile "org.testcontainers:testcontainers:$testContainersVersion"

testCompile project(":eventuate-common-testcontainers")
testCompile "io.eventuate.util:eventuate-util-test:$eventuateUtilVersion"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
package io.eventuate.coordination.leadership.zookeeper;

import io.eventuate.common.testcontainers.EventuateZookeeperContainer;
import io.eventuate.common.testcontainers.PropertyProvidingContainer;
import io.eventuate.coordination.leadership.LeaderSelectedCallback;
import io.eventuate.coordination.leadership.LeadershipController;
import io.eventuate.coordination.leadership.tests.AbstractLeadershipTest;
import io.eventuate.util.test.async.Eventually;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

@SpringBootTest(classes = LeadershipTest.Config.class)
@RunWith(SpringJUnit4ClassRunner.class)
Expand All @@ -29,14 +28,24 @@ public class LeadershipTest extends AbstractLeadershipTest<ZkLeaderSelector> {
public static class Config {
}

public static EventuateZookeeperContainer zookeeper = new EventuateZookeeperContainer("eventuateio/eventuate-zookeeper:0.19.0.BUILD-SNAPSHOT")
.withReuse(true)
.withNetworkAliases("zookeeper");

@DynamicPropertySource
static void registerContainerProperties(DynamicPropertyRegistry registry) {
PropertyProvidingContainer.startAndProvideProperties(registry, zookeeper);
}


@Value("${eventuatelocal.zookeeper.connection.string}")
private String zkUrl;

private String lockId;

@Before
public void init() {
lockId = String.format("/zk/lock/test/%s", UUID.randomUUID().toString());
lockId = String.format("/zk/lock/test/%s", UUID.randomUUID());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
eventuatelocal.zookeeper.connection.string=localhost:2181
logging.level.io.eventuate=INFO
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public EventuateZookeeperContainer() {
withConfiguration();
}

public EventuateZookeeperContainer(String image) {
super(image);
withConfiguration();
}

public EventuateZookeeperContainer(Path path) {
super(new ImageFromDockerfile().withDockerfile(path));
withConfiguration();
Expand Down

0 comments on commit 91200c7

Please sign in to comment.