GH-3213: Expose LeaderInitiatorFB.candidate

Fixes https://github.com/spring-projects/spring-integration/issues/3213

* Expose a `LeaderInitiatorFactoryBean.setCandidate()` for better control over candidate options
* Expose a `candidate` attribute for the `<int-zk:leader-listener>` tag
* Fix `dokka` task to obtain `package-list` URL from the local dir
* Migrate ZK tests to JUnit 5
This commit is contained in:
Artem Bilan
2020-03-11 16:07:17 -04:00
parent e9a3580ef5
commit 46cf5db78a
13 changed files with 115 additions and 93 deletions

View File

@@ -1008,6 +1008,7 @@ dokka {
moduleName = 'spring-integration'
externalDocumentationLink {
url = new URL("https://docs.spring.io/spring-integration/docs/$version/api/")
packageListUrl = new File(buildDir, "api/package-list").toURI().toURL()
}
externalDocumentationLink {
url = new URL('https://docs.spring.io/spring-framework/docs/current/javadoc-api/')

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ import org.springframework.integration.leader.DefaultCandidate;
import org.springframework.integration.leader.event.DefaultLeaderEventPublisher;
import org.springframework.integration.leader.event.LeaderEventPublisher;
import org.springframework.integration.zookeeper.leader.LeaderInitiator;
import org.springframework.util.Assert;
/**
* Creates a {@link LeaderInitiator}.
@@ -71,8 +72,27 @@ public class LeaderInitiatorFactoryBean
return this;
}
/**
* Configure a role for {@link DefaultCandidate}.
* Or this or {@link #setCandidate(Candidate)} can be configured, but not both.
* @param role the role for candidate
* @return this instance
*/
public LeaderInitiatorFactoryBean setRole(String role) {
this.candidate = new DefaultCandidate(UUID.randomUUID().toString(), role);
Assert.isNull(this.candidate,
"Or 'role' for an internal 'DefaultCandidate' or 'candidate' option must be provided, but not both.");
return setCandidate(new DefaultCandidate(UUID.randomUUID().toString(), role));
}
/**
* Configure a {@link Candidate} for leader election.
* Or this or {@link #setRole(String)} can be configured, but not both.
* @param candidate the {@link Candidate} to use
* @return this instance
* @since 5.3
*/
public LeaderInitiatorFactoryBean setCandidate(Candidate candidate) {
this.candidate = candidate;
return this;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,6 +44,7 @@ public class LeaderListenerParser extends AbstractBeanDefinitionParser {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "candidate");
return builder.getBeanDefinition();
}

View File

@@ -31,7 +31,7 @@
</xsd:annotation>
<xsd:complexType>
<xsd:attributeGroup ref="integration:smartLifeCycleAttributeGroup"/>
<xsd:attribute name="id" type="xsd:string" use="optional"/>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="client" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
@@ -44,6 +44,19 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="candidate" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
A reference to a Candidate bean.
Or this or 'role' option can be provided, but not both.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.leader.Candidate"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="path">
<xsd:annotation>
<xsd:documentation>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,10 +25,10 @@ import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.BoundedExponentialBackoffRetry;
import org.apache.curator.test.TestingServer;
import org.apache.curator.utils.CloseableUtils;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
/**
* @author Marius Bogoevici
@@ -48,12 +48,12 @@ public class ZookeeperTestSupport {
protected CuratorFramework client;
@BeforeClass
@BeforeAll
public static void setUpClass() throws Exception {
testingServer = new TestingServer(true);
}
@AfterClass
@AfterAll
public static void tearDownClass() {
try {
testingServer.stop();
@@ -64,12 +64,12 @@ public class ZookeeperTestSupport {
testingServer.getTempDirectory().delete();
}
@Before
@BeforeEach
public void setUp() {
client = createNewClient();
}
@After
@AfterEach
public void tearDown() throws Exception {
CloseableUtils.closeQuietly(this.client);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.test.TestingServer;
import org.junit.Test;
import org.junit.jupiter.api.Test;
/**
* @author Gary Russell

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,14 +20,14 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.curator.framework.CuratorFramework;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
@@ -42,17 +42,16 @@ import org.springframework.integration.leader.event.OnRevokedEvent;
import org.springframework.integration.zookeeper.ZookeeperTestSupport;
import org.springframework.integration.zookeeper.leader.LeaderInitiator;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Gary Russell
* @author Artem Bilan
*
* @since 4.2
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringJUnitConfig
@DirtiesContext
public class LeaderInitiatorFactoryBeanTests extends ZookeeperTestSupport {
@@ -64,12 +63,12 @@ public class LeaderInitiatorFactoryBeanTests extends ZookeeperTestSupport {
@Autowired
private Config config;
@BeforeClass
public static void getClient() throws Exception {
@BeforeAll
public static void getClient() {
client = createNewClient();
}
@AfterClass
@AfterAll
public static void closeClient() {
if (client != null) {
client.close();
@@ -118,7 +117,7 @@ public class LeaderInitiatorFactoryBeanTests extends ZookeeperTestSupport {
@Configuration
public static class Config {
private final List<AbstractLeaderEvent> events = new ArrayList<AbstractLeaderEvent>();
private final List<AbstractLeaderEvent> events = new ArrayList<>();
private final CountDownLatch latch1 = new CountDownLatch(1);
@@ -129,7 +128,7 @@ public class LeaderInitiatorFactoryBeanTests extends ZookeeperTestSupport {
return new LeaderInitiatorFactoryBean()
.setClient(client)
.setPath("/siTest/")
.setRole("foo");
.setCandidate(new DefaultCandidate(UUID.randomUUID().toString(), "foo"));
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,7 @@ package org.springframework.integration.zookeeper.config.xml;
import static org.assertj.core.api.Assertions.assertThat;
import org.apache.curator.framework.CuratorFramework;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -29,16 +28,16 @@ import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.zookeeper.ZookeeperTestSupport;
import org.springframework.integration.zookeeper.leader.LeaderInitiator;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Gary Russell
* @author Artem Bilan
*
* @since 4.2
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringJUnitConfig
@DirtiesContext
public class ZookeeperParserTests extends ZookeeperTestSupport {
@@ -76,9 +75,10 @@ public class ZookeeperParserTests extends ZookeeperTestSupport {
public static class Config {
@Bean
public CuratorFramework client() throws Exception {
public CuratorFramework client() {
return createNewClient();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,12 +25,11 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
@@ -48,17 +47,19 @@ import org.springframework.scheduling.Trigger;
/**
* @author Gary Russell
* @author Artem Bilan
*
* @since 4.2
*
*/
public class ZookeeperLeaderTests extends ZookeeperTestSupport {
private final BlockingQueue<AbstractLeaderEvent> events = new LinkedBlockingQueue<AbstractLeaderEvent>();
private final BlockingQueue<AbstractLeaderEvent> events = new LinkedBlockingQueue<>();
private final SourcePollingChannelAdapter adapter = buildChannelAdapter();
private final SmartLifecycleRoleController controller = new SmartLifecycleRoleController(
Collections.singletonList("sitest"), Collections.<SmartLifecycle>singletonList(this.adapter));
Collections.singletonList("sitest"), Collections.singletonList(this.adapter));
private final CountDownLatch yieldBarrier = new CountDownLatch(1);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.zookeeper.ZookeeperTestSupport;
@@ -35,7 +35,8 @@ import org.springframework.messaging.MessagingException;
/**
* @author Gary Russell
* @author Artem Bilan
* @author Artem Bilan\
*
* @since 4.2
*
*/
@@ -78,7 +79,7 @@ public class ZkLockRegistryTests extends ZookeeperTestSupport {
}
@Test
public void testReentrantLock() throws Exception {
public void testReentrantLock() {
ZookeeperLockRegistry registry = new ZookeeperLockRegistry(this.client);
for (int i = 0; i < 10; i++) {
Lock lock1 = registry.obtain("foo");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 the original author or authors.
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,8 @@
package org.springframework.integration.zookeeper.metadata;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.awaitility.Awaitility.await;
import java.util.ArrayList;
@@ -30,9 +31,9 @@ import java.util.concurrent.TimeUnit;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.utils.CloseableUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.DirectFieldAccessor;
@@ -52,7 +53,7 @@ public class ZookeeperMetadataStoreTests extends ZookeeperTestSupport {
private ZookeeperMetadataStore metadataStore;
@Override
@Before
@BeforeEach
public void setUp() {
super.setUp();
this.metadataStore = new ZookeeperMetadataStore(client);
@@ -60,7 +61,7 @@ public class ZookeeperMetadataStoreTests extends ZookeeperTestSupport {
}
@Override
@After
@AfterEach
public void tearDown() throws Exception {
this.metadataStore.stop();
this.client.delete().deletingChildrenIfNeeded().forPath(this.metadataStore.getRoot());
@@ -152,14 +153,9 @@ public class ZookeeperMetadataStoreTests extends ZookeeperTestSupport {
@Test
public void testPersistNullStringToMetadataStore() {
try {
metadataStore.put("ZookeeperMetadataStoreTests-PersistEmpty", null);
}
catch (IllegalArgumentException e) {
assertThat(e.getMessage()).isEqualTo("'value' must not be null.");
return;
}
fail("Expected an IllegalArgumentException to be thrown.");
assertThatIllegalArgumentException()
.isThrownBy(() -> metadataStore.put("ZookeeperMetadataStoreTests-PersistEmpty", null))
.withMessage("'value' must not be null.");
}
@Test
@@ -171,26 +167,16 @@ public class ZookeeperMetadataStoreTests extends ZookeeperTestSupport {
@Test
public void testPersistWithNullKeyToMetadataStore() {
try {
metadataStore.put(null, "something");
}
catch (IllegalArgumentException e) {
assertThat(e.getMessage()).isEqualTo("'key' must not be null.");
return;
}
fail("Expected an IllegalArgumentException to be thrown.");
assertThatIllegalArgumentException()
.isThrownBy(() -> metadataStore.put(null, "something"))
.withMessage("'key' must not be null.");
}
@Test
public void testGetValueWithNullKeyFromMetadataStore() {
try {
metadataStore.get(null);
}
catch (IllegalArgumentException e) {
assertThat(e.getMessage()).isEqualTo("'key' must not be null.");
return;
}
fail("Expected an IllegalArgumentException to be thrown.");
assertThatIllegalArgumentException()
.isThrownBy(() -> metadataStore.get(null))
.withMessage("'key' must not be null.");
}
@Test
@@ -212,14 +198,10 @@ public class ZookeeperMetadataStoreTests extends ZookeeperTestSupport {
barriers.put("add", new CyclicBarrier(2));
barriers.put("remove", new CyclicBarrier(2));
barriers.put("update", new CyclicBarrier(2));
try {
metadataStore.addListener(null);
fail("IllegalArgumentException expected");
}
catch (Exception e) {
assertThat(e).isInstanceOf(IllegalArgumentException.class);
assertThat(e.getMessage()).contains("'listener' must not be null");
}
assertThatIllegalArgumentException()
.isThrownBy(() -> metadataStore.addListener(null))
.withMessageContaining("'listener' must not be null");
metadataStore.addListener(new MetadataStoreListenerAdapter() {
@Override
@@ -365,14 +347,9 @@ public class ZookeeperMetadataStoreTests extends ZookeeperTestSupport {
@Test
public void testEnsureStarted() {
ZookeeperMetadataStore zookeeperMetadataStore = new ZookeeperMetadataStore(this.client);
try {
zookeeperMetadataStore.get("foo");
}
catch (Exception e) {
assertThat(e).isInstanceOf(IllegalStateException.class);
assertThat(e.getMessage()).contains("ZookeeperMetadataStore has to be started before using.");
}
assertThatIllegalStateException()
.isThrownBy(() -> zookeeperMetadataStore.get("foo"))
.withMessageContaining("ZookeeperMetadataStore has to be started before using.");
}
private void waitAtBarrier(String barrierName, Map<String, CyclicBarrier> barriers) {

View File

@@ -110,3 +110,9 @@ See <<./ip.adoc#failover-cf,TCP Failover Client Connection Factory>> for more in
A `decodeFluxAsUnit` option has been added to the `RSocketInboundGateway` with the meaning to decode incoming `Flux` as a single unit or apply decoding for each event in it.
See <<./rsocket.adoc#rsocket-inbound,RSocket Inbound Gateway>> for more information.
[[x5.3-zookeeper]]
=== Zookeeper Changes
A `LeaderInitiatorFactoryBean` (as well as its XML `<int-zk:leader-listener>`) exposes a `candidate` option for more control over a `Candidate` configuration.
See <<./zookeeper.adoc#zk-leadership,Leadership event handling>> for more information.

View File

@@ -118,3 +118,6 @@ public LeaderInitiatorFactoryBean leaderInitiator(CuratorFramework client) {
}
----
====
Starting with version 5.3, a `candidate` option is exposed on the `LeaderInitiatorFactoryBean` for more configuration control of the externally provided `Candidate` instance.
Only one of the `candidate` or `role` options has to be provided, but not both; the `role` options creates internally a `DefaultCandidate` instance with an `UUID` for `id` option.