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 new file mode 100644 index 0000000000..6ed37cd560 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/node/FileNodeListener.java @@ -0,0 +1,120 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.druid.pool.ha.node; + +import com.alibaba.druid.pool.ha.PropertiesUtils; +import com.alibaba.druid.support.logging.Log; +import com.alibaba.druid.support.logging.LogFactory; + +import java.util.List; +import java.util.Properties; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * A NodeList that monitors the change of a file. + * + * @author DigitalSonic + */ +public class FileNodeListener extends NodeListener { + private final static Log LOG = LogFactory.getLog(FileNodeListener.class); + + private String file = null; + private String prefix = ""; + private int intervalSeconds = 60; + private ScheduledExecutorService executor; + + /** + * Start a Scheduler to check the specified file. + * + * @see #setIntervalSeconds(int) + * @see #update() + */ + @Override + public void init() { + super.init(); + if (intervalSeconds <= 0) { + intervalSeconds = 60; + } + executor = Executors.newScheduledThreadPool(1); + executor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + try { + update(); + } catch (Exception e) { + LOG.error("Can NOT update the node list.", e); + } + } + }, 0, intervalSeconds, TimeUnit.SECONDS); + } + + /** + * Load the properties file and diff with the stored Properties. + * + * @return A List of the modification + */ + @Override + public List refresh() { + Properties originalProperties = PropertiesUtils.loadProperties(file); + List nameList = PropertiesUtils.loadNameList(originalProperties, prefix); + Properties properties = new Properties(); + for (String n : nameList) { + String url = originalProperties.getProperty(n + ".url"); + String username = originalProperties.getProperty(n + ".username"); + String password = originalProperties.getProperty(n + ".password"); + if (url == null || url.isEmpty() + || username == null || username.isEmpty() + || password == null || password.isEmpty()) { + continue; + } + properties.setProperty(n + ".url", url); + properties.setProperty(n + ".username", username); + properties.setProperty(n + ".password", password); + } + + List events = NodeEvent.getEventListFromProperties(getProperties(), properties); + if (events != null && !events.isEmpty()) { + setProperties(properties); + } + return events; + } + + public int getIntervalSeconds() { + return intervalSeconds; + } + + public void setIntervalSeconds(int intervalSeconds) { + this.intervalSeconds = intervalSeconds; + } + + public String getFile() { + return file; + } + + 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/NodeEvent.java b/src/main/java/com/alibaba/druid/pool/ha/node/NodeEvent.java new file mode 100644 index 0000000000..eaac382bf1 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/node/NodeEvent.java @@ -0,0 +1,118 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.druid.pool.ha.node; + +import com.alibaba.druid.pool.ha.PropertiesUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/** + * Define the detail of a node update event. + * + * @author DigitalSonic + */ +public class NodeEvent { + private NodeEventType type; + private String nodeName; + private String url; + private String username; + private String password; + + /** + * Diff the given two Properties. + * + * @return A List of AddEvent and DelEvent + */ + public static List getEventListFromProperties(Properties previous, Properties next) { + List prevNames = PropertiesUtils.loadNameList(previous, ""); + List nextNames = PropertiesUtils.loadNameList(next, ""); + + List namesToAdd = new ArrayList(); + List namesToDel = new ArrayList(); + for (String n : prevNames) { + if (n != null && !n.trim().isEmpty() && !nextNames.contains(n)) { + namesToDel.add(n); + } + } + for (String n : nextNames) { + if (n != null && !n.trim().isEmpty() && !prevNames.contains(n)) { + namesToAdd.add(n); + } + } + + List list = new ArrayList(); + list.addAll(generateEvents(next, namesToAdd, NodeEventType.ADD)); + list.addAll(generateEvents(previous, namesToDel, NodeEventType.DELETE)); + + return list; + } + + private static List generateEvents(Properties properties, List names, NodeEventType type) { + List list = new ArrayList(); + for (String n : names) { + NodeEvent event = new NodeEvent(); + event.setType(type); + event.setNodeName(n); + event.setUrl(properties.getProperty(n + ".url")); + event.setUsername(properties.getProperty(n + ".username")); + event.setPassword(properties.getProperty(n + ".password")); + list.add(event); + } + return list; + } + + public NodeEventType getType() { + return type; + } + + public void setType(NodeEventType type) { + this.type = type; + } + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/com/alibaba/druid/pool/ha/node/NodeEventType.java b/src/main/java/com/alibaba/druid/pool/ha/node/NodeEventType.java new file mode 100644 index 0000000000..078d1f45a6 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/node/NodeEventType.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.druid.pool.ha.node; + +/** + * Enum that specifies the type of a NodeEvent. + * + * @author DigitalSonic + */ +public enum NodeEventType { + ADD, DELETE +} 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 new file mode 100644 index 0000000000..4f61bef40b --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/node/NodeListener.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.druid.pool.ha.node; + +import java.util.Date; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.Properties; + +/** + * This abstract class defines a listener to monitor the change of DataSource nodes. + * + * @author DigitalSonic + * @see Observable + */ +public abstract class NodeListener extends Observable { + private Properties properties = new Properties(); + private Date lastUpdateTime = null; + private Observer observer = null; + + /** + * The method implements the detail update logic. + */ + public abstract List refresh(); + + /** + * Add the given PoolUpdater as the Observer. + * + * @see #setObserver(Observer) + */ + public void init() { + if (observer == null) { + throw new RuntimeException("No Observer(such as PoolUpdater) specified."); + } + this.addObserver(observer); + } + + /** + * Fire the refresh() method and notify the Observer. + * + * @see #refresh() + */ + public void update() { + List events = refresh(); + if (events != null && !events.isEmpty()) { + this.lastUpdateTime = new Date(); + NodeEvent[] arr = new NodeEvent[events.size()]; + for (int i = 0; i < events.size(); i++) { + arr[i] = events.get(i); + } + this.setChanged(); + this.notifyObservers(arr); + } + } + + public Observer getObserver() { + return observer; + } + + public void setObserver(Observer observer) { + this.observer = observer; + } + + public Date getLastUpdateTime() { + return lastUpdateTime; + } + + public void setLastUpdateTime(Date lastUpdateTime) { + this.lastUpdateTime = lastUpdateTime; + } + + public Properties getProperties() { + return properties; + } + + public void setProperties(Properties properties) { + this.properties = properties; + } +} 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 new file mode 100644 index 0000000000..dde3946929 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/ha/node/PoolUpdater.java @@ -0,0 +1,201 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.druid.pool.ha.node; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.pool.ha.DataSourceCreator; +import com.alibaba.druid.pool.ha.HighAvailableDataSource; +import com.alibaba.druid.support.logging.Log; +import com.alibaba.druid.support.logging.LogFactory; + +import javax.sql.DataSource; +import java.util.HashSet; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Update the DataSource Connection Pool when notified. + * + * @author DigitalSonic + */ +public class PoolUpdater implements Observer { + private final static Log LOG = LogFactory.getLog(PoolUpdater.class); + + private Set nodesToDel = new CopyOnWriteArraySet(); + private HighAvailableDataSource highAvailableDataSource; + + private Lock lock = new ReentrantLock(); + private ScheduledExecutorService executor; + + public PoolUpdater() { + executor = Executors.newScheduledThreadPool(1); + executor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + try { + removeDataSources(); + } catch (Exception e) { + LOG.error("Exception occurred while removing DataSources.", e); + } + } + }, 60, 60, TimeUnit.SECONDS); + } + + public PoolUpdater(HighAvailableDataSource highAvailableDataSource) { + this(); + setHighAvailableDataSource(highAvailableDataSource); + } + + @Override + public void update(Observable o, Object arg) { + if (!(o instanceof NodeListener)) { + return; + } + if (arg == null || !(arg instanceof NodeEvent[])) { + return; + } + NodeEvent[] events = (NodeEvent[]) arg; + + if (events.length <= 0) { + return; + } + + try { + LOG.info("Waiting for Lock to start processing NodeEvents."); + lock.lock(); + LOG.info("Start processing the NodeEvent[" + events.length + "]."); + for (NodeEvent e : events) { + if (e.getType() == NodeEventType.ADD) { + addNode(e); + } else if (e.getType() == NodeEventType.DELETE) { + deleteNode(e); + } + } + } catch (Exception e) { + LOG.error("Exception occurred while updating Pool.", e); + } finally { + lock.unlock(); + } + } + + public void removeDataSources() { + if (nodesToDel == null || nodesToDel.isEmpty()) { + return; + } + try { + lock.lock(); + Map map = highAvailableDataSource.getDataSourceMap(); + Set copySet = new HashSet(nodesToDel); + for (String nodeName : copySet) { + LOG.info("Start removing Node " + nodeName + "."); + if (!map.containsKey(nodeName)) { + LOG.info("Node " + nodeName + " is NOT existed in the map."); + cancelBlacklistNode(nodeName); + continue; + } + DataSource ds = map.get(nodeName); + if (ds instanceof DruidDataSource) { + DruidDataSource dds = (DruidDataSource) ds; + int activeCount = dds.getActiveCount(); // CAUTION, activeCount MAYBE changed! + if (activeCount > 0) { + LOG.warn("Node " + nodeName + " is still running [activeCount=" + activeCount + + "], try next time."); + continue; + } else { + LOG.info("Close Node " + nodeName + " and remove it."); + try { + dds.close(); + } catch (Exception e) { + LOG.error("Exception occurred while closing Node " + nodeName + + ", just remove it.", e); + } + } + } + map.remove(nodeName); // Remove the node directly if it is NOT a DruidDataSource. + cancelBlacklistNode(nodeName); + } + } catch (Exception e) { + LOG.error("Exception occurred while removing DataSources.", e); + } finally { + lock.unlock(); + } + } + + protected void addNode(NodeEvent event) { + String nodeName = event.getNodeName(); + String url = event.getUrl(); + String username = event.getUsername(); + String password = event.getPassword(); + + Map map = highAvailableDataSource.getDataSourceMap(); + if (nodeName == null || nodeName.isEmpty()) { + return; + } + LOG.info("Adding Node " + nodeName + "[url: " + url + ", username: " + username + "]."); + if (map.containsKey(nodeName)) { + cancelBlacklistNode(nodeName); + return; + } + try { + DruidDataSource dataSource = DataSourceCreator.create(nodeName, url, username, + password, this.highAvailableDataSource); + map.put(nodeName, dataSource); + } catch (Exception e) { + LOG.error("Can NOT create DataSource " + nodeName, e); + } + } + + protected void deleteNode(NodeEvent event) { + String nodeName = event.getNodeName(); + Map map = highAvailableDataSource.getDataSourceMap(); + if (nodeName == null || nodeName.isEmpty() || !map.containsKey(nodeName)) { + return; + } + blacklistNode(nodeName); + } + + private void cancelBlacklistNode(String nodeName) { + LOG.info("Cancel the deletion of Node " + nodeName + "."); + nodesToDel.remove(nodeName); + highAvailableDataSource.removeBlackList(nodeName); + } + + private void blacklistNode(String nodeName) { + LOG.info("Deleting Node " + nodeName + ", just add into blacklist."); + nodesToDel.add(nodeName); + highAvailableDataSource.addBlackList(nodeName); + } + + public HighAvailableDataSource getHighAvailableDataSource() { + return highAvailableDataSource; + } + + public void setHighAvailableDataSource(HighAvailableDataSource highAvailableDataSource) { + this.highAvailableDataSource = highAvailableDataSource; + } + + public Set getNodesToDel() { + return nodesToDel; + } +} diff --git a/src/test/java/com/alibaba/druid/pool/ha/node/FileNodeListenerTest.java b/src/test/java/com/alibaba/druid/pool/ha/node/FileNodeListenerTest.java new file mode 100644 index 0000000000..cc9decf9dd --- /dev/null +++ b/src/test/java/com/alibaba/druid/pool/ha/node/FileNodeListenerTest.java @@ -0,0 +1,71 @@ +package com.alibaba.druid.pool.ha.node; + +import org.junit.Test; +import sun.java2d.SurfaceDataProxy; + +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class FileNodeListenerTest { + @Test + public void testUpdate() throws InterruptedException { + final CountDownLatch cdl = new CountDownLatch(1); + String file = "/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties"; + FileNodeListener listener = new FileNodeListener(); + listener.setFile(file); + listener.setPrefix("prefix1"); + listener.setObserver(new Observer() { + @Override + public void update(Observable o, Object arg) { + cdl.countDown(); + assertTrue(o instanceof FileNodeListener); + assertTrue(arg instanceof NodeEvent[]); + NodeEvent[] events = (NodeEvent[]) arg; + assertEquals(1, events.length); + assertEquals(NodeEventType.ADD, events[0].getType()); + assertEquals("prefix1.foo", events[0].getNodeName()); + assertEquals("jdbc:derby:memory:foo1;create=true", events[0].getUrl()); + } + }); + listener.init(); + listener.update(); + assertTrue(cdl.await(100, TimeUnit.MILLISECONDS)); + } + + @Test + public void testRefresh_emptyPropertiesMatch() { + String file = "/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties"; + FileNodeListener listener = new FileNodeListener(); + listener.setFile(file); + listener.setPrefix("prefix3"); + List list = listener.refresh(); + + assertTrue(listener.getProperties().isEmpty()); + assertTrue(list.isEmpty()); + } + + @Test + public void testRefresh() { + String file = "/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties"; + FileNodeListener listener = new FileNodeListener(); + listener.setFile(file); + listener.setPrefix("prefix1"); + List list = listener.refresh(); + + Properties properties = listener.getProperties(); + assertEquals(3, properties.size()); + assertEquals("jdbc:derby:memory:foo1;create=true", properties.getProperty("prefix1.foo.url")); + + assertEquals(1, list.size()); + NodeEvent event = list.get(0); + assertEquals(NodeEventType.ADD, event.getType()); + assertEquals("prefix1.foo", event.getNodeName()); + assertEquals("jdbc:derby:memory:foo1;create=true", event.getUrl()); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..cf61193049 --- /dev/null +++ b/src/test/java/com/alibaba/druid/pool/ha/node/NodeEventTest.java @@ -0,0 +1,42 @@ +package com.alibaba.druid.pool.ha.node; + +import org.junit.Test; + +import java.util.List; +import java.util.Properties; + +import static org.junit.Assert.*; + +public class NodeEventTest { + + @Test + public void testGetEventListFromProperties_emptyProperties() { + Properties p1 = new Properties(); + Properties p2 = new Properties(); + + List list = NodeEvent.getEventListFromProperties(p1, p2); + assertTrue(list.isEmpty()); + + p1.setProperty("foo.url", "foo_url"); + p1.setProperty("foo.username", "foo_username"); + p1.setProperty("foo.password", "foo_password"); + + list = NodeEvent.getEventListFromProperties(p1, p2); + assertEquals(1, list.size()); + NodeEvent event = list.get(0); + assertEquals(NodeEventType.DELETE, event.getType()); + assertEquals("foo", event.getNodeName()); + assertEquals("foo_url", event.getUrl()); + assertEquals("foo_username", event.getUsername()); + assertEquals("foo_password", event.getPassword()); + + list = NodeEvent.getEventListFromProperties(p2, p1); + assertEquals(1, list.size()); + event = list.get(0); + assertEquals(NodeEventType.ADD, event.getType()); + assertEquals("foo", event.getNodeName()); + assertEquals("foo_url", event.getUrl()); + assertEquals("foo_username", event.getUsername()); + assertEquals("foo_password", event.getPassword()); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..82022e38c4 --- /dev/null +++ b/src/test/java/com/alibaba/druid/pool/ha/node/PoolUpdaterTest.java @@ -0,0 +1,148 @@ +package com.alibaba.druid.pool.ha.node; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.pool.ha.HighAvailableDataSource; +import com.alibaba.druid.pool.ha.MockDataSource; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; + +public class PoolUpdaterTest { + private HighAvailableDataSource haDataSource; + private PoolUpdater updater; + + @Before + public void setUp() throws Exception { + haDataSource = new HighAvailableDataSource(); + Map map = new HashMap(); + haDataSource.setDataSourceMap(map); + + updater = new PoolUpdater(haDataSource); + } + + @After + public void tearDown() throws Exception { + haDataSource = null; + updater = null; + } + + @Test + public void testUpdate() { + haDataSource.getDataSourceMap().put("foo", new MockDataSource("foo")); + + NodeEvent event = new NodeEvent(); + event.setNodeName("foo"); + event.setType(NodeEventType.DELETE); + + updater.update(new FileNodeListener(), new NodeEvent[] { event }); + validateDeleteNode(); + + event = new NodeEvent(); + event.setNodeName("foo"); + event.setType(NodeEventType.ADD); + updater.update(new FileNodeListener(), new NodeEvent[] { event }); + assertFalse(updater.getNodesToDel().contains("foo")); + assertFalse(haDataSource.isInBlackList("foo")); + } + + @Test + public void testRemoveDataSources() { + String url = "jdbc:derby:memory:foo;create=true"; + String name = "foo"; + addNode(url, name); + DruidDataSource ds = (DruidDataSource) haDataSource.getDataSourceMap().get(name); + + updater.getNodesToDel().add(name); + haDataSource.addBlackList(name); + + updater.removeDataSources(); + assertTrue(haDataSource.getDataSourceMap().isEmpty()); + assertFalse(haDataSource.isInBlackList(name)); + assertTrue(ds.isClosed()); + } + + @Test + public void testRemoveDataSources_nodeNotExisted() { + DataSource ds = new MockDataSource("foo"); + haDataSource.getDataSourceMap().put("foo", ds); + updater.getNodesToDel().add("bar"); + updater.removeDataSources(); + assertFalse(updater.getNodesToDel().contains("bar")); + assertEquals(1, haDataSource.getDataSourceMap().size()); + } + + @Test + public void testRemoveDataSources_notDruid() { + DataSource ds = new MockDataSource("foo"); + haDataSource.getDataSourceMap().put("foo", ds); + updater.getNodesToDel().add("foo"); + updater.removeDataSources(); + assertFalse(updater.getNodesToDel().contains("foo")); + assertTrue(haDataSource.getDataSourceMap().isEmpty()); + } + + @Test + public void testAddNode() { + String url = "jdbc:derby:memory:foo;create=true"; + String name = "foo"; + addNode(url, name); + String targetUrl = ((DruidDataSource) haDataSource.getDataSourceMap().get(name)).getUrl(); + assertEquals(1, haDataSource.getDataSourceMap().size()); + assertEquals(url, targetUrl); + } + + @Test + public void testAddNode_alreadyExisted() { + DataSource ds = new MockDataSource("foo"); + haDataSource.getDataSourceMap().put("foo", ds); + updater.getNodesToDel().add("foo"); + + addNode("foo_url", "foo"); + + assertFalse(updater.getNodesToDel().contains("foo")); + assertFalse(haDataSource.isInBlackList("foo")); + assertEquals(ds, haDataSource.getDataSourceMap().get("foo")); + } + + @Test + public void testDeleteNode_notExisted() { + NodeEvent event = new NodeEvent(); + event.setNodeName("foo"); + event.setType(NodeEventType.DELETE); + updater.deleteNode(event); + assertTrue(updater.getNodesToDel().isEmpty()); + assertFalse(haDataSource.isInBlackList("foo")); + } + + @Test + public void testDeleteNode() { + haDataSource.getDataSourceMap().put("foo", new MockDataSource("foo")); + + NodeEvent event = new NodeEvent(); + event.setNodeName("foo"); + event.setType(NodeEventType.DELETE); + updater.deleteNode(event); + validateDeleteNode(); + } + + private void validateDeleteNode() { + assertEquals(1, updater.getNodesToDel().size()); + assertTrue(updater.getNodesToDel().contains("foo")); + assertTrue(haDataSource.isInBlackList("foo")); + } + + private void addNode(String url, String name) { + NodeEvent event = new NodeEvent(); + event.setNodeName(name); + event.setUrl(url); + event.setType(NodeEventType.ADD); + updater.addNode(event); + } +} \ No newline at end of file diff --git a/src/test/resources/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties b/src/test/resources/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties index b008c63d9f..ae431a0e65 100644 --- a/src/test/resources/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties +++ b/src/test/resources/com/alibaba/druid/pool/ha/ha-with-prefix-datasource.properties @@ -1,4 +1,8 @@ prefix1.foo.url=jdbc:derby:memory:foo1;create=true +prefix1.foo.username=foo +prefix1.foo.password=foo_password prefix2.foo.url=jdbc:derby:memory:foo2;create=true +prefix2.foo.username=bar +prefix2.foo.password=bar_password foo.url=jdbc:derby:memory:foo3;create=true bar=jdbc:derby:memory:bar;create=true