diff --git a/src/main/java/com/alibaba/druid/pool/ha/node/FileNodeListener.java b/src/main/java/com/alibaba/druid/pool/ha/node/FileNodeListener.java index a4e135b98d..957d066196 100644 --- a/src/main/java/com/alibaba/druid/pool/ha/node/FileNodeListener.java +++ b/src/main/java/com/alibaba/druid/pool/ha/node/FileNodeListener.java @@ -37,7 +37,6 @@ public class FileNodeListener extends NodeListener { private Lock lock = new ReentrantLock(); private String file = null; - private String prefix = ""; private int intervalSeconds = 60; private ScheduledExecutorService executor; @@ -81,7 +80,7 @@ public class FileNodeListener extends NodeListener { @Override public List refresh() { Properties originalProperties = PropertiesUtils.loadProperties(file); - List nameList = PropertiesUtils.loadNameList(originalProperties, prefix); + List nameList = PropertiesUtils.loadNameList(originalProperties, getPrefix()); Properties properties = new Properties(); for (String n : nameList) { String url = originalProperties.getProperty(n + ".url"); @@ -143,12 +142,4 @@ public class FileNodeListener extends NodeListener { public void setFile(String file) { this.file = file; } - - public String getPrefix() { - return prefix; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } } diff --git a/src/main/java/com/alibaba/druid/pool/ha/node/NodeListener.java b/src/main/java/com/alibaba/druid/pool/ha/node/NodeListener.java index 30ca181962..8fd6aef664 100644 --- a/src/main/java/com/alibaba/druid/pool/ha/node/NodeListener.java +++ b/src/main/java/com/alibaba/druid/pool/ha/node/NodeListener.java @@ -36,6 +36,7 @@ import java.util.concurrent.locks.ReentrantLock; public abstract class NodeListener extends Observable { private final static Log LOG = LogFactory.getLog(NodeListener.class); + private String prefix = ""; private Properties properties = new Properties(); private Date lastUpdateTime = null; private Observer observer = null; @@ -111,4 +112,12 @@ public abstract class NodeListener extends Observable { public void setProperties(Properties properties) { this.properties = properties; } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } } diff --git a/src/main/java/com/alibaba/druid/pool/ha/node/PoolUpdater.java b/src/main/java/com/alibaba/druid/pool/ha/node/PoolUpdater.java index 2310a07447..95628822f5 100644 --- a/src/main/java/com/alibaba/druid/pool/ha/node/PoolUpdater.java +++ b/src/main/java/com/alibaba/druid/pool/ha/node/PoolUpdater.java @@ -231,7 +231,7 @@ public class PoolUpdater implements Observer { } private void blacklistNode(String nodeName) { - LOG.info("Deleting Node " + nodeName + ", just add into blacklist."); + LOG.info("Deleting Node " + nodeName + ", just add it into blacklist."); nodesToDel.add(nodeName); highAvailableDataSource.addBlackList(nodeName); } diff --git a/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeInfo.java b/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeInfo.java index d049d8f533..9b5c402e66 100644 --- a/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeInfo.java +++ b/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeInfo.java @@ -24,6 +24,13 @@ public class ZookeeperNodeInfo { private String prefix = ""; private String host; private Integer port; + /** + * Database can be the following ones: + * 1. Database in MySQL and PostgreSQL JDBC URL + * 2. ServiceName or SID in Oracle JDBC URL + * etc. + */ + private String database; private String username; private String password; @@ -71,4 +78,12 @@ public class ZookeeperNodeInfo { public void setPassword(String password) { this.password = password; } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } } diff --git a/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeListener.java b/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeListener.java index 37b223b243..94538809a6 100644 --- a/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeListener.java +++ b/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeListener.java @@ -57,6 +57,7 @@ import java.util.concurrent.locks.ReentrantLock; * bar.port=3309 * bar.username=bar * bar.password=bar_password + * bar.database=bar_database * * * @author DigitalSonic @@ -65,15 +66,14 @@ public class ZookeeperNodeListener extends NodeListener { private final static Log LOG = LogFactory.getLog(ZookeeperNodeListener.class); private String zkConnectString; private String path = "/ha-druid-datasources"; - private String dataPrefix = ""; private Lock lock = new ReentrantLock(); private boolean privateZkClient = false; // Should I close the client? private PathChildrenCache cache; private CuratorFramework client; /** - * Template Format: - * jdbc:mysql://${host}:${port}/foo?useUnicode=true - * ${host} and ${port} will be replaced by values in ZK + * URL Template, e.g. + * jdbc:mysql://${host}:${port}/${database}?useUnicode=true + * ${host}, ${port} and ${database} will be replaced by values in ZK */ private String urlTemplate; @@ -183,7 +183,7 @@ public class ZookeeperNodeListener extends NodeListener { ChildData data = event.getData(); String nodeName = getNodeName(data); List names = new ArrayList(); - names.add(dataPrefix + "." + nodeName); + names.add(getPrefix() + "." + nodeName); Properties properties = getPropertiesFromChildData(data); List events = NodeEvent.generateEvents(properties, names, type); @@ -228,6 +228,7 @@ public class ZookeeperNodeListener extends NodeListener { } private Properties getPropertiesFromChildData(ChildData data) { + String dataPrefix = getPrefix(); Properties properties = new Properties(); if (data == null) { return properties; @@ -243,7 +244,7 @@ public class ZookeeperNodeListener extends NodeListener { Properties filtered = PropertiesUtils.filterPrefix(full, dataPrefix); for (String n : filtered.stringPropertyNames()) { properties.setProperty( - n.replaceFirst(dataPrefix, dataPrefix + "." + nodeName), + n.replaceFirst(dataPrefix, dataPrefix + "\\." + nodeName), filtered.getProperty(n)); } if (!properties.containsKey(dataPrefix + "." + nodeName + ".url")) { @@ -254,12 +255,16 @@ public class ZookeeperNodeListener extends NodeListener { private String formatUrl(Properties properties) { String url = urlTemplate; + String dataPrefix = getPrefix(); if (properties.containsKey(dataPrefix + ".host")) { url = url.replace("${host}", properties.getProperty(dataPrefix + ".host")); } if (properties.containsKey(dataPrefix + ".port")) { url = url.replace("${port}", properties.getProperty(dataPrefix + ".port")); } + if (properties.containsKey(dataPrefix + ".database")) { + url = url.replace("${database}", properties.getProperty(dataPrefix + ".database")); + } return url; } @@ -299,14 +304,6 @@ public class ZookeeperNodeListener extends NodeListener { this.path = path; } - public String getDataPrefix() { - return dataPrefix; - } - - public void setDataPrefix(String dataPrefix) { - this.dataPrefix = dataPrefix; - } - public String getUrlTemplate() { return urlTemplate; } diff --git a/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeRegister.java b/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeRegister.java index d176c394cd..5ce02cfa96 100644 --- a/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeRegister.java +++ b/src/main/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeRegister.java @@ -131,6 +131,9 @@ public class ZookeeperNodeRegister { if (n.getPort() != null) { properties.setProperty(n.getPrefix() + "port", n.getPort().toString()); } + if (n.getDatabase() != null) { + properties.setProperty(n.getPrefix() + "database", n.getDatabase()); + } if (n.getUsername() != null) { properties.setProperty(n.getPrefix() + "username", n.getUsername()); } diff --git a/src/test/java/com/alibaba/druid/pool/ha/node/NodeEventTest.java b/src/test/java/com/alibaba/druid/pool/ha/node/NodeEventTest.java index f24ed7f535..258134af04 100644 --- a/src/test/java/com/alibaba/druid/pool/ha/node/NodeEventTest.java +++ b/src/test/java/com/alibaba/druid/pool/ha/node/NodeEventTest.java @@ -5,7 +5,8 @@ import org.junit.Test; import java.util.List; import java.util.Properties; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class NodeEventTest { diff --git a/src/test/java/com/alibaba/druid/pool/ha/node/PoolUpdaterTest.java b/src/test/java/com/alibaba/druid/pool/ha/node/PoolUpdaterTest.java index 700abc5c5c..66695819c1 100644 --- a/src/test/java/com/alibaba/druid/pool/ha/node/PoolUpdaterTest.java +++ b/src/test/java/com/alibaba/druid/pool/ha/node/PoolUpdaterTest.java @@ -11,7 +11,9 @@ import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class PoolUpdaterTest { private HighAvailableDataSource haDataSource; diff --git a/src/test/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeListenerTest.java b/src/test/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeListenerTest.java index 8eedda1167..619e111e5a 100644 --- a/src/test/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeListenerTest.java +++ b/src/test/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeListenerTest.java @@ -1,6 +1,8 @@ package com.alibaba.druid.pool.ha.node; import com.alibaba.druid.DruidRuntimeException; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.pool.ha.HighAvailableDataSource; import com.alibaba.druid.support.logging.Log; import com.alibaba.druid.support.logging.LogFactory; import org.apache.curator.test.TestingServer; @@ -17,6 +19,9 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -35,6 +40,48 @@ public class ZookeeperNodeListenerTest { server.close(); } + @Test + public void testHaWithZk() throws Exception { + // 1. Init HighAvailableDataSource + ZookeeperNodeListener listener = new ZookeeperNodeListener(); + listener.setZkConnectString(server.getConnectString()); + listener.setPrefix("foo"); + listener.setPath(PATH); + listener.setUrlTemplate("jdbc:derby:memory:${database};create=true"); + HighAvailableDataSource dataSource = new HighAvailableDataSource(); + dataSource.setNodeListener(listener); + dataSource.setPoolPurgeIntervalSeconds(10); + dataSource.init(); + assertTrue(dataSource.getDataSourceMap().isEmpty()); + + // 2. Register one Node + ZookeeperNodeRegister register = registerNodeAndReturnRegister(); + + Thread.sleep(3000); // Wait for the Node to be created. + assertFalse(dataSource.getDataSourceMap().isEmpty()); + assertEquals(1, dataSource.getAvailableDataSourceMap().size()); + DruidDataSource ds = (DruidDataSource) dataSource.getAvailableDataSourceMap().get("foo.test-foo"); + assertEquals("jdbc:derby:memory:foo;create=true", ds.getUrl()); + assertNotNull(dataSource.getConnection()); + + // 3. Delete one Node + register.destroy(); + boolean checkBlackList = false; + for (int i = 0; i < 10; i++) { + Thread.sleep(500); + if (dataSource.isInBlackList("foo.test-foo")) { + assertTrue(dataSource.getAvailableDataSourceMap().isEmpty()); + assertFalse(dataSource.getDataSourceMap().isEmpty()); + assertNull(dataSource.getConnection()); + checkBlackList = true; + break; + } + } + assertTrue(checkBlackList); + + dataSource.destroy(); + } + @Test public void testAddOneNode() throws Exception { CountDownLatch cdl = new CountDownLatch(1); @@ -42,7 +89,7 @@ public class ZookeeperNodeListenerTest { listener.setZkConnectString(server.getConnectString()); listener.setPath(PATH); listener.setUrlTemplate("jdbc:mysql://${host}:${port}/foo"); - listener.setDataPrefix("foo"); + listener.setPrefix("foo"); listener.setObserver(new Observer() { @Override public void update(Observable o, Object arg) { @@ -51,8 +98,8 @@ public class ZookeeperNodeListenerTest { NodeEvent event = events[0]; LOG.info("NodeEvent received: " + event); if (NodeEventTypeEnum.ADD == event.getType()) { - assertEquals("foo", event.getUsername()); - assertEquals("password", event.getPassword()); + assertEquals("sa", event.getUsername()); + assertEquals("", event.getPassword()); assertEquals("foo.test-foo", event.getNodeName()); assertEquals("jdbc:mysql://127.0.0.1:1234/foo", event.getUrl()); @@ -67,9 +114,9 @@ public class ZookeeperNodeListenerTest { cdl.await(10, TimeUnit.SECONDS); Properties properties = listener.getProperties(); - assertEquals(5, properties.size()); - assertEquals("foo", properties.getProperty("foo.test-foo.username")); - assertEquals("password", properties.getProperty("foo.test-foo.password")); + assertEquals(6, properties.size()); + assertEquals("sa", properties.getProperty("foo.test-foo.username")); + assertEquals("", properties.getProperty("foo.test-foo.password")); assertEquals("127.0.0.1", properties.getProperty("foo.test-foo.host")); assertEquals("1234", properties.getProperty("foo.test-foo.port")); assertEquals("jdbc:mysql://127.0.0.1:1234/foo", @@ -89,7 +136,7 @@ public class ZookeeperNodeListenerTest { listener.setZkConnectString(server.getConnectString()); listener.setPath(PATH); listener.setUrlTemplate("jdbc:mysql://${host}:${port}/foo"); - listener.setDataPrefix("foo"); + listener.setPrefix("foo"); listener.setObserver(new Observer() { @Override public void update(Observable o, Object arg) { @@ -98,8 +145,8 @@ public class ZookeeperNodeListenerTest { NodeEvent event = events[0]; LOG.info("NodeEvent received: " + event); if (NodeEventTypeEnum.DELETE == event.getType()) { - assertEquals("foo", event.getUsername()); - assertEquals("password", event.getPassword()); + assertEquals("sa", event.getUsername()); + assertEquals("", event.getPassword()); assertEquals("foo.test-foo", event.getNodeName()); assertEquals("jdbc:mysql://127.0.0.1:1234/foo", event.getUrl()); @@ -113,7 +160,7 @@ public class ZookeeperNodeListenerTest { listener.update(); addCDL.await(10, TimeUnit.SECONDS); Properties properties = listener.getProperties(); - assertEquals(5, properties.size()); + assertEquals(6, properties.size()); register.destroy(); removeCDL.await(10, TimeUnit.SECONDS); @@ -132,8 +179,9 @@ public class ZookeeperNodeListenerTest { node.setPrefix("foo"); node.setHost("127.0.0.1"); node.setPort(1234); - node.setUsername("foo"); - node.setPassword("password"); + node.setDatabase("foo"); + node.setUsername("sa"); + node.setPassword(""); payload.add(node); register.register("test-foo", payload); return register; diff --git a/src/test/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeRegisterTest.java b/src/test/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeRegisterTest.java index 7c927961ed..8a8c341e72 100644 --- a/src/test/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeRegisterTest.java +++ b/src/test/java/com/alibaba/druid/pool/ha/node/ZookeeperNodeRegisterTest.java @@ -3,8 +3,6 @@ package com.alibaba.druid.pool.ha.node; import com.alibaba.druid.support.logging.Log; import com.alibaba.druid.support.logging.LogFactory; import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.framework.CuratorFrameworkFactory; -import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.curator.test.TestingServer; import org.junit.After; import org.junit.AfterClass; @@ -17,12 +15,14 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class ZookeeperNodeRegisterTest { private final static Log LOG = LogFactory.getLog(ZookeeperNodeRegisterTest.class); - private final String PATH = "/ha-druid-datasource"; private static TestingServer server; + private final String PATH = "/ha-druid-datasource"; private ZookeeperNodeRegister register; @BeforeClass @@ -56,6 +56,7 @@ public class ZookeeperNodeRegisterTest { node1.setPrefix("foo"); node1.setHost("127.0.0.1"); node1.setPort(1234); + node1.setDatabase("foo_db"); node1.setUsername("foo"); node1.setPassword("password"); payload.add(node1); @@ -87,6 +88,9 @@ public class ZookeeperNodeRegisterTest { properties.load(new StringReader(str)); validateNodeProperties(node1, properties); validateNodeProperties(node2, properties); + assertTrue(properties.containsKey("foo.database")); + assertFalse(properties.containsKey("bar.database")); + assertEquals("foo_db", properties.getProperty("foo.database")); } private void validateNodeProperties(ZookeeperNodeInfo node, Properties properties) {